File: AnalyzerAssemblyLoaderTests.cs
Web Access
Project: src\src\Compilers\Core\CodeAnalysisTest\Microsoft.CodeAnalysis.UnitTests.csproj (Microsoft.CodeAnalysis.UnitTests)
// 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.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
using Xunit.Abstractions;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.Text;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
 
#if NET
using Roslyn.Test.Utilities.CoreClr;
using System.Runtime.Loader;
#else
using Roslyn.Test.Utilities.Desktop;
#endif
 
namespace Microsoft.CodeAnalysis.UnitTests
{
    public enum AnalyzerTestKind
    {
        LoadDirect,
        ShadowLoad,
#if NET
        LoadStream,
#endif
    }
 
    /// <summary>
    /// Contains the bulk of our analyzer / generator loading tests.
    /// </summary>
    /// <remarks>
    /// These tests often have quirks associated with fundamental limitation issues around either 
    /// .NET Framework, .NET Core or our own legacy decisions. Rather than repeating a specific rationale
    /// at all the tests that hit them, the common are outlined below and referenced with the following 
    /// comment style within the test.
    ///
    ///    // See limitation 1
    ///
    /// This allows us to provide central description of the limitations that can be easily referenced in the impacted
    /// tests. For all the descriptions below assume that A.dll depends on B.dll. 
    ///
    /// Limitation 1: .NET Framework probing path.
    ///
    /// The .NET Framework assembly loader will only call AppDomain.AssemblyResolve when it cannot satisfy a load
    /// request. One of the places the assembly loader will always consider when looking for dependencies of A.dll
    /// is the directory that A.dll was loading from (it's added to the probing path). That means if B.dll is in the
    /// same directory then the runtime will silently load it without a way for us to intervene.
    ///
    /// Note: this only applies when A.dll is in the Load or LoadFrom context which is always true for these tests
    /// 
    /// Limitation 2: Dependency is already loaded.
    ///
    /// Similar to Limitation 1 is when the dependency, B.dll, is already present in the Load or LoadFrom context 
    /// then that will be used. The runtime will not attempt to load a better version (an exact match for example).
    ///
    /// Limitation 3: Shadow copy breaks up directories
    ///
    /// The shadow copy loader strategy is to put every analyzer dependency into a different shadow directory. That 
    /// means if A.dll and B.dll are in the same directory for a normal load, they are in different directories 
    /// during a shadow copy load.
    /// 
    /// This causes significant issues in .NET Framework because we don't have the ability to know where a load
    /// is coming from. The AppDomain.AssemblyResolve event just requests "B, Version=1.0.0.0" but gives no context 
    /// as to where the request is coming from. That means we often end up loading a different copy of B.dll in a
    /// shadow load scenario. 
    /// 
    /// Long term this is something that needs to be addressed. Tracked by https://github.com/dotnet/roslyn/issues/66532
    ///
    /// </remarks>
    [Collection(AssemblyLoadTestFixtureCollection.Name)]
    public sealed class AnalyzerAssemblyLoaderTests : TestBase
    {
        public ITestOutputHelper TestOutputHelper { get; }
 
        public AssemblyLoadTestFixture TestFixture { get; }
 
        public AnalyzerAssemblyLoaderTests(ITestOutputHelper testOutputHelper, AssemblyLoadTestFixture testFixture)
        {
            TestOutputHelper = testOutputHelper;
            TestFixture = testFixture;
        }
 
#if NET

        private void Run(
            AnalyzerTestKind kind,
            Action<AnalyzerAssemblyLoader, AssemblyLoadTestFixture> testAction,
            IAnalyzerAssemblyResolver[]? externalResolvers = null,
            [CallerMemberName] string? memberName = null) =>
            Run(
                kind,
                static (_, _) => { },
                testAction,
                externalResolvers,
                memberName);
 
        private void Run(
            AnalyzerTestKind kind,
            object state,
            Action<AnalyzerAssemblyLoader, AssemblyLoadTestFixture, object> testAction,
            IAnalyzerAssemblyResolver[]? externalResolvers = null,
            [CallerMemberName] string? memberName = null) =>
            Run(
                kind,
                state,
                static (_, _) => { },
                testAction.Method,
                externalResolvers,
                memberName);
 
        private void Run(
            AnalyzerTestKind kind,
            Action<AssemblyLoadContext, AssemblyLoadTestFixture> prepLoadContextAction,
            Action<AnalyzerAssemblyLoader, AssemblyLoadTestFixture> testAction,
            IAnalyzerAssemblyResolver[]? externalResolvers = null,
            [CallerMemberName] string? memberName = null) =>
            Run(
                kind,
                state: null,
                prepLoadContextAction,
                testAction.Method,
                externalResolvers,
                memberName);
 
        private void Run(
            AnalyzerTestKind kind,
            object? state,
            Action<AssemblyLoadContext, AssemblyLoadTestFixture> prepLoadContextAction,
            MethodInfo method,
            IAnalyzerAssemblyResolver[]? externalResolvers,
            string? memberName)
        {
            var alc = new AssemblyLoadContext($"Test {memberName}", isCollectible: true);
            try
            {
                prepLoadContextAction(alc, TestFixture);
                var util = new InvokeUtil();
                util.Exec(TestOutputHelper, alc, TestFixture, kind, method.DeclaringType!.FullName!, method.Name, externalResolvers ?? [], state);
            }
            finally
            {
                alc.Unload();
            }
        }
 
#else
 
        private void Run(
            AnalyzerTestKind kind,
            Action<AnalyzerAssemblyLoader, AssemblyLoadTestFixture> testAction,
            IAnalyzerAssemblyResolver[]? externalResolvers = null,
            [CallerMemberName] string? memberName = null) =>
            Run(
                kind,
                state: null,
                testAction.Method,
                externalResolvers,
                memberName);
 
        private void Run(
            AnalyzerTestKind kind,
            object state,
            Action<AnalyzerAssemblyLoader, AssemblyLoadTestFixture, object> testAction,
            IAnalyzerAssemblyResolver[]? externalResolvers = null,
            [CallerMemberName] string? memberName = null) =>
            Run(kind,
                state,
                testAction.Method,
                externalResolvers,
                memberName);
 
        private void Run(
            AnalyzerTestKind kind,
            object state,
            MethodInfo method,
            IAnalyzerAssemblyResolver[]? externalResolvers,
            string? memberName)
        {
            AppDomain? appDomain = null;
            try
            {
                appDomain = AppDomainUtils.Create($"Test {memberName}");
                var testOutputHelper = new AppDomainTestOutputHelper(TestOutputHelper);
                var type = typeof(InvokeUtil);
                var util = (InvokeUtil)appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
                util.Exec(testOutputHelper, TestFixture, kind, method.DeclaringType.FullName, method.Name, externalResolvers ?? [], state);
            }
            finally
            {
                AppDomain.Unload(appDomain);
            }
        }
 
#endif
 
        /// <summary>
        /// This is called from our newly created AppDomain or AssemblyLoadContext and needs to get 
        /// us back to the actual test code to execute. The intent is to invoke the lambda / static
        /// local func where the code exists.
        /// </summary>
        internal static void InvokeTestCode(AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture fixture, string typeName, string methodName, object? state)
        {
            var type = typeof(AnalyzerAssemblyLoaderTests).Assembly.GetType(typeName, throwOnError: false)!;
            var member = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)!;
 
            // A static lambda will still be an instance method so we need to create the closure
            // here.
            var obj = member.IsStatic
                ? null
                : type.Assembly.CreateInstance(typeName);
 
            object[] args = state is null
                ? [loader, fixture]
                : [loader, fixture, state];
            member.Invoke(obj, args);
        }
 
        [Theory]
        [CombinatorialData]
        [WorkItem(32226, "https://github.com/dotnet/roslyn/issues/32226")]
        public void LoadWithDependency(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var analyzerDependencyFile = testFixture.AnalyzerDependency;
                var analyzerMainFile = testFixture.AnalyzerWithDependency;
                loader.AddDependencyLocation(analyzerDependencyFile);
 
                var analyzerMainReference = new AnalyzerFileReference(analyzerMainFile, loader);
                analyzerMainReference.AnalyzerLoadFailed += (_, e) => AssertEx.Fail(e.Exception!.Message);
                var analyzerDependencyReference = new AnalyzerFileReference(analyzerDependencyFile, loader);
                analyzerDependencyReference.AnalyzerLoadFailed += (_, e) => AssertEx.Fail(e.Exception!.Message);
 
                var analyzers = analyzerMainReference.GetAnalyzersForAllLanguages();
                Assert.Equal(1, analyzers.Length);
                Assert.Equal("TestAnalyzer", analyzers[0].ToString());
 
                Assert.Equal(0, analyzerDependencyReference.GetAnalyzersForAllLanguages().Length);
 
                Assert.NotNull(analyzerDependencyReference.GetAssembly());
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AddDependencyLocationThrowsOnNull(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                Assert.Throws<ArgumentNullException>("fullPath", () => loader.AddDependencyLocation(null!));
                Assert.Throws<ArgumentException>("fullPath", () => loader.AddDependencyLocation("a"));
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void ThrowsForMissingFile(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".dll");
                Assert.ThrowsAny<Exception>(() => loader.LoadFromPath(path));
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void BasicLoad(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                loader.AddDependencyLocation(testFixture.Alpha);
                Assembly alpha = loader.LoadFromPath(testFixture.Alpha);
 
                Assert.NotNull(alpha);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_Multiple(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Alpha);
                loader.AddDependencyLocation(testFixture.Beta);
                loader.AddDependencyLocation(testFixture.Gamma);
                loader.AddDependencyLocation(testFixture.Delta1);
 
                Assembly alpha = loader.LoadFromPath(testFixture.Alpha);
 
                var a = alpha.CreateInstance("Alpha.A")!;
                a.GetType().GetMethod("Write")!.Invoke(a, new object[] { sb, "Test A" });
 
                Assembly beta = loader.LoadFromPath(testFixture.Beta);
 
                var b = beta.CreateInstance("Beta.B")!;
                b.GetType().GetMethod("Write")!.Invoke(b, new object[] { sb, "Test B" });
 
                var expected = @"Delta: Gamma: Alpha: Test A
Delta: Gamma: Beta: Test B
";
 
                var actual = sb.ToString();
 
                Assert.Equal(expected, actual);
            });
        }
 
        /// <summary>
        /// The loaders should not actually look at the contents of the disk until a <see cref="AnalyzerAssemblyLoader.LoadFromPath(string)"/>
        /// call has occurred. This is historical behavior that doesn't have a clear reason for existing. There
        /// is strong suspicion it's to delay loading of analyzers until absolutely necessary. As such we're
        /// enshrining the behavior here so it is not _accidentally_ changed.
        /// </summary>
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_OverwriteBeforeLoad(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                var delta1Copy = tempDir.CreateDirectory("a").CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                loader.AddDependencyLocation(delta1Copy);
                File.Copy(testFixture.Delta2, delta1Copy, overwrite: true);
                var assembly = loader.LoadFromPath(delta1Copy);
 
                var name = AssemblyName.GetAssemblyName(testFixture.Delta2);
                Assert.Equal(name.FullName, assembly.GetName().FullName);
 
                VerifyDependencyAssemblies(
                    loader,
                    delta1Copy);
            });
        }
 
        [ConditionalTheory(typeof(WindowsOnly), Reason = "https://github.com/dotnet/roslyn/issues/66621")]
        [CombinatorialData]
        public void AssemblyLoading_AssemblyLocationNotAdded(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                loader.AddDependencyLocation(testFixture.Gamma);
                loader.AddDependencyLocation(testFixture.Delta1);
                Assert.Throws<InvalidOperationException>(() => loader.LoadFromPath(testFixture.Beta));
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_DependencyLocationNotAdded(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Gamma);
                loader.AddDependencyLocation(testFixture.Beta);
                Assembly beta = loader.LoadFromPath(testFixture.Beta);
 
                var b = beta.CreateInstance("Beta.B")!;
                var writeMethod = b.GetType().GetMethod("Write")!;
 
                if (ExecutionConditionUtil.IsCoreClr || loader is ShadowCopyAnalyzerAssemblyLoader)
                {
                    // We don't pass Alpha's path to AddDependencyLocation here, and therefore expect
                    // calling Beta.B.Write to fail because loader will prevent the load of Alpha
                    var exception = Assert.Throws<TargetInvocationException>(
                        () => writeMethod.Invoke(b, new object[] { sb, "Test B" }));
                    Assert.IsAssignableFrom<FileNotFoundException>(exception.InnerException);
 
                    var actual = sb.ToString();
                    Assert.Equal(@"", actual);
                }
                else
                {
                    // See limitation 1
                    writeMethod.Invoke(b, new object[] { sb, "Test B" });
                    var actual = sb.ToString();
                    Assert.Equal(@"Delta: Gamma: Beta: Test B
", actual);
                }
            });
        }
 
        private static void VerifyAssemblies(AnalyzerAssemblyLoader loader, IEnumerable<Assembly> assemblies, params (string simpleName, string version, string path)[] expected) =>
            VerifyAssemblies(loader, assemblies, expectedCopyCount: null, expected);
 
        private static void VerifyAssemblies(AnalyzerAssemblyLoader loader, IEnumerable<Assembly> assemblies, int? expectedCopyCount, params (string simpleName, string version, string path)[] expected)
        {
            Assert.Equal(
                expected
                    .Select(x => (x.simpleName, x.version, getExpectedLoadPath(x.path)))
                    .OrderBy(static x => x)
                    .ToArray(),
                assemblies.Select(assembly => (assembly.GetName().Name!, assembly.GetName().Version!.ToString(), assembly.Location))
                    .OrderBy(static x => x)
                    .ToArray());
 
            if (loader is ShadowCopyAnalyzerAssemblyLoader shadowLoader)
            {
                Assert.All(assemblies, x => x.Location.StartsWith(shadowLoader.BaseDirectory, StringComparison.Ordinal));
                Assert.Equal(expectedCopyCount ?? expected.Length, shadowLoader.CopyCount);
            }
 
            string getExpectedLoadPath(string path)
            {
#if NET
                if (loader is AnalyzerAssemblyLoader { AnalyzerLoadOption: AnalyzerLoadOption.LoadFromStream })
                {
                    return "";
                }
#endif
 
                if (path.EndsWith(".resources.dll", StringComparison.Ordinal))
                {
                    return getRealSatelliteLoadPath(path) ?? "";
                }
                return loader.GetRealAnalyzerLoadPath(path ?? "");
            }
 
            // When PreparePathToLoad is overridden this returns the most recent
            // real path for the given analyzer satellite assembly path
            string? getRealSatelliteLoadPath(string originalSatelliteFullPath)
            {
                // This is a satellite assembly, need to find the mapped path of the real assembly, then 
                // adjust that mapped path for the suffix of the satellite assembly
                //
                // Example of dll and it's corresponding satellite assembly
                //
                //  c:\some\path\en-GB\util.resources.dll
                //  c:\some\path\util.dll
                var assemblyFileName = Path.ChangeExtension(Path.GetFileNameWithoutExtension(originalSatelliteFullPath), ".dll");
 
                var assemblyDir = Path.GetDirectoryName(originalSatelliteFullPath)!;
                var cultureInfo = CultureInfo.GetCultureInfo(Path.GetFileName(assemblyDir));
                assemblyDir = Path.GetDirectoryName(assemblyDir)!;
 
                // Real assembly is located in the directory above this one
                var assemblyPath = Path.Combine(assemblyDir, assemblyFileName);
                return loader.GetRealSatelliteLoadPath(assemblyPath, cultureInfo);
            }
        }
 
        private static void VerifyAssemblies(AnalyzerAssemblyLoader loader, IEnumerable<Assembly> assemblies, int? copyCount, params string[] assemblyPaths)
        {
            var data = assemblyPaths
                .Select(x =>
                {
                    var name = AssemblyName.GetAssemblyName(x);
                    return (name.Name!, name.Version?.ToString() ?? "", x);
                })
                .ToArray();
            VerifyAssemblies(loader, assemblies, copyCount, data);
        }
 
        /// <summary>
        /// Verify the set of assemblies loaded as analyzer dependencies are the specified assembly paths
        /// </summary>
        private static void VerifyDependencyAssemblies(AnalyzerAssemblyLoader loader, params string[] assemblyPaths) =>
            VerifyDependencyAssemblies(loader, copyCount: null, assemblyPaths);
 
        private static void VerifyDependencyAssemblies(AnalyzerAssemblyLoader loader, int? copyCount, params string[] assemblyPaths)
        {
            IEnumerable<Assembly> loadedAssemblies;
 
#if NET
            var alcs = loader.GetDirectoryLoadContextsSnapshot();
            loadedAssemblies = alcs.SelectMany(x => x.Assemblies);
#else
 
            // The assemblies in the LoadFrom context are the assemblies loaded from 
            // analyzer dependencies.
            loadedAssemblies = AppDomain.CurrentDomain
                .GetAssemblies()
                .Where(x => isInLoadFromContext(loader, x));
 
            static bool isInLoadFromContext(AnalyzerAssemblyLoader loader, Assembly assembly)
            {
                var undidHook = false;
                try
                {
                    // Have to unhook resolve here otherwise calls to Load will hit the resolver and 
                    // we will end up bringing these assemblies into the Load context by handling them
                    // there.
                    undidHook = loader.EnsureResolvedUnhooked();
                    var name = assembly.FullName;
                    var other = AppDomain.CurrentDomain.Load(name);
                    return other is null || other != assembly;
                }
                catch
                {
                    return true;
                }
                finally
                {
                    if (undidHook)
                    {
                        loader.EnsureResolvedHooked();
                    }
                }
            }
 
#endif
            VerifyAssemblies(loader, loadedAssemblies, copyCount, assemblyPaths);
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_Simple(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Delta1);
                loader.AddDependencyLocation(testFixture.Gamma);
 
                Assembly gamma = loader.LoadFromPath(testFixture.Gamma);
                var b = gamma.CreateInstance("Gamma.G")!;
                var writeMethod = b.GetType().GetMethod("Write")!;
                writeMethod.Invoke(b, new object[] { sb, "Test G" });
 
                var actual = sb.ToString();
                Assert.Equal(@"Delta: Gamma: Test G
", actual);
 
                VerifyDependencyAssemblies(
                    loader,
                    testFixture.Delta1,
                    testFixture.Gamma);
            });
        }
 
        [ConditionalTheory(typeof(WindowsOnly), Reason = "https://github.com/dotnet/runtime/issues/81108")]
        [CombinatorialData]
        public void AssemblyLoading_DependencyInDifferentDirectory(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                StringBuilder sb = new StringBuilder();
 
                var deltaFile = tempDir.CreateDirectory("a").CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                var gammaFile = tempDir.CreateDirectory("b").CreateFile("Gamma.dll").CopyContentFrom(testFixture.Gamma).Path;
 
                loader.AddDependencyLocation(deltaFile);
                loader.AddDependencyLocation(gammaFile);
                Assembly gamma = loader.LoadFromPath(gammaFile);
 
                var b = gamma.CreateInstance("Gamma.G")!;
                var writeMethod = b.GetType().GetMethod("Write")!;
                writeMethod.Invoke(b, new object[] { sb, "Test G" });
 
                var actual = sb.ToString();
                Assert.Equal(@"Delta: Gamma: Test G
", actual);
 
                VerifyDependencyAssemblies(
                    loader,
                    deltaFile,
                    gammaFile);
            });
        }
 
#if NET472
        /// <summary>
        /// Verify that MS.CA.EA.RazorCompiler will be loaded from the compiler directory not the 
        /// analyzer directory.
        /// </summary>
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_RazorCompiler1(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
 
                var externalAccessRazorPath = typeof(Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.GeneratorExtensions).Assembly.Location;
                var alternatePath = tempDir.CreateDirectory("a").CreateFile("Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll").CopyContentFrom(externalAccessRazorPath).Path;
 
                loader.AddDependencyLocation(alternatePath);
                Assembly assembly = loader.LoadFromPath(alternatePath);
 
                Assert.Equal(externalAccessRazorPath, assembly.Location);
 
                // Even though EA.RazorCompiler is loaded from the compiler directory the shadow copy loader
                // still does a defensive copy.
                var copyCount = loader is ShadowCopyAnalyzerAssemblyLoader
                    ? 1
                    : (int?)null;
 
                VerifyDependencyAssemblies(
                    loader,
                    copyCount: copyCount,
                    []);
            });
        }
 
        /// <summary>
        /// Verify that MS.CA.EA.RazorCompiler will be loaded from the compiler directory not the 
        /// analyzer directory.
        /// </summary>
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_RazorCompiler2(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
 
                var externalAccessRazorPath = typeof(Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.GeneratorExtensions).Assembly.Location;
                var dir = tempDir.CreateDirectory("a");
                var alternatePath = dir.CreateFile("Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll").CopyContentFrom(externalAccessRazorPath).Path;
                var deltaFile = dir.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
 
                loader.AddDependencyLocation(alternatePath);
                loader.AddDependencyLocation(deltaFile);
                Assembly razorAssembly = loader.LoadFromPath(alternatePath);
                _ = loader.LoadFromPath(deltaFile);
 
                Assert.Equal(externalAccessRazorPath, razorAssembly.Location);
 
                // Even though EA.RazorCompiler is loaded from the compiler directory the shadow copy loader
                // still does a defensive copy.
                var copyCount = loader is ShadowCopyAnalyzerAssemblyLoader
                    ? 2
                    : (int?)null;
                VerifyDependencyAssemblies(
                    loader,
                    copyCount: copyCount,
                    assemblyPaths: [deltaFile]);
            });
        }
 
#endif
 
        /// <summary>
        /// Similar to <see cref="AssemblyLoading_DependencyInDifferentDirectory"/> except want to validate
        /// a dependency in the same directory is preferred over one in a different directory.
        /// </summary>
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_DependencyInDifferentDirectory2(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
 
                // It's important that we create these directories in a deterministic order so that 
                // our test has reliably output. Part of our resolution code will search the registered
                // paths in a sorted order.
                var deltaFile1 = tempDir.CreateDirectory("a").CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                var tempSubDir = tempDir.CreateDirectory("b");
                var gammaFile = tempSubDir.CreateFile("Gamma.dll").CopyContentFrom(testFixture.Gamma).Path;
                var deltaFile2 = tempSubDir.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
 
                loader.AddDependencyLocation(deltaFile1);
                loader.AddDependencyLocation(deltaFile2);
                loader.AddDependencyLocation(gammaFile);
                Assembly gamma = loader.LoadFromPath(gammaFile);
 
                StringBuilder sb = new StringBuilder();
                var b = gamma.CreateInstance("Gamma.G")!;
                var writeMethod = b.GetType().GetMethod("Write")!;
                writeMethod.Invoke(b, new object[] { sb, "Test G" });
 
                var actual = sb.ToString();
                Assert.Equal(@"Delta: Gamma: Test G
", actual);
 
                if (ExecutionConditionUtil.IsDesktop && loader is ShadowCopyAnalyzerAssemblyLoader)
                {
                    // See limitation 3
                    VerifyDependencyAssemblies(
                        loader,
                        deltaFile1,
                        gammaFile);
                }
                else
                {
                    VerifyDependencyAssemblies(
                        loader,
                        deltaFile2,
                        gammaFile);
 
                }
            });
        }
 
        /// <summary>
        /// This is very similar to <see cref="AssemblyLoading_DependencyInDifferentDirectory2"/> except 
        /// that we ensure the code does not prefer a dependency in the same directory if it's 
        /// unregistered
        /// </summary>
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_DependencyInDifferentDirectory3(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                StringBuilder sb = new StringBuilder();
 
                var deltaFile = tempDir.CreateDirectory("a").CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                var gammaFile = tempDir.CreateDirectory("b").CreateFile("Gamma.dll").CopyContentFrom(testFixture.Gamma).Path;
 
                loader.AddDependencyLocation(deltaFile);
                loader.AddDependencyLocation(gammaFile);
                Assembly gamma = loader.LoadFromPath(gammaFile);
 
                var b = gamma.CreateInstance("Gamma.G")!;
                var writeMethod = b.GetType().GetMethod("Write")!;
                writeMethod.Invoke(b, new object[] { sb, "Test G" });
 
                var actual = sb.ToString();
                Assert.Equal(@"Delta: Gamma: Test G
", actual);
 
                VerifyDependencyAssemblies(
                    loader,
                    deltaFile,
                    gammaFile);
            });
        }
 
        [ConditionalTheory(typeof(WindowsOnly), Reason = "https://github.com/dotnet/roslyn/issues/66626")]
        [CombinatorialData]
        [WorkItem(32226, "https://github.com/dotnet/roslyn/issues/32226")]
        public void AssemblyLoading_DependencyInDifferentDirectory4(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var analyzerDependencyFile = testFixture.AnalyzerDependency;
                var analyzerMainFile = testFixture.AnalyzerWithDependency;
 
                var analyzerMainReference = new AnalyzerFileReference(analyzerMainFile, loader);
                analyzerMainReference.AnalyzerLoadFailed += (_, e) => AssertEx.Fail(e.Exception!.Message);
                var analyzerDependencyReference = new AnalyzerFileReference(analyzerDependencyFile, loader);
                analyzerDependencyReference.AnalyzerLoadFailed += (_, e) => AssertEx.Fail(e.Exception!.Message);
 
                Assert.True(loader.IsAnalyzerDependencyPath(analyzerMainFile));
                Assert.True(loader.IsAnalyzerDependencyPath(analyzerDependencyFile));
 
                var analyzers = analyzerMainReference.GetAnalyzersForAllLanguages();
                Assert.Equal(1, analyzers.Length);
                Assert.Equal("TestAnalyzer", analyzers[0].ToString());
                Assert.Equal(0, analyzerDependencyReference.GetAnalyzersForAllLanguages().Length);
                Assert.NotNull(analyzerDependencyReference.GetAssembly());
 
                VerifyDependencyAssemblies(
                    loader,
                    testFixture.AnalyzerWithDependency,
                    testFixture.AnalyzerDependency);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Gamma);
                loader.AddDependencyLocation(testFixture.Delta1);
                loader.AddDependencyLocation(testFixture.Epsilon);
                loader.AddDependencyLocation(testFixture.Delta2);
 
                Assembly gamma = loader.LoadFromPath(testFixture.Gamma);
                var g = gamma.CreateInstance("Gamma.G")!;
                g.GetType().GetMethod("Write")!.Invoke(g, new object[] { sb, "Test G" });
 
                Assembly epsilon = loader.LoadFromPath(testFixture.Epsilon);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                e.GetType().GetMethod("Write")!.Invoke(e, new object[] { sb, "Test E" });
                var actual = sb.ToString();
 
#if NET
                var alcs = loader.GetDirectoryLoadContextsSnapshot();
                Assert.Equal(2, alcs.Length);
 
                VerifyAssemblies(
                    loader,
                    alcs[0].Assemblies,
                    expectedCopyCount: 4,
                    ("Delta", "1.0.0.0", testFixture.Delta1),
                    ("Gamma", "0.0.0.0", testFixture.Gamma)
                );
 
                VerifyAssemblies(
                    loader,
                    alcs[1].Assemblies,
                    expectedCopyCount: 4,
                    ("Delta", "2.0.0.0", testFixture.Delta2),
                    ("Epsilon", "0.0.0.0", testFixture.Epsilon));
 
                Assert.Equal(
    @"Delta: Gamma: Test G
Delta.2: Epsilon: Test E
",
                    actual);
#else
                Assert.Equal(
    @"Delta: Gamma: Test G
Delta: Epsilon: Test E
",
                    actual);
 
#endif
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_NoExactMatch(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Delta1);
                loader.AddDependencyLocation(testFixture.Epsilon);
                loader.AddDependencyLocation(testFixture.Delta3);
 
                Assembly epsilon = loader.LoadFromPath(testFixture.Epsilon);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                e.GetType().GetMethod("Write")!.Invoke(e, new object[] { sb, "Test E" });
 
                var actual = sb.ToString();
                if (ExecutionConditionUtil.IsCoreClr || loader is ShadowCopyAnalyzerAssemblyLoader)
                {
                    // In .NET Core we have _full_ control over assembly loading and can prevent implicit
                    // loads from probing paths. That means we can avoid implicitly loading the Delta v2 
                    // next to Epsilon
                    //
                    // Similarly in the shadow copy scenarios the assemblies are not side by side so the 
                    // load is controllable.
                    //
                    // There is an extra copy count here as both deltas are read from disk in order to 
                    // get AssemblyName so the code can determine which is the best match. 
                    VerifyDependencyAssemblies(
                        loader,
                        copyCount: 3,
                        testFixture.Delta3,
                        testFixture.Epsilon);
                    Assert.Equal(
@"Delta.3: Epsilon: Test E
",
                    actual);
                }
                else
                {
                    // See limitation 1
                    // The Epsilon.dll has Delta.dll (v2) next to it in the directory. 
                    Assert.Throws<InvalidOperationException>(() => loader.GetRealAnalyzerLoadPath(testFixture.Delta2));
 
                    // Fake the dependency so we can verify the rest of the load
                    loader.AddDependencyLocation(testFixture.Delta2);
                    VerifyDependencyAssemblies(
                        loader,
                        testFixture.Delta2,
                        testFixture.Epsilon);
 
                    Assert.Equal(
    @"Delta.2: Epsilon: Test E
",
                        actual);
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_MultipleEqualMatches(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Delta2B);
                loader.AddDependencyLocation(testFixture.Delta2);
                loader.AddDependencyLocation(testFixture.Epsilon);
 
                Assembly epsilon = loader.LoadFromPath(testFixture.Epsilon);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                e.GetType().GetMethod("Write")!.Invoke(e, new object[] { sb, "Test E" });
 
                if (ExecutionConditionUtil.IsDesktop && loader is ShadowCopyAnalyzerAssemblyLoader)
                {
                    // Delta2B and Delta2 have the same version, but we prefer Delta2B because it's added first and 
                    // in shadow loader we can't fall back to same directory because the runtime doesn't provide
                    // context for who requested the load. Just have to go to best version.
                    VerifyDependencyAssemblies(
                        loader,
                        testFixture.Delta2B,
                        testFixture.Epsilon);
 
                    var actual = sb.ToString();
                    Assert.Equal(
    @"Delta.2B: Epsilon: Test E
",
                        actual);
                }
                else
                {
                    // See limitation 1
                    // Delta2B and Delta2 have the same version, but we prefer Delta2 because it's in the same directory as Epsilon.
                    VerifyDependencyAssemblies(
                        loader,
                        testFixture.Delta2,
                        testFixture.Epsilon);
 
                    var actual = sb.ToString();
                    Assert.Equal(
    @"Delta.2: Epsilon: Test E
",
                        actual);
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_MultipleVersionsOfSameAnalyzerItself(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Delta2);
                loader.AddDependencyLocation(testFixture.Delta2B);
 
                Assembly delta2 = loader.LoadFromPath(testFixture.Delta2);
                Assembly delta2B = loader.LoadFromPath(testFixture.Delta2B);
 
                // 2B or not 2B? That is the question...that depends on whether we're on .NET Core or not.
 
#if NET

                // On Core, we're able to load both of these into separate AssemblyLoadContexts.
                if (loader.AnalyzerLoadOption == AnalyzerLoadOption.LoadFromDisk)
                {
                    Assert.NotEqual(delta2B.Location, delta2.Location);
                    Assert.Equal(loader.GetRealAnalyzerLoadPath(testFixture.Delta2), delta2.Location);
                    Assert.Equal(loader.GetRealAnalyzerLoadPath(testFixture.Delta2B), delta2B.Location);
                }
 
#else
 
                // See limitation 2
                // In non-core, we cache by assembly identity; since we don't use multiple AppDomains we have no
                // way to load different assemblies with the same identity, no matter what. Thus, we'll get the
                // same assembly for both of these.
                Assert.Same(delta2B, delta2);
#endif
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_ExactAndGreaterMatch(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Delta2B);
                loader.AddDependencyLocation(testFixture.Delta3);
                loader.AddDependencyLocation(testFixture.Epsilon);
 
                Assembly epsilon = loader.LoadFromPath(testFixture.Epsilon);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                e.GetType().GetMethod("Write")!.Invoke(e, new object[] { sb, "Test E" });
 
                var actual = sb.ToString();
                if (ExecutionConditionUtil.IsCoreClr || loader is ShadowCopyAnalyzerAssemblyLoader)
                {
                    // This works in CoreClr because we have full control over assembly loading. It 
                    // works in shadow copy because all the DLLs are put into different directories
                    // so everything is a AppDomain.AssemblyResolve event and we get full control there.
                    VerifyDependencyAssemblies(
                        loader,
                        testFixture.Delta2B,
                        testFixture.Epsilon);
 
                    Assert.Equal(
    @"Delta.2B: Epsilon: Test E
",
                        actual);
                }
                else
                {
                    // See limitation 2
                    Assert.Throws<InvalidOperationException>(() => loader.GetRealAnalyzerLoadPath(testFixture.Delta2));
 
                    // Fake the dependency so we can verify the rest of the load
                    loader.AddDependencyLocation(testFixture.Delta2);
                    VerifyDependencyAssemblies(
                        loader,
                        testFixture.Delta2,
                        testFixture.Epsilon);
 
                    Assert.Equal(
                        @"Delta.2: Epsilon: Test E
",
                        actual);
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_WorseMatchInSameDirectory(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                StringBuilder sb = new StringBuilder();
 
                var tempDir = temp.CreateDirectory();
                var tempDir1 = tempDir.CreateDirectory("a");
                var tempDir2 = tempDir.CreateDirectory("b");
                var epsilonFile = tempDir1.CreateFile("Epsilon.dll").CopyContentFrom(testFixture.Epsilon).Path;
                var delta1File = tempDir1.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                var delta2File = tempDir2.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta2).Path;
 
                loader.AddDependencyLocation(delta1File);
                loader.AddDependencyLocation(delta2File);
                loader.AddDependencyLocation(epsilonFile);
 
                Assembly epsilon = loader.LoadFromPath(epsilonFile);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                e.GetType().GetMethod("Write")!.Invoke(e, new object[] { sb, "Test E" });
 
                if (ExecutionConditionUtil.IsDesktop && loader is ShadowCopyAnalyzerAssemblyLoader)
                {
                    // In desktop + shadow load the dependencies are in different directories with 
                    // no context available when the load for Delta comes in. So we pick the best 
                    // option.
                    // Epsilon wants Delta2, but since Delta1 is in the same directory, we prefer Delta1 over Delta2.
                    VerifyDependencyAssemblies(
                        loader,
                        copyCount: 3,
                        delta2File,
                        epsilonFile);
 
                    var actual = sb.ToString();
                    Assert.Equal(
        @"Delta.2: Epsilon: Test E
",
                        actual);
                }
                else
                {
                    // See limitation 2
                    VerifyDependencyAssemblies(
                        loader,
                        delta1File,
                        epsilonFile);
 
                    var actual = sb.ToString();
                    Assert.Equal(
        @"Delta: Epsilon: Test E
",
                        actual);
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_MultipleLoaders(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader1, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader1.AddDependencyLocation(testFixture.Gamma);
                loader1.AddDependencyLocation(testFixture.Delta1);
 
                var loader2 = new DefaultAnalyzerAssemblyLoader();
                loader2.AddDependencyLocation(testFixture.Epsilon);
                loader2.AddDependencyLocation(testFixture.Delta2);
 
                Assembly gamma = loader1.LoadFromPath(testFixture.Gamma);
                var g = gamma.CreateInstance("Gamma.G")!;
                g.GetType().GetMethod("Write")!.Invoke(g, new object[] { sb, "Test G" });
 
                Assembly epsilon = loader2.LoadFromPath(testFixture.Epsilon);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                e.GetType().GetMethod("Write")!.Invoke(e, new object[] { sb, "Test E" });
 
#if NET
                var alcs1 = loader1.GetDirectoryLoadContextsSnapshot();
                Assert.Equal(1, alcs1.Length);
 
                VerifyAssemblies(
                    loader1,
                    alcs1[0].Assemblies,
                    ("Delta", "1.0.0.0", testFixture.Delta1),
                    ("Gamma", "0.0.0.0", testFixture.Gamma));
 
                var alcs2 = loader2.GetDirectoryLoadContextsSnapshot();
                Assert.Equal(1, alcs2.Length);
 
                VerifyAssemblies(
                    loader2,
                    alcs2[0].Assemblies,
                    ("Delta", "2.0.0.0", testFixture.Delta2),
                    ("Epsilon", "0.0.0.0", testFixture.Epsilon));
#endif
 
                var actual = sb.ToString();
                if (ExecutionConditionUtil.IsCoreClr)
                {
                    Assert.Equal(
    @"Delta: Gamma: Test G
Delta.2: Epsilon: Test E
",
                        actual);
                }
                else
                {
                    Assert.Equal(
    @"Delta: Gamma: Test G
Delta: Epsilon: Test E
",
                        actual);
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_MissingVersion(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.Gamma);
                loader.AddDependencyLocation(testFixture.Delta1);
                loader.AddDependencyLocation(testFixture.Epsilon);
 
                Assembly gamma = loader.LoadFromPath(testFixture.Gamma);
                var g = gamma.CreateInstance("Gamma.G")!;
                g.GetType().GetMethod("Write")!.Invoke(g, new object[] { sb, "Test G" });
 
                Assembly epsilon = loader.LoadFromPath(testFixture.Epsilon);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                var eWrite = e.GetType().GetMethod("Write")!;
 
                var actual = sb.ToString();
                eWrite.Invoke(e, new object[] { sb, "Test E" });
                Assert.Equal(
    @"Delta: Gamma: Test G
",
                    actual);
            });
        }
 
        /// <summary>
        /// Test the case where a utility is loaded by multiple analyzers at different versions. Ensure that no matter
        /// what order we load the analyzers we correctly resolve the utility version.
        /// </summary>
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_MultipleVersions_AnalyzerDependency(AnalyzerTestKind kind, bool normalOrder)
        {
            Run(kind, state: normalOrder, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture, object state) =>
            {
                using var temp = new TempRoot();
                var analyzerFilePaths = new List<string>();
                var compilerReference = MetadataReference.CreateFromFile(typeof(SyntaxNode).Assembly.Location);
                var immutableReference = MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location);
 
                var testCode = """
                    using System;
 
                    Console.WriteLine("Hello World");
                    """;
 
                var compilation = CSharpCompilation.Create(
                    "test",
                    [CSharpSyntaxTree.ParseText(SourceText.From(testCode, encoding: null, checksumAlgorithm: SourceHashAlgorithms.Default))],
                    NetStandard20.References.All);
 
                // Test loading the analyzers in different orders. That makes sure we verify the loading handles
                // the higher version of delta being loaded first or second.
                ImmutableArray<DiagnosticAnalyzer> analyzers = state is true
                    ? [loadAnalyzer1(), loadAnalyzer2()]
                    : [loadAnalyzer2(), loadAnalyzer1()];
                var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers);
                compilation.VerifyEmitDiagnostics();
                Assert.Empty(compilationWithAnalyzers.GetAllDiagnosticsAsync().Result);
 
                foreach (var analyzer in analyzers)
                {
                    assertRan(analyzer);
                }
 
                VerifyDependencyAssemblies(loader, analyzerFilePaths.ToArray());
 
                void assertRan(DiagnosticAnalyzer a)
                {
                    var prop = a.GetType().GetProperty("Ran", BindingFlags.Public | BindingFlags.Instance);
                    Assert.NotNull(prop);
                    var value = prop.GetValue(a, null);
                    Assert.True(value is true);
                }
 
                DiagnosticAnalyzer loadAnalyzer1()
                {
                    var code = """
 
                        using System;
                        using System.Collections.Immutable;
                        using Microsoft.CodeAnalysis;
                        using Microsoft.CodeAnalysis.Diagnostics;
 
                        [DiagnosticAnalyzer(LanguageNames.CSharp)]
                        public class Analyzer1: DiagnosticAnalyzer
                        {
                            public static readonly DiagnosticDescriptor Warning = new DiagnosticDescriptor(
                                "Warning2",
                                "",
                                "",
                                "",
                                DiagnosticSeverity.Warning,
                                isEnabledByDefault: true);
                            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray<DiagnosticDescriptor>.Empty.Add(Warning);
                            public bool Ran { get; set; }
                            public override void Initialize(AnalysisContext context)
                            {
                                var d = new Delta.D();
                                d.M1();
                                Ran = true;
                            }
                        }
                        """;
                    var assemblyFilePath = buildWithCode("analyzer1", code, testFixture.DeltaPublicSigned1);
                    var assembly = loader.LoadFromPath(assemblyFilePath);
                    return (DiagnosticAnalyzer)assembly.CreateInstance("Analyzer1")!;
                }
 
                DiagnosticAnalyzer loadAnalyzer2()
                {
                    var code = """
                        using System;
                        using System.Collections.Immutable;
                        using Microsoft.CodeAnalysis;
                        using Microsoft.CodeAnalysis.Diagnostics;
 
                        [DiagnosticAnalyzer(LanguageNames.CSharp)]
                        public class Analyzer2: DiagnosticAnalyzer
                        {
                            public static readonly DiagnosticDescriptor Warning = new DiagnosticDescriptor(
                                "Warning1",
                                "",
                                "",
                                "",
                                DiagnosticSeverity.Warning,
                                isEnabledByDefault: true);
                            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray<DiagnosticDescriptor>.Empty.Add(Warning);
 
                            public bool Ran { get; set; }
                            public override void Initialize(AnalysisContext context)
                            {
                                var d = new Delta.D();
                                d.M2();
                                Ran = true;
                            }
                        }
                        """;
                    var assemblyFilePath = buildWithCode("analyzer2", code, testFixture.DeltaPublicSigned2);
                    var assembly = loader.LoadFromPath(assemblyFilePath);
                    return (DiagnosticAnalyzer)assembly.CreateInstance("Analyzer2")!;
                }
 
                string buildWithCode(string assemblyName, string analyzerCode, string deltaFilePath)
                {
                    var dir = temp.CreateDirectory();
                    var deltaNewFilePath = dir.CopyFile(deltaFilePath).Path;
 
                    var compilation = CSharpCompilation.Create(
                        assemblyName,
                        [CSharpSyntaxTree.ParseText(SourceText.From(analyzerCode, encoding: null, checksumAlgorithm: SourceHashAlgorithms.Default))],
                        [
                            .. NetStandard20.References.All,
                            compilerReference,
                            immutableReference,
                            MetadataReference.CreateFromFile(deltaFilePath)
                        ],
                        TestOptions.DebugDll.WithPublicSign(true).WithCryptoPublicKey(SigningTestHelpers.PublicKey));
 
                    var array = compilation.EmitToArray(EmitOptions.Default);
                    var assemblyFilePath = dir.CreateFile(assemblyName + ".dll").WriteAllBytes(array).Path;
                    loader.AddDependencyLocation(deltaNewFilePath);
                    analyzerFilePaths.Add(deltaNewFilePath);
                    loader.AddDependencyLocation(assemblyFilePath);
                    analyzerFilePaths.Add(assemblyFilePath);
                    return assemblyFilePath;
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_UnifyToHighest(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var sb = new StringBuilder();
 
                // Gamma depends on Delta v1, and Epsilon depends on Delta v2. But both should load
                // and both use Delta v2. We intentionally here are not adding a reference to Delta1, since
                // this test is testing what happens if it's not present. A simple example for this scenario
                // is an analyzer that depends on both Gamma and Epsilon; an analyzer package can't reasonably
                // package both Delta v1 and Delta v2, so it'll only package the highest and things should work.
                loader.AddDependencyLocation(testFixture.GammaReferencingPublicSigned);
                loader.AddDependencyLocation(testFixture.EpsilonReferencingPublicSigned);
                loader.AddDependencyLocation(testFixture.DeltaPublicSigned2);
 
                var gamma = loader.LoadFromPath(testFixture.GammaReferencingPublicSigned);
                var g = gamma.CreateInstance("Gamma.G")!;
                g.GetType().GetMethod("Write")!.Invoke(g, new object[] { sb, "Test G" });
 
                var epsilon = loader.LoadFromPath(testFixture.EpsilonReferencingPublicSigned);
                var e = epsilon.CreateInstance("Epsilon.E")!;
                e.GetType().GetMethod("Write")!.Invoke(e, new object[] { sb, "Test E" });
 
                var actual = sb.ToString();
 
                Assert.Equal(
    @"Delta.2: Gamma: Test G
Delta.2: Epsilon: Test E
",
                    actual);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_CanLoadDifferentVersionsDirectly(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var sb = new StringBuilder();
 
                // Ensure that no matter what, if we have two analyzers of different versions, we never unify them.
                loader.AddDependencyLocation(testFixture.DeltaPublicSigned1);
                loader.AddDependencyLocation(testFixture.DeltaPublicSigned2);
 
                var delta1Assembly = loader.LoadFromPath(testFixture.DeltaPublicSigned1);
                var delta1Instance = delta1Assembly.CreateInstance("Delta.D")!;
                delta1Instance.GetType().GetMethod("Write")!.Invoke(delta1Instance, new object[] { sb, "Test D1" });
 
                var delta2Assembly = loader.LoadFromPath(testFixture.DeltaPublicSigned2);
                var delta2Instance = delta2Assembly.CreateInstance("Delta.D")!;
                delta2Instance.GetType().GetMethod("Write")!.Invoke(delta2Instance, new object[] { sb, "Test D2" });
 
                var actual = sb.ToString();
 
                Assert.Equal(
    @"Delta: Test D1
Delta.2: Test D2
",
                    actual);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_AnalyzerReferencesSystemCollectionsImmutable_01(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.UserSystemCollectionsImmutable);
                loader.AddDependencyLocation(testFixture.AnalyzerReferencesSystemCollectionsImmutable1);
 
                Assembly analyzerAssembly = loader.LoadFromPath(testFixture.AnalyzerReferencesSystemCollectionsImmutable1);
                var analyzer = analyzerAssembly.CreateInstance("Analyzer")!;
 
                if (ExecutionConditionUtil.IsCoreClr)
                {
                    var ex = Assert.ThrowsAny<Exception>(() => analyzer.GetType().GetMethod("Method")!.Invoke(analyzer, new object[] { sb }));
                    Assert.True(ex is MissingMethodException or TargetInvocationException, $@"Unexpected exception type: ""{ex.GetType()}""");
                }
                else
                {
                    analyzer.GetType().GetMethod("Method")!.Invoke(analyzer, new object[] { sb });
                    Assert.Equal("42", sb.ToString());
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_AnalyzerReferencesSystemCollectionsImmutable_02(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                StringBuilder sb = new StringBuilder();
 
                loader.AddDependencyLocation(testFixture.UserSystemCollectionsImmutable);
                loader.AddDependencyLocation(testFixture.AnalyzerReferencesSystemCollectionsImmutable2);
 
                Assembly analyzerAssembly = loader.LoadFromPath(testFixture.AnalyzerReferencesSystemCollectionsImmutable2);
                var analyzer = analyzerAssembly.CreateInstance("Analyzer")!;
                analyzer.GetType().GetMethod("Method")!.Invoke(analyzer, new object[] { sb });
                Assert.Equal(ExecutionConditionUtil.IsCoreClr ? "1" : "42", sb.ToString());
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_CompilerDependencyDuplicated(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var assembly = typeof(ImmutableArray<int>).Assembly;
 
                // Copy the dependency to a new location to simulate it being deployed by an 
                // analyzer / generator
                using var tempRoot = new TempRoot();
                var destFile = tempRoot.CreateDirectory().CreateOrOpenFile($"{assembly.GetName().Name}.dll").Path;
                File.Copy(assembly.Location, destFile, overwrite: true);
                loader.AddDependencyLocation(destFile);
 
                var copiedAssembly = loader.LoadFromPath(destFile);
                Assert.Single(AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName == assembly.FullName));
                Assert.Same(copiedAssembly, assembly);
            });
        }
 
        [ConditionalTheory(typeof(WindowsOnly))]
        [CombinatorialData]
        public void AssemblyLoading_NativeDependency(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                const int INVALID_FILE_ATTRIBUTES = -1;
                loader.AddDependencyLocation(testFixture.AnalyzerWithNativeDependency);
 
                Assembly analyzerAssembly = loader.LoadFromPath(testFixture.AnalyzerWithNativeDependency);
                var analyzer = analyzerAssembly.CreateInstance("Class1")!;
                var result = analyzer.GetType().GetMethod("GetFileAttributes")!.Invoke(analyzer, new[] { testFixture.AnalyzerWithNativeDependency });
                Assert.NotEqual(INVALID_FILE_ATTRIBUTES, result);
                Assert.Equal(FileAttributes.Archive | FileAttributes.ReadOnly, (FileAttributes)result!);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_DeleteAfterLoad1(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                var deltaCopy = tempDir.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                loader.AddDependencyLocation(deltaCopy);
                _ = loader.LoadFromPath(deltaCopy);
 
                if (loader is ShadowCopyAnalyzerAssemblyLoader || !ExecutionConditionUtil.IsWindows)
                {
                    File.Delete(deltaCopy);
                }
                else
                {
                    Assert.Throws<UnauthorizedAccessException>(() => File.Delete(testFixture.Delta1));
                }
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_DeleteAfterLoad2(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                StringBuilder sb = new StringBuilder();
 
                var tempDir = temp.CreateDirectory();
                var deltaCopy = tempDir.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                loader.AddDependencyLocation(deltaCopy);
                Assembly? delta = loader.LoadFromPath(deltaCopy);
 
                if (loader is ShadowCopyAnalyzerAssemblyLoader || !ExecutionConditionUtil.IsWindows)
                {
                    File.Delete(deltaCopy);
                }
 
                // Ensure everything is functioning still 
                var d = delta.CreateInstance("Delta.D");
                d!.GetType().GetMethod("Write")!.Invoke(d, new object[] { sb, "Test D" });
 
                var actual = sb.ToString();
                Assert.Equal(
    @"Delta: Test D
",
                    actual);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_DeleteAfterLoad3(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                var sb = new StringBuilder();
 
                var tempDir1 = tempDir.CreateDirectory("a");
                var tempDir2 = tempDir.CreateDirectory("b");
                var tempDir3 = tempDir.CreateDirectory("c");
 
                var delta1File = tempDir1.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1).Path;
                var delta2File = tempDir2.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta2).Path;
                var gammaFile = tempDir3.CreateFile("Gamma.dll").CopyContentFrom(testFixture.Gamma).Path;
 
                loader.AddDependencyLocation(delta1File);
                loader.AddDependencyLocation(delta2File);
                loader.AddDependencyLocation(gammaFile);
                Assembly gamma = loader.LoadFromPath(gammaFile);
 
                var b = gamma.CreateInstance("Gamma.G")!;
                var writeMethod = b.GetType().GetMethod("Write")!;
                writeMethod.Invoke(b, new object[] { sb, "Test G" });
 
                if (loader is ShadowCopyAnalyzerAssemblyLoader)
                {
                    File.Delete(delta1File);
                    File.Delete(delta2File);
                    File.Delete(gammaFile);
                }
 
                var actual = sb.ToString();
                Assert.Equal(@"Delta: Gamma: Test G
", actual);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_RepeatedLoads1(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var path = testFixture.Delta1;
                loader.AddDependencyLocation(path);
                var expected = loader.LoadFromPath(path);
 
                for (var i = 0; i < 5; i++)
                {
                    loader.AddDependencyLocation(path);
                    var actual = loader.LoadFromPath(path);
                    Assert.Same(expected, actual);
                }
 
                VerifyDependencyAssemblies(loader, path);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_RepeatedLoads2(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                var tempFile = tempDir.CreateFile("Delta.dll").CopyContentFrom(testFixture.Delta1);
                var path = tempFile.Path;
                loader.AddDependencyLocation(path);
                var expected = loader.LoadFromPath(path);
 
                for (var i = 0; i < 5; i++)
                {
                    if (loader is ShadowCopyAnalyzerAssemblyLoader)
                    {
                        File.WriteAllBytes(path, new byte[] { 42 });
                    }
                    loader.AddDependencyLocation(path);
                    var actual = loader.LoadFromPath(path);
                    Assert.Same(expected, actual);
                }
 
                if (loader is ShadowCopyAnalyzerAssemblyLoader shadowLoader)
                {
                    // Ensure that despite the on disk changes only one shadow copy occurred
                    Assert.Equal(1, shadowLoader.CopyCount);
                    tempFile.CopyContentFrom(testFixture.Delta1);
                }
 
                VerifyDependencyAssemblies(loader, path);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_Resources(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                var analyzerPath = tempDir.CreateFile("AnalyzerWithLoc.dll").CopyContentFrom(testFixture.AnalyzerWithLoc).Path;
                var analyzerResourcesPath = tempDir.CreateDirectory("en-GB").CreateFile("AnalyzerWithLoc.resources.dll").CopyContentFrom(testFixture.AnalyzerWithLocResourceEnGB).Path;
                loader.AddDependencyLocation(analyzerPath);
                var assembly = loader.LoadFromPath(analyzerPath);
                var methodInfo = assembly
                    .GetType("AnalyzerWithLoc.Util")!
                    .GetMethod("Exec", BindingFlags.Static | BindingFlags.Public)!;
                methodInfo.Invoke(null, ["en-GB"]);
 
                // The copy count is 1 here as only one real assembly was copied, the resource 
                // dlls don't apply for this count.
                VerifyDependencyAssemblies(loader, copyCount: 1, analyzerPath, analyzerResourcesPath);
            });
        }
 
        [Theory]
        [CombinatorialData]
        public void AssemblyLoading_ResourcesInParent(AnalyzerTestKind kind)
        {
            Run(kind, static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                using var temp = new TempRoot();
                var tempDir = temp.CreateDirectory();
                var analyzerPath = tempDir.CreateFile("AnalyzerWithLoc.dll").CopyContentFrom(testFixture.AnalyzerWithLoc).Path;
                var analyzerResourcesPath = tempDir.CreateDirectory("es").CreateFile("AnalyzerWithLoc.resources.dll").CopyContentFrom(testFixture.AnalyzerWithLocResourceEnGB).Path;
                loader.AddDependencyLocation(analyzerPath);
                var assembly = loader.LoadFromPath(analyzerPath);
                var methodInfo = assembly
                    .GetType("AnalyzerWithLoc.Util")!
                    .GetMethod("Exec", BindingFlags.Static | BindingFlags.Public)!;
                methodInfo.Invoke(null, ["es-ES"]);
 
                // The copy count is 1 here as only one real assembly was copied, the resource 
                // dlls don't apply for this count.
                VerifyDependencyAssemblies(loader, copyCount: 1, analyzerPath, analyzerResourcesPath);
            });
        }
 
#if NET

        [Theory]
        [CombinatorialData]
        public void AssemblyLoadingInNonDefaultContext_AnalyzerReferencesSystemCollectionsImmutable(AnalyzerTestKind kind)
        {
            Run(kind,
                static (AssemblyLoadContext compilerContext, AssemblyLoadTestFixture testFixture) =>
                {
                    // Load the compiler assembly and a modified version of S.C.I into the compiler load context. We
                    // expect the analyzer will use the bogus S.C.I in the compiler context instead of the one 
                    // in the host context.
                    _ = compilerContext.LoadFromAssemblyPath(testFixture.UserSystemCollectionsImmutable);
                    _ = compilerContext.LoadFromAssemblyPath(typeof(AnalyzerAssemblyLoader).GetTypeInfo().Assembly.Location);
                },
                static (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
                {
                    StringBuilder sb = new StringBuilder();
 
                    loader.AddDependencyLocation(testFixture.UserSystemCollectionsImmutable);
                    loader.AddDependencyLocation(testFixture.AnalyzerReferencesSystemCollectionsImmutable1);
 
                    Assembly analyzerAssembly = loader.LoadFromPath(testFixture.AnalyzerReferencesSystemCollectionsImmutable1);
                    var analyzer = analyzerAssembly.CreateInstance("Analyzer")!;
                    analyzer.GetType().GetMethod("Method")!.Invoke(analyzer, new object[] { sb });
 
                    Assert.Equal("42", sb.ToString());
                });
        }
#endif
 
        [Theory]
        [CombinatorialData]
        public void ExternalResolver_CanIntercept_ReturningNull(AnalyzerTestKind kind)
        {
            var resolver = new TestAnalyzerAssemblyResolver(n => null);
            Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                loader.AddDependencyLocation(testFixture.Delta1);
                Assembly delta = loader.LoadFromPath(testFixture.Delta1);
                Assert.NotNull(delta);
                VerifyDependencyAssemblies(loader, testFixture.Delta1);
 
            }, externalResolvers: [resolver]);
            Assert.Collection(resolver.CalledFor, (a => Assert.Equal("Delta", a.Name)));
        }
 
        [Theory]
        [CombinatorialData]
        public void ExternalResolver_CanIntercept_ReturningAssembly(AnalyzerTestKind kind)
        {
            var resolver = new TestAnalyzerAssemblyResolver(n => GetType().Assembly);
            Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                // net core assembly loader checks that the resolved assembly name is the same as the requested one
                // so we use the assembly the tests are contained in as its already be loaded
                var thisAssembly = typeof(AnalyzerAssemblyLoaderTests).Assembly;
                loader.AddDependencyLocation(thisAssembly.Location);
                Assembly loaded = loader.LoadFromPath(thisAssembly.Location);
                Assert.Equal(thisAssembly, loaded);
 
            }, externalResolvers: [resolver]);
            Assert.Collection(resolver.CalledFor, (a => Assert.Equal(GetType().Assembly.GetName().Name, a.Name)));
        }
 
        [Theory]
        [CombinatorialData]
        public void ExternalResolver_CanIntercept_ReturningAssembly_Or_Null(AnalyzerTestKind kind)
        {
            var thisAssemblyName = GetType().Assembly.GetName();
            var resolver = new TestAnalyzerAssemblyResolver(n => n == thisAssemblyName ? GetType().Assembly : null);
            Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var thisAssembly = typeof(AnalyzerAssemblyLoaderTests).Assembly;
 
                loader.AddDependencyLocation(testFixture.Alpha);
                Assembly alpha = loader.LoadFromPath(testFixture.Alpha);
                Assert.NotNull(alpha);
 
                loader.AddDependencyLocation(thisAssembly.Location);
                Assembly loaded = loader.LoadFromPath(thisAssembly.Location);
                Assert.Equal(thisAssembly, loaded);
 
                loader.AddDependencyLocation(testFixture.Delta1);
                Assembly delta = loader.LoadFromPath(testFixture.Delta1);
                Assert.NotNull(delta);
 
            }, externalResolvers: [resolver]);
            Assert.Collection(resolver.CalledFor, (a => Assert.Equal("Alpha", a.Name)), a => Assert.Equal(thisAssemblyName.Name, a.Name), a => Assert.Equal("Delta", a.Name));
        }
 
        [Theory]
        [CombinatorialData]
        public void ExternalResolver_MultipleResolvers_CanIntercept_ReturningNull(AnalyzerTestKind kind)
        {
            var resolver1 = new TestAnalyzerAssemblyResolver(n => null);
            var resolver2 = new TestAnalyzerAssemblyResolver(n => null);
            Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                loader.AddDependencyLocation(testFixture.Delta1);
                Assembly delta = loader.LoadFromPath(testFixture.Delta1);
                Assert.NotNull(delta);
                VerifyDependencyAssemblies(loader, testFixture.Delta1);
 
            }, externalResolvers: [resolver1, resolver2]);
            Assert.Collection(resolver1.CalledFor, (a => Assert.Equal("Delta", a.Name)));
            Assert.Collection(resolver2.CalledFor, (a => Assert.Equal("Delta", a.Name)));
        }
 
        [Theory]
        [CombinatorialData]
        public void ExternalResolver_MultipleResolvers_ResolutionStops_AfterFirstResolve(AnalyzerTestKind kind)
        {
            var resolver1 = new TestAnalyzerAssemblyResolver(n => GetType().Assembly);
            var resolver2 = new TestAnalyzerAssemblyResolver(n => null);
            Run(kind, (AnalyzerAssemblyLoader loader, AssemblyLoadTestFixture testFixture) =>
            {
                var thisAssembly = typeof(AnalyzerAssemblyLoaderTests).Assembly;
                loader.AddDependencyLocation(thisAssembly.Location);
                Assembly loaded = loader.LoadFromPath(thisAssembly.Location);
                Assert.Equal(thisAssembly, loaded);
 
            }, externalResolvers: [resolver1, resolver2]);
            Assert.Collection(resolver1.CalledFor, (a => Assert.Equal(GetType().Assembly.GetName().Name, a.Name)));
            Assert.Empty(resolver2.CalledFor);
        }
 
        [Serializable]
        private class TestAnalyzerAssemblyResolver(Func<AssemblyName, Assembly?> func) : MarshalByRefObject, IAnalyzerAssemblyResolver
        {
            private readonly Func<AssemblyName, Assembly?> _func = func;
 
            public List<AssemblyName> CalledFor { get; } = [];
 
            public Assembly? ResolveAssembly(AssemblyName assemblyName)
            {
                CalledFor.Add(assemblyName);
                return _func(assemblyName);
            }
        }
    }
}