File: Utils\StringComparersTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Xunit;
using System.Reflection;
using System.Globalization;
 
namespace Aspire.Hosting.Tests.Utils;
 
public sealed class StringComparersTests
{
    [Fact]
    public void StringComparersAndStringComparisonsMatch()
    {
        var flags = BindingFlags.Public | BindingFlags.Static;
 
        var comparers = typeof(StringComparers).GetProperties(flags).OrderBy(c => c.Name, StringComparer.Ordinal).ToList();
        var comparisons = typeof(StringComparisons).GetProperties(flags).OrderBy(c => c.Name, StringComparer.Ordinal).ToList();
 
        var currentCulture = CultureInfo.CurrentCulture;
        var currentUICulture = CultureInfo.CurrentUICulture;
        var defaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentCulture;
        var defaultThreadCurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture;
 
        try
        {
            // Temporarily set the culture to en-AU to ensure consistent results.
            // This prevents test failures when the current culture is the invariant culture.
            CultureInfo.CurrentCulture = new CultureInfo("en-AU");
            CultureInfo.CurrentUICulture = new CultureInfo("en-AU");
            CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-AU");
            CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-AU");
 
            ValidateSets();
 
            ValidateValues();
        }
        finally
        {
            CultureInfo.CurrentCulture = currentCulture;
            CultureInfo.CurrentUICulture = currentUICulture;
            CultureInfo.DefaultThreadCurrentCulture = defaultThreadCurrentCulture;
            CultureInfo.DefaultThreadCurrentUICulture = defaultThreadCurrentUICulture;
        }
 
        void ValidateSets()
        {
            var comparerNames = comparers.Select(c => c.Name).ToList();
            var comparisonNames = comparisons.Select(c => c.Name).ToList();
 
            // Check that all comparers have a matching comparison.
            // Include details about which ones are missing in the test failure message to make fixing easier.
            var extraComparers = comparerNames.Except(comparisonNames, StringComparer.Ordinal).ToList();
            var extraComparisons = comparisonNames.Except(comparerNames, StringComparer.Ordinal).ToList();
 
            if (extraComparers.Count + extraComparisons.Count != 0)
            {
                Assert.Fail($"""
                Mismatched {nameof(StringComparers)} and {nameof(StringComparisons)}:
                - Comparers without matching comparisons: {string.Join(", ", extraComparers)}
                - Comparisons without matching comparers: {string.Join(", ", extraComparisons)}
                """);
            }
        }
 
        void ValidateValues()
        {
            var comparerValues = comparers.Select(c => (c.Name, Value: (StringComparer)c.GetValue(null)!)).ToList();
            var comparisonValues = comparisons.Select(c => (c.Name, Value: (StringComparison)c.GetValue(null)!)).ToList();
 
            // Check that all comparer values match the corresponding comparison values.
            foreach (var (comparer, comparison) in comparerValues.Zip(comparisonValues))
            {
                Assert.Equal(comparer.Name, comparison.Name, StringComparer.Ordinal);
 
                var comparerKind = GetComparerKind(comparer.Value);
                var comparisonKind = GetComparisonKind(comparison.Value);
 
                if (!string.Equals(comparerKind, comparisonKind, StringComparison.Ordinal))
                {
                    Assert.Fail($"""
                    Mismatched comparisons:
                    - {nameof(StringComparers)}.{comparer.Name} = {comparerKind}
                    - {nameof(StringComparisons)}.{comparer.Name} = {comparisonKind}
                    """);
                }
            }
 
            return;
 
            static string GetComparerKind(StringComparer comparer)
            {
                foreach (var (c, name) in Comparers())
                {
                    if (Equals(c, comparer))
                    {
                        return name;
                    }
                }
 
                Assert.Fail("Unknown comparer: " + comparer);
                return null!; // Unreachable
 
                static IEnumerable<(StringComparer, string)> Comparers()
                {
                    yield return (StringComparer.Ordinal, nameof(StringComparer.Ordinal));
                    yield return (StringComparer.OrdinalIgnoreCase, nameof(StringComparer.OrdinalIgnoreCase));
                    yield return (StringComparer.CurrentCulture, nameof(StringComparer.CurrentCulture));
                    yield return (StringComparer.CurrentCultureIgnoreCase, nameof(StringComparer.CurrentCultureIgnoreCase));
                    yield return (StringComparer.InvariantCulture, nameof(StringComparer.InvariantCulture));
                    yield return (StringComparer.InvariantCultureIgnoreCase, nameof(StringComparer.InvariantCultureIgnoreCase));
                }
            }
 
            static string GetComparisonKind(StringComparison comparison)
            {
                foreach (var (c, name) in Comparisons())
                {
                    if (c == comparison)
                    {
                        return name;
                    }
                }
 
                Assert.Fail("Unknown comparison: " + comparison);
                return null!; // Unreachable
 
                static IEnumerable<(StringComparison, string)> Comparisons()
                {
                    yield return (StringComparison.Ordinal, nameof(StringComparison.Ordinal));
                    yield return (StringComparison.OrdinalIgnoreCase, nameof(StringComparison.OrdinalIgnoreCase));
                    yield return (StringComparison.CurrentCulture, nameof(StringComparison.CurrentCulture));
                    yield return (StringComparison.CurrentCultureIgnoreCase, nameof(StringComparison.CurrentCultureIgnoreCase));
                    yield return (StringComparison.InvariantCulture, nameof(StringComparison.InvariantCulture));
                    yield return (StringComparison.InvariantCultureIgnoreCase, nameof(StringComparison.InvariantCultureIgnoreCase));
                }
            }
        }
    }
}