File: XunitDisposeHook.cs
Web Access
Project: src\src\EditorFeatures\XunitHook\Microsoft.CodeAnalysis.XunitHook.csproj (Microsoft.CodeAnalysis.XunitHook)
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace Microsoft.CodeAnalysis.Test.Utilities;
 
internal sealed class XunitDisposeHook : MarshalByRefObject
{
    [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Invoked across app domains")]
    public void Execute()
    {
        if (!AppDomain.CurrentDomain.IsDefaultAppDomain())
            throw new InvalidOperationException();
 
        var xunitUtilities = AppDomain.CurrentDomain.GetAssemblies().Where(static assembly => assembly.GetName().Name.StartsWith("xunit.runner.utility")).ToArray();
        foreach (var xunitUtility in xunitUtilities)
        {
            var appDomainManagerType = xunitUtility.GetType("Xunit.AppDomainManager_AppDomain");
            if (appDomainManagerType is null)
                continue;
 
            // AppDomainManager_AppDomain.Dispose() calls AppDomain.Unload(), which is unfortunately not reliable
            // when the test creates STA COM objects. Since this call to Unload() only occurs at the end of testing
            // (immediately before the process is going to close anyway), we can simply hot-patch the executable
            // code in Dispose() to return without taking any action.
            //
            // This is a workaround for https://github.com/xunit/xunit/issues/2097. The fix in
            // https://github.com/xunit/xunit/pull/2192 was not viable because xunit v2 is no longer shipping
            // updates. Once xunit v3 is available, it will no longer be necessary.
            var method = appDomainManagerType.GetMethod("Dispose");
            RuntimeHelpers.PrepareMethod(method.MethodHandle);
            var functionPointer = method.MethodHandle.GetFunctionPointer();
 
            switch (RuntimeInformation.ProcessArchitecture)
            {
                case Architecture.X86:
                case Architecture.X64:
                    // 😱 Overwrite the compiled method to just return.
                    // Note that the same sequence works for x86 and x64.
 
                    // ret
                    Marshal.WriteByte(functionPointer, 0xC3);
                    break;
 
                default:
                    throw new NotSupportedException();
            }
        }
    }
}