|
// 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();
}
}
}
}
}
|