Skip to content

Commit bf289fb

Browse files
authored
Fix item dedupe in PackageSpecFactory (#6993)
1 parent f470286 commit bf289fb

2 files changed

Lines changed: 100 additions & 2 deletions

File tree

src/NuGet.Core/NuGet.Commands/RestoreCommand/Utility/ProjectItemIdentityComparer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ internal class ProjectItemIdentityComparer : IEqualityComparer<IItem>
1212
{
1313
internal static ProjectItemIdentityComparer Default { get; } = new();
1414

15+
private IEqualityComparer<string> _identityComparer = StringComparer.OrdinalIgnoreCase;
16+
1517
public bool Equals(IItem x, IItem y)
1618
{
17-
return StringComparer.InvariantCultureIgnoreCase.Equals(x.Identity, y.Identity);
19+
return _identityComparer.Equals(x.Identity, y.Identity);
1820
}
1921
public int GetHashCode(IItem obj)
2022
{
21-
return obj.Identity.GetHashCode();
23+
return _identityComparer.GetHashCode(obj.Identity);
2224
}
2325
}
2426
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using FluentAssertions;
8+
using NuGet.Commands.Restore;
9+
using NuGet.Commands.Restore.Utility;
10+
using Xunit;
11+
12+
namespace NuGet.Commands.Test.RestoreCommandTests.Utility;
13+
14+
public class ProjectItemIdentityComparerTests
15+
{
16+
[Fact]
17+
public void Distinct_WithDifferentPackageNames_DoesNotDeduplicate()
18+
{
19+
// Arrange
20+
var item1 = new TestItem("packagea", new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
21+
{ { "Version", "1.0.0" } }
22+
);
23+
var item2 = new TestItem("packageb", new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
24+
{ { "Version", "1.0.0" } }
25+
);
26+
List<IItem> items = [item1, item2];
27+
28+
// Act
29+
var result = items.Distinct(ProjectItemIdentityComparer.Default).ToList();
30+
31+
// Assert
32+
result.Should().HaveCount(2);
33+
}
34+
35+
[Fact]
36+
public void Distinct_WithSamePackageNameDifferentVersions_Deduplicates()
37+
{
38+
// Arrange
39+
var item1 = new TestItem("packagea", new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
40+
{ { "Version", "1.0.0" } }
41+
);
42+
var item2 = new TestItem("packagea", new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
43+
{ { "Version", "2.0.0" } }
44+
);
45+
List<IItem> items = [item1, item2];
46+
47+
// Act
48+
var result = items.Distinct(ProjectItemIdentityComparer.Default).ToList();
49+
50+
// Assert
51+
result.Should().HaveCount(1);
52+
}
53+
54+
[Fact]
55+
public void Distinct_WithDuplicateMixedCasePackageName_Deduplicates()
56+
{
57+
// Arrange
58+
var item1 = new TestItem("packagea", new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
59+
{ { "Version", "1.0.0" } }
60+
);
61+
var item2 = new TestItem("PackageA", new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
62+
{ { "Version", "1.0.0" } }
63+
);
64+
List<IItem> items = [item1, item2];
65+
66+
// Act
67+
var result = items.Distinct(ProjectItemIdentityComparer.Default).ToList();
68+
69+
// Assert
70+
result.Should().HaveCount(1);
71+
}
72+
73+
private class TestItem : IItem
74+
{
75+
private readonly IReadOnlyDictionary<string, string> _metadata;
76+
77+
public TestItem(string identity, IReadOnlyDictionary<string, string> metadata)
78+
{
79+
Identity = identity;
80+
_metadata = metadata;
81+
}
82+
public string Identity { get; }
83+
84+
public string GetMetadata(string name)
85+
{
86+
if (_metadata.TryGetValue(name, out string? value))
87+
{
88+
if (!string.IsNullOrWhiteSpace(value))
89+
{
90+
return value.Trim();
91+
}
92+
}
93+
return null!;
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)