Skip to content

Commit 25df905

Browse files
authored
Test that StringComparers and StringComparisons are kept in sync (#5428)
* Test that StringComparers and StringComparisons are kept in sync These two utility classes should have the same members, with equivalent values. This change adds missing properties to both clases, and adds a test to ensure that they're kept in sync in future. * Avoid test failures when current culture is invariant
1 parent e4ef146 commit 25df905

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

src/Shared/StringComparers.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
namespace Aspire;
55

6+
// NOTE unit tests enforce that these two classes are kept in sync
7+
68
internal static class StringComparers
79
{
810
public static StringComparer ResourceName => StringComparer.OrdinalIgnoreCase;
@@ -11,6 +13,8 @@ internal static class StringComparers
1113
public static StringComparer ResourceType => StringComparer.Ordinal;
1214
public static StringComparer ResourcePropertyName => StringComparer.Ordinal;
1315
public static StringComparer ResourceOwnerName => StringComparer.Ordinal;
16+
public static StringComparer ResourceOwnerKind => StringComparer.Ordinal;
17+
public static StringComparer ResourceOwnerUid => StringComparer.Ordinal;
1418
public static StringComparer UserTextSearch => StringComparer.CurrentCultureIgnoreCase;
1519
public static StringComparer EnvironmentVariableName => StringComparer.InvariantCultureIgnoreCase;
1620
public static StringComparer UrlPath => StringComparer.OrdinalIgnoreCase;
@@ -26,10 +30,13 @@ internal static class StringComparisons
2630
public static StringComparison EndpointAnnotationName => StringComparison.OrdinalIgnoreCase;
2731
public static StringComparison ResourceType => StringComparison.Ordinal;
2832
public static StringComparison ResourcePropertyName => StringComparison.Ordinal;
33+
public static StringComparison ResourceOwnerName => StringComparison.Ordinal;
2934
public static StringComparison ResourceOwnerKind => StringComparison.Ordinal;
3035
public static StringComparison ResourceOwnerUid => StringComparison.Ordinal;
3136
public static StringComparison UserTextSearch => StringComparison.CurrentCultureIgnoreCase;
3237
public static StringComparison EnvironmentVariableName => StringComparison.InvariantCultureIgnoreCase;
3338
public static StringComparison UrlPath => StringComparison.OrdinalIgnoreCase;
3439
public static StringComparison UrlHost => StringComparison.OrdinalIgnoreCase;
40+
public static StringComparison Attribute => StringComparison.Ordinal;
41+
public static StringComparison GridColumn => StringComparison.Ordinal;
3542
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Xunit;
5+
using System.Reflection;
6+
using System.Globalization;
7+
8+
namespace Aspire.Hosting.Tests.Utils;
9+
10+
public sealed class StringComparersTests
11+
{
12+
[Fact]
13+
public void StringComparersAndStringComparisonsMatch()
14+
{
15+
var flags = BindingFlags.Public | BindingFlags.Static;
16+
17+
var comparers = typeof(StringComparers).GetProperties(flags).OrderBy(c => c.Name, StringComparer.Ordinal).ToList();
18+
var comparisons = typeof(StringComparisons).GetProperties(flags).OrderBy(c => c.Name, StringComparer.Ordinal).ToList();
19+
20+
var currentCulture = CultureInfo.CurrentCulture;
21+
var currentUICulture = CultureInfo.CurrentUICulture;
22+
var defaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentCulture;
23+
var defaultThreadCurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture;
24+
25+
try
26+
{
27+
// Temporarily set the culture to en-AU to ensure consistent results.
28+
// This prevents test failures when the current culture is the invariant culture.
29+
CultureInfo.CurrentCulture = new CultureInfo("en-AU");
30+
CultureInfo.CurrentUICulture = new CultureInfo("en-AU");
31+
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-AU");
32+
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-AU");
33+
34+
ValidateSets();
35+
36+
ValidateValues();
37+
}
38+
finally
39+
{
40+
CultureInfo.CurrentCulture = currentCulture;
41+
CultureInfo.CurrentUICulture = currentUICulture;
42+
CultureInfo.DefaultThreadCurrentCulture = defaultThreadCurrentCulture;
43+
CultureInfo.DefaultThreadCurrentUICulture = defaultThreadCurrentUICulture;
44+
}
45+
46+
void ValidateSets()
47+
{
48+
var comparerNames = comparers.Select(c => c.Name).ToList();
49+
var comparisonNames = comparisons.Select(c => c.Name).ToList();
50+
51+
// Check that all comparers have a matching comparison.
52+
// Include details about which ones are missing in the test failure message to make fixing easier.
53+
var extraComparers = comparerNames.Except(comparisonNames, StringComparer.Ordinal).ToList();
54+
var extraComparisons = comparisonNames.Except(comparerNames, StringComparer.Ordinal).ToList();
55+
56+
if (extraComparers.Count + extraComparisons.Count != 0)
57+
{
58+
Assert.Fail($"""
59+
Mismatched {nameof(StringComparers)} and {nameof(StringComparisons)}:
60+
- Comparers without matching comparisons: {string.Join(", ", extraComparers)}
61+
- Comparisons without matching comparers: {string.Join(", ", extraComparisons)}
62+
""");
63+
}
64+
}
65+
66+
void ValidateValues()
67+
{
68+
var comparerValues = comparers.Select(c => (c.Name, Value: (StringComparer)c.GetValue(null)!)).ToList();
69+
var comparisonValues = comparisons.Select(c => (c.Name, Value: (StringComparison)c.GetValue(null)!)).ToList();
70+
71+
// Check that all comparer values match the corresponding comparison values.
72+
foreach (var (comparer, comparison) in comparerValues.Zip(comparisonValues))
73+
{
74+
Assert.Equal(comparer.Name, comparison.Name, StringComparer.Ordinal);
75+
76+
var comparerKind = GetComparerKind(comparer.Value);
77+
var comparisonKind = GetComparisonKind(comparison.Value);
78+
79+
if (!string.Equals(comparerKind, comparisonKind, StringComparison.Ordinal))
80+
{
81+
Assert.Fail($"""
82+
Mismatched comparisons:
83+
- {nameof(StringComparers)}.{comparer.Name} = {comparerKind}
84+
- {nameof(StringComparisons)}.{comparer.Name} = {comparisonKind}
85+
""");
86+
}
87+
}
88+
89+
return;
90+
91+
static string GetComparerKind(StringComparer comparer)
92+
{
93+
foreach (var (c, name) in Comparers())
94+
{
95+
if (Equals(c, comparer))
96+
{
97+
return name;
98+
}
99+
}
100+
101+
Assert.Fail("Unknown comparer: " + comparer);
102+
return null!; // Unreachable
103+
104+
static IEnumerable<(StringComparer, string)> Comparers()
105+
{
106+
yield return (StringComparer.Ordinal, nameof(StringComparer.Ordinal));
107+
yield return (StringComparer.OrdinalIgnoreCase, nameof(StringComparer.OrdinalIgnoreCase));
108+
yield return (StringComparer.CurrentCulture, nameof(StringComparer.CurrentCulture));
109+
yield return (StringComparer.CurrentCultureIgnoreCase, nameof(StringComparer.CurrentCultureIgnoreCase));
110+
yield return (StringComparer.InvariantCulture, nameof(StringComparer.InvariantCulture));
111+
yield return (StringComparer.InvariantCultureIgnoreCase, nameof(StringComparer.InvariantCultureIgnoreCase));
112+
}
113+
}
114+
115+
static string GetComparisonKind(StringComparison comparison)
116+
{
117+
foreach (var (c, name) in Comparisons())
118+
{
119+
if (c == comparison)
120+
{
121+
return name;
122+
}
123+
}
124+
125+
Assert.Fail("Unknown comparison: " + comparison);
126+
return null!; // Unreachable
127+
128+
static IEnumerable<(StringComparison, string)> Comparisons()
129+
{
130+
yield return (StringComparison.Ordinal, nameof(StringComparison.Ordinal));
131+
yield return (StringComparison.OrdinalIgnoreCase, nameof(StringComparison.OrdinalIgnoreCase));
132+
yield return (StringComparison.CurrentCulture, nameof(StringComparison.CurrentCulture));
133+
yield return (StringComparison.CurrentCultureIgnoreCase, nameof(StringComparison.CurrentCultureIgnoreCase));
134+
yield return (StringComparison.InvariantCulture, nameof(StringComparison.InvariantCulture));
135+
yield return (StringComparison.InvariantCultureIgnoreCase, nameof(StringComparison.InvariantCultureIgnoreCase));
136+
}
137+
}
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)