From af952dcb1bbe1a5e603745c21f4a4b97bcfb0829 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Tue, 13 Aug 2024 11:40:57 -0500 Subject: [PATCH] Implement a cache for ReferenceAssemblies instances Avoids the need to manually keep track of identical instances across test suites. --- .../PackageIdentity.cs | 28 ++- .../PublicAPI.Unshipped.txt | 6 + .../ReferenceAssemblies.cs | 220 +++++++++++++++++- 3 files changed, 246 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PackageIdentity.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PackageIdentity.cs index 5a82223b..52ab88b0 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PackageIdentity.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PackageIdentity.cs @@ -5,13 +5,17 @@ using System; using NuGet.Versioning; +#if !NETCOREAPP +using System.Collections.Generic; +#endif + namespace Microsoft.CodeAnalysis.Testing { /// /// Represents the core identity of a NuGet package. /// /// - public sealed class PackageIdentity + public sealed class PackageIdentity : IEquatable { /// /// Initializes a new instance of the class with the specified name and version. @@ -41,6 +45,28 @@ namespace Microsoft.CodeAnalysis.Testing /// public string Version { get; } + public override int GetHashCode() + { +#if NETCOREAPP + return HashCode.Combine(Id, Version); +#else + var hashCode = -612338121; + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Id); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Version); + return hashCode; +#endif + } + + public override bool Equals(object? obj) + => Equals(obj as PackageIdentity); + + public bool Equals(PackageIdentity? other) + { + return other is not null + && Id == other.Id + && Version == other.Version; + } + internal NuGet.Packaging.Core.PackageIdentity ToNuGetIdentity() { return new NuGet.Packaging.Core.PackageIdentity(Id, NuGetVersion.Parse(Version)); diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt index 5748c37c..ae5efba2 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/PublicAPI.Unshipped.txt @@ -144,6 +144,7 @@ Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.Sources.get -> System Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.WithAdditionalDiagnostics(System.Collections.Immutable.ImmutableArray additionalDiagnostics) -> Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState.WithSources(System.Collections.Immutable.ImmutableArray<(string filename, Microsoft.CodeAnalysis.Text.SourceText content)> sources) -> Microsoft.CodeAnalysis.Testing.Model.EvaluatedProjectState Microsoft.CodeAnalysis.Testing.PackageIdentity +Microsoft.CodeAnalysis.Testing.PackageIdentity.Equals(Microsoft.CodeAnalysis.Testing.PackageIdentity other) -> bool Microsoft.CodeAnalysis.Testing.PackageIdentity.Id.get -> string Microsoft.CodeAnalysis.Testing.PackageIdentity.PackageIdentity(string id, string version) -> void Microsoft.CodeAnalysis.Testing.PackageIdentity.Version.get -> string @@ -176,6 +177,7 @@ Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AddLanguageSpecificAssemblies Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AddPackages(System.Collections.Immutable.ImmutableArray packages) -> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Assemblies.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.AssemblyIdentityComparer.get -> Microsoft.CodeAnalysis.AssemblyIdentityComparer +Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Equals(Microsoft.CodeAnalysis.Testing.ReferenceAssemblies other) -> bool Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.FacadeAssemblies.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.LanguageSpecificAssemblies.get -> System.Collections.Immutable.ImmutableDictionary> Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Net @@ -246,6 +248,10 @@ abstract Microsoft.CodeAnalysis.Testing.CodeActionTest.SyntaxKindType override Microsoft.CodeAnalysis.Testing.DiagnosticResult.ToString() -> string override Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext context) -> void override Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray +override Microsoft.CodeAnalysis.Testing.PackageIdentity.Equals(object obj) -> bool +override Microsoft.CodeAnalysis.Testing.PackageIdentity.GetHashCode() -> int +override Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.Equals(object obj) -> bool +override Microsoft.CodeAnalysis.Testing.ReferenceAssemblies.GetHashCode() -> int static Microsoft.CodeAnalysis.Testing.AnalyzerTest.Verify.get -> TVerifier static Microsoft.CodeAnalysis.Testing.AnalyzerVerifier.Diagnostic() -> Microsoft.CodeAnalysis.Testing.DiagnosticResult static Microsoft.CodeAnalysis.Testing.AnalyzerVerifier.Diagnostic(Microsoft.CodeAnalysis.DiagnosticDescriptor descriptor) -> Microsoft.CodeAnalysis.Testing.DiagnosticResult diff --git a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs index c842bc51..39b0e0ba 100644 --- a/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs +++ b/src/Microsoft.CodeAnalysis.Testing/Microsoft.CodeAnalysis.Analyzer.Testing/ReferenceAssemblies.cs @@ -25,7 +25,7 @@ using NuGet.Packaging.Signing; namespace Microsoft.CodeAnalysis.Testing { - public sealed partial class ReferenceAssemblies + public sealed partial class ReferenceAssemblies : IEquatable { private const string ReferenceAssembliesPackageVersion = "1.0.2"; @@ -37,6 +37,8 @@ namespace Microsoft.CodeAnalysis.Testing private static ImmutableHashSet s_emptyPackages = ImmutableHashSet.Create(PackageIdentityComparer.Default); + private static ImmutableHashSet s_knownAssemblies = ImmutableHashSet.Empty; + private readonly Dictionary> _references = new(); @@ -123,14 +125,83 @@ namespace Microsoft.CodeAnalysis.Testing public string? NuGetConfigFilePath { get; } + private static ReferenceAssemblies GetOrAddReferenceAssemblies(ReferenceAssemblies value) + { + if (s_knownAssemblies.TryGetValue(value, out var existingValue)) + { + return existingValue; + } + + if (ImmutableInterlocked.Update( + ref s_knownAssemblies, + static (knownAssemblies, value) => knownAssemblies.Add(value), + value)) + { + return value; + } + + if (!s_knownAssemblies.TryGetValue(value, out existingValue)) + { + throw new InvalidOperationException(); + } + + return existingValue; + } + + public override int GetHashCode() + { +#if NETCOREAPP + var hash = default(HashCode); + hash.Add(TargetFramework); + hash.Add(AssemblyIdentityComparer); + hash.Add(ReferenceAssemblyPackage); + hash.Add(ReferenceAssemblyPath); + hash.Add(Assemblies, ImmutableArrayEqualityComparer.Instance); + hash.Add(FacadeAssemblies, ImmutableArrayEqualityComparer.Instance); + hash.Add(LanguageSpecificAssemblies, ImmutableDictionaryWithImmutableArrayValuesEqualityComparer.Instance); + hash.Add(Packages, ImmutableArrayEqualityComparer.Instance); + hash.Add(NuGetConfigFilePath); + return hash.ToHashCode(); +#else + var hashCode = -450793227; + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(TargetFramework); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(AssemblyIdentityComparer); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ReferenceAssemblyPackage); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ReferenceAssemblyPath); + hashCode = (hashCode * -1521134295) + ImmutableArrayEqualityComparer.Instance.GetHashCode(Assemblies); + hashCode = (hashCode * -1521134295) + ImmutableArrayEqualityComparer.Instance.GetHashCode(FacadeAssemblies); + hashCode = (hashCode * -1521134295) + ImmutableDictionaryWithImmutableArrayValuesEqualityComparer.Instance.GetHashCode(LanguageSpecificAssemblies); + hashCode = (hashCode * -1521134295) + ImmutableArrayEqualityComparer.Instance.GetHashCode(Packages); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(NuGetConfigFilePath); + return hashCode; +#endif + } + + public override bool Equals(object? obj) + => Equals(obj as ReferenceAssemblies); + + public bool Equals(ReferenceAssemblies? other) + { + return other is not null + && TargetFramework == other.TargetFramework + && EqualityComparer.Default.Equals(AssemblyIdentityComparer, other.AssemblyIdentityComparer) + && EqualityComparer.Default.Equals(ReferenceAssemblyPackage, other.ReferenceAssemblyPackage) + && ReferenceAssemblyPath == other.ReferenceAssemblyPath + && ImmutableArrayEqualityComparer.Instance.Equals(Assemblies, other.Assemblies) + && ImmutableArrayEqualityComparer.Instance.Equals(FacadeAssemblies, other.FacadeAssemblies) + && ImmutableDictionaryWithImmutableArrayValuesEqualityComparer.Instance.Equals(LanguageSpecificAssemblies, other.LanguageSpecificAssemblies) + && ImmutableArrayEqualityComparer.Instance.Equals(Packages, other.Packages) + && NuGetConfigFilePath == other.NuGetConfigFilePath; + } + public ReferenceAssemblies WithAssemblyIdentityComparer(AssemblyIdentityComparer assemblyIdentityComparer) - => new(TargetFramework, assemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath); + => GetOrAddReferenceAssemblies(new(TargetFramework, assemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath)); public ReferenceAssemblies WithAssemblies(ImmutableArray assemblies) - => new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath); + => GetOrAddReferenceAssemblies(new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath)); public ReferenceAssemblies WithFacadeAssemblies(ImmutableArray facadeAssemblies) - => new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, facadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath); + => GetOrAddReferenceAssemblies(new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, facadeAssemblies, LanguageSpecificAssemblies, Packages, NuGetConfigFilePath)); public ReferenceAssemblies AddAssemblies(ImmutableArray assemblies) => WithAssemblies(Assemblies.AddRange(assemblies)); @@ -139,7 +210,7 @@ namespace Microsoft.CodeAnalysis.Testing => WithFacadeAssemblies(FacadeAssemblies.AddRange(facadeAssemblies)); public ReferenceAssemblies WithLanguageSpecificAssemblies(ImmutableDictionary> languageSpecificAssemblies) - => new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, languageSpecificAssemblies, Packages, NuGetConfigFilePath); + => GetOrAddReferenceAssemblies(new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, languageSpecificAssemblies, Packages, NuGetConfigFilePath)); public ReferenceAssemblies WithLanguageSpecificAssemblies(string language, ImmutableArray assemblies) => WithLanguageSpecificAssemblies(LanguageSpecificAssemblies.SetItem(language, assemblies)); @@ -155,13 +226,13 @@ namespace Microsoft.CodeAnalysis.Testing } public ReferenceAssemblies WithPackages(ImmutableArray packages) - => new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, packages, NuGetConfigFilePath); + => GetOrAddReferenceAssemblies(new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, packages, NuGetConfigFilePath)); public ReferenceAssemblies AddPackages(ImmutableArray packages) => WithPackages(Packages.AddRange(packages)); public ReferenceAssemblies WithNuGetConfigFilePath(string nugetConfigFilePath) - => new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, nugetConfigFilePath); + => GetOrAddReferenceAssemblies(new(TargetFramework, AssemblyIdentityComparer, ReferenceAssemblyPackage, ReferenceAssemblyPath, Assemblies, FacadeAssemblies, LanguageSpecificAssemblies, Packages, nugetConfigFilePath)); public async Task> ResolveAsync(string? language, CancellationToken cancellationToken) { @@ -1353,5 +1424,140 @@ namespace Microsoft.CodeAnalysis.Testing return framework.IsPackageBased; } } + + private sealed class ImmutableArrayEqualityComparer : IEqualityComparer> + { + public static readonly ImmutableArrayEqualityComparer Instance = new(); + + private ImmutableArrayEqualityComparer() + { + } + + public bool Equals(ImmutableArray x, ImmutableArray y) + { + if (x.IsDefault) + { + return y.IsDefault; + } + else if (y.IsDefault) + { + return false; + } + + if (x.Length != y.Length) + { + return false; + } + + for (var i = 0; i < x.Length; i++) + { + if (!EqualityComparer.Default.Equals(x[i], y[i])) + { + return false; + } + } + + return true; + } + + public int GetHashCode(ImmutableArray obj) + { + if (obj.IsDefault) + { + return 0; + } + +#if NETCOREAPP + var hash = default(HashCode); + foreach (var item in obj) + { + hash.Add(item); + } + + return hash.ToHashCode(); +#else + var hashCode = -450793227; + foreach (var item in obj) + { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(item); + } + + return hashCode; +#endif + } + } + + private sealed class ImmutableDictionaryWithImmutableArrayValuesEqualityComparer : IEqualityComparer>?> + { + public static readonly ImmutableDictionaryWithImmutableArrayValuesEqualityComparer Instance = new(); + + private ImmutableDictionaryWithImmutableArrayValuesEqualityComparer() + { + } + + public bool Equals(ImmutableDictionary>? x, ImmutableDictionary>? y) + { + if (x is null) + { + return y is null; + } + else if (y is null) + { + return false; + } + + if (x.Count != y.Count) + { + return false; + } + + foreach (var (key, valueX) in x) + { + // Use a separate lookup in 'y' since ImmutableDictionary<,> can reorder pairs where the key has the + // same hash code. + if (!y.TryGetValue(key, out var valueY)) + { + return false; + } + + if (!ImmutableArrayEqualityComparer.Instance.Equals(valueX, valueY)) + { + return false; + } + } + + return true; + } + + public int GetHashCode(ImmutableDictionary>? obj) + { + if (obj is null) + { + return 0; + } + +#if NETCOREAPP + var hash = default(HashCode); + foreach (var (key, _) in obj) + { + // Intentionally ignore values since ImmutableDictionary<,> can reorder pairs where the key has the + // same hash code. + hash.Add(key); + } + + return hash.ToHashCode(); +#else + var hashCode = -450793227; + foreach (var (key, _) in obj) + { + // Intentionally ignore values since ImmutableDictionary<,> can reorder pairs where the key has the + // same hash code. + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(key); + } + + return hashCode; +#endif + } + } } }