/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/CoreTestUtilities/MEF/TestComposition.cs
235 строк
9 KB
Tomáš Matoušek
Publish Microsoft.CodeAnalysis.Workspaces.Test.Utilities package to be shared with other repositories (#80377)
20 сен 2025, 00:09
Не верифицирован
20 сен 2025, 00:09
bbbfa51
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Text; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.Composition; using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Test.Utilities; /// <summary> /// Represents a MEF composition used for testing. /// </summary> public sealed class TestComposition { public static readonly TestComposition Empty = new([], [], []); public static readonly TestComposition Default = Empty.AddAssemblies(MefHostServices.DefaultAssemblies); private static readonly Dictionary<CacheKey, IExportProviderFactory> s_factoryCache = []; private readonly struct CacheKey : IEquatable<CacheKey> { private readonly ImmutableArray<Assembly> _assemblies; private readonly ImmutableArray<Type> _parts; private readonly ImmutableArray<Type> _excludedPartTypes; public CacheKey(ImmutableHashSet<Assembly> assemblies, ImmutableHashSet<Type> parts, ImmutableHashSet<Type> excludedPartTypes) { _assemblies = [.. assemblies.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; _parts = [.. parts.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; _excludedPartTypes = [.. excludedPartTypes.OrderBy((a, b) => string.CompareOrdinal(a.FullName, b.FullName))]; } public override bool Equals(object? obj) => obj is CacheKey key && Equals(key); public bool Equals(CacheKey other) => _parts.SequenceEqual(other._parts) && _excludedPartTypes.SequenceEqual(other._excludedPartTypes) && _assemblies.SequenceEqual(other._assemblies); public override int GetHashCode() => Hash.Combine(Hash.Combine(Hash.CombineValues(_assemblies), Hash.CombineValues(_parts)), Hash.CombineValues(_excludedPartTypes)); public static bool operator ==(CacheKey left, CacheKey right) => left.Equals(right); public static bool operator !=(CacheKey left, CacheKey right) => !(left == right); } /// <summary> /// Assemblies to include in the composition. /// </summary> public readonly ImmutableHashSet<Assembly> Assemblies; /// <summary> /// Types to exclude from the composition. /// All subtypes of types specified in <see cref="ExcludedPartTypes"/> and defined in <see cref="Assemblies"/> are excluded before <see cref="Parts"/> are added. /// </summary> public readonly ImmutableHashSet<Type> ExcludedPartTypes; /// <summary> /// Additional part types to add to the composition. /// </summary> public readonly ImmutableHashSet<Type> Parts; private readonly Lazy<IExportProviderFactory> _exportProviderFactory; private TestComposition(ImmutableHashSet<Assembly> assemblies, ImmutableHashSet<Type> parts, ImmutableHashSet<Type> excludedPartTypes) { Assemblies = assemblies; Parts = parts; ExcludedPartTypes = excludedPartTypes; _exportProviderFactory = new Lazy<IExportProviderFactory>(GetOrCreateFactory); } /// <summary> /// Returns a new instance of <see cref="HostServices"/> for the composition. This will either be a MEF composition or VS MEF composition host, /// depending on what layer the composition is for. Editor Features and VS layers use VS MEF composition while anything else uses System.Composition. /// </summary> public HostServices GetHostServices() => GetHostServices(out _); public HostServices GetHostServices(out ExportProvider exportProvider) => VisualStudioMefHostServices.Create(exportProvider = ExportProviderFactory.CreateExportProvider()); /// <summary> /// VS MEF <see cref="ExportProvider"/>. /// </summary> public IExportProviderFactory ExportProviderFactory => _exportProviderFactory.Value; private IExportProviderFactory GetOrCreateFactory() { var key = new CacheKey(Assemblies, Parts, ExcludedPartTypes); lock (s_factoryCache) { if (s_factoryCache.TryGetValue(key, out var existing)) { return existing; } } var newFactory = ExportProviderCache.CreateExportProviderFactory(GetCatalog(), IsRemote); lock (s_factoryCache) { if (s_factoryCache.TryGetValue(key, out var existing)) { return existing; } s_factoryCache.Add(key, newFactory); } return newFactory; } public bool IsRemote => Assemblies.Contains(typeof(Remote.BrokeredServiceBase).Assembly); private ComposableCatalog GetCatalog() { // Compositions should not be realized if they contain the same part in both the explicit include list and // the explicit exclude list. var configurationOverlap = Parts.Intersect(ExcludedPartTypes); Assert.Empty(configurationOverlap); return ExportProviderCache.CreateAssemblyCatalog(Assemblies, ExportProviderCache.CreateResolver()).WithoutPartsOfTypes(ExcludedPartTypes).WithParts(Parts); } public CompositionConfiguration GetCompositionConfiguration() => CompositionConfiguration.Create(GetCatalog()); public TestComposition Add(TestComposition composition) => AddAssemblies(composition.Assemblies).AddParts(composition.Parts).AddExcludedPartTypes(composition.ExcludedPartTypes); public TestComposition AddAssemblies(params Assembly[]? assemblies) => AddAssemblies((IEnumerable<Assembly>?)assemblies); public TestComposition AddAssemblies(IEnumerable<Assembly>? assemblies) => WithAssemblies(Assemblies.Union(assemblies ?? [])); public TestComposition AddParts(IEnumerable<Type>? types) => WithParts(Parts.Union(types ?? [])); public TestComposition AddParts(params Type[]? types) => AddParts((IEnumerable<Type>?)types); public TestComposition AddExcludedPartTypes(IEnumerable<Type>? types) => WithExcludedPartTypes(ExcludedPartTypes.Union(types ?? [])); public TestComposition AddExcludedPartTypes(params Type[]? types) => AddExcludedPartTypes((IEnumerable<Type>?)types); public TestComposition Remove(TestComposition composition) => RemoveAssemblies(composition.Assemblies).RemoveParts(composition.Parts).RemoveExcludedPartTypes(composition.ExcludedPartTypes); public TestComposition RemoveAssemblies(params Assembly[]? assemblies) => RemoveAssemblies((IEnumerable<Assembly>?)assemblies); public TestComposition RemoveAssemblies(IEnumerable<Assembly>? assemblies) => WithAssemblies(Assemblies.Except(assemblies ?? [])); public TestComposition RemoveParts(IEnumerable<Type>? types) => WithParts(Parts.Except(types ?? [])); public TestComposition RemoveParts(params Type[]? types) => RemoveParts((IEnumerable<Type>?)types); public TestComposition RemoveExcludedPartTypes(IEnumerable<Type>? types) => WithExcludedPartTypes(ExcludedPartTypes.Except(types ?? [])); public TestComposition RemoveExcludedPartTypes(params Type[]? types) => RemoveExcludedPartTypes((IEnumerable<Type>?)types); public TestComposition WithAssemblies(ImmutableHashSet<Assembly> assemblies) { if (assemblies == Assemblies) { return this; } var testAssembly = assemblies.FirstOrDefault(IsTestAssembly); Contract.ThrowIfFalse(testAssembly == null, $"Test assemblies are not allowed in test composition: {testAssembly}. Specify explicit test parts instead."); return new TestComposition(assemblies, Parts, ExcludedPartTypes); static bool IsTestAssembly(Assembly assembly) { var name = assembly.GetName().Name!; return name.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".UnitTests", StringComparison.OrdinalIgnoreCase) || name.IndexOf("Test.Utilities", StringComparison.OrdinalIgnoreCase) >= 0; } } public TestComposition WithParts(ImmutableHashSet<Type> parts) => (parts == Parts) ? this : new TestComposition(Assemblies, parts, ExcludedPartTypes); public TestComposition WithExcludedPartTypes(ImmutableHashSet<Type> excludedPartTypes) => (excludedPartTypes == ExcludedPartTypes) ? this : new TestComposition(Assemblies, Parts, excludedPartTypes); /// <summary> /// Use for VS MEF composition troubleshooting. /// </summary> /// <returns>All composition error messages.</returns> internal string GetCompositionErrorLog() { var configuration = CompositionConfiguration.Create(GetCatalog()); var sb = new StringBuilder(); foreach (var errorGroup in configuration.CompositionErrors) { foreach (var error in errorGroup) { sb.Append(error.Message); sb.AppendLine(); } } return sb.ToString(); } }