diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index 1b8756af00..ee240aab96 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -53,7 +53,11 @@ internal TestMethodInfo(MethodInfo testMethod, TestClassInfo parent) internal TestMethodAttribute Executor { get; /*For testing only*/set; } - internal static ITestContext TestContext => (ITestContext?)TestTools.UnitTesting.TestContext.Current ?? throw ApplicationStateGuard.Unreachable(); + internal ITestContext TestContext + { + get => field ?? (ITestContext?)TestTools.UnitTesting.TestContext.Current ?? throw ApplicationStateGuard.Unreachable(); + set; + } /// /// Gets a value indicating whether timeout is set. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs index 3f0332fa95..da74d5b9f0 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodRunner.cs @@ -392,6 +392,7 @@ private async Task ExecuteTestAsync(TestMethodInfo testMethodInfo) { using (TestContextImplementation.SetCurrentTestContext(_testContext as TestContext)) { + testMethodInfo.TestContext = _testContext; tcs.SetResult(await _testMethodInfo.Executor.ExecuteAsync(testMethodInfo).ConfigureAwait(false)); } } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 257043dc55..6186169e76 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -420,8 +420,56 @@ private async Task RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyData } } + public async Task RunTestMethodShouldPassWhenAttributeInvokesTestMethodOnExecutionContextUnsafeThread() + { + _testablePlatformServiceProvider.MockThreadOperations + .Setup(tho => tho.Execute(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(true) + .Callback((Action a, int timeout, CancellationToken token) => a.Invoke()); + + var localTestMethodOptions = new TestMethodOptions(TimeoutInfo.FromTimeout(200), new ExecutionContextUnsafeThreadTestMethodAttribute()); + var testMethodInfo = new TestMethodInfo(_methodInfo, _testClassInfo) + { + TimeoutInfo = localTestMethodOptions.TimeoutInfo, + Executor = localTestMethodOptions.TestMethodAttribute, + }; + var testMethodRunner = new TestMethodRunner(testMethodInfo, _testMethod, _testContextImplementation); + + TestResult[] results = await testMethodRunner.ExecuteAsync(string.Empty, string.Empty, string.Empty, string.Empty); + results.Should().HaveCount(1); + results[0].Outcome.Should().Be(UnitTestOutcome.Passed); + } + #region Test data + private sealed class ExecutionContextUnsafeThreadTestMethodAttribute : TestMethodAttribute + { + private static readonly TimeSpan WaitTimeout = TimeSpan.FromSeconds(10); + + public override async Task ExecuteAsync(ITestMethod testMethod) + { + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + ThreadPool.UnsafeQueueUserWorkItem( + _ => + { + try + { + taskCompletionSource.SetResult(testMethod.InvokeAsync(null).ConfigureAwait(false).GetAwaiter().GetResult()); + } + catch (Exception exception) + { + taskCompletionSource.SetException(exception); + } + }, + null); + + Task completedTask = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(WaitTimeout)).ConfigureAwait(false); + return completedTask == taskCompletionSource.Task + ? [await taskCompletionSource.Task.ConfigureAwait(false)] + : throw new TimeoutException($"The execution did not complete within {WaitTimeout}."); + } + } + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Use through reflection")] private static void InitMethodThrowingException(TestContext tc) // TODO: Fix exception type