From d692bf5a929e573d57f9657db60648b6cf6fa626 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 17 Apr 2026 12:49:01 +0200 Subject: [PATCH 1/8] Fix WinUI TestContext.Current being null --- .../Execution/TestMethodInfo.cs | 6 +++++- .../Execution/TestMethodRunner.cs | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) 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)); } } From 8b1d7e9cdb60b3b4f437aa398b194445154f0412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 17 Apr 2026 17:30:22 +0200 Subject: [PATCH 2/8] Remove AsyncLocal fallback and add regression test for cross-thread TestContext --- .../Execution/TestMethodInfo.cs | 2 +- .../Execution/TestMethodRunnerTests.cs | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index ee240aab96..45395538fa 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -55,7 +55,7 @@ internal TestMethodInfo(MethodInfo testMethod, TestClassInfo parent) internal ITestContext TestContext { - get => field ?? (ITestContext?)TestTools.UnitTesting.TestContext.Current ?? throw ApplicationStateGuard.Unreachable(); + get => field ?? throw ApplicationStateGuard.Unreachable(); set; } diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 257043dc55..e6f1d41c86 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -420,8 +420,46 @@ private async Task RunTestMethodWithEmptyDataSourceShouldFailIfConsiderEmptyData } } + public async Task RunTestMethodShouldPassWhenAttributeInvokesTestMethodOnExecutionContextUnsafeThread() + { + 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 + { + public override async Task ExecuteAsync(ITestMethod testMethod) + { + var taskCompletionSource = new TaskCompletionSource(); + ThreadPool.UnsafeQueueUserWorkItem( + _ => + { + try + { + taskCompletionSource.SetResult(testMethod.InvokeAsync(null).ConfigureAwait(false).GetAwaiter().GetResult()); + } + catch (Exception exception) + { + taskCompletionSource.SetException(exception); + } + }, + null); + + return [await taskCompletionSource.Task.ConfigureAwait(false)]; + } + } + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Use through reflection")] private static void InitMethodThrowingException(TestContext tc) // TODO: Fix exception type From ff33e06b143dd05c346175b295f4b752916548d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:02:14 +0000 Subject: [PATCH 3/8] Restore TestContext.Current fallback in TestMethodInfo Agent-Logs-Url: https://github.com/microsoft/testfx/sessions/0103e8e1-f2d9-4a30-9e5d-93ea53c3775f Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com> --- .../MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index 45395538fa..ee240aab96 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -55,7 +55,7 @@ internal TestMethodInfo(MethodInfo testMethod, TestClassInfo parent) internal ITestContext TestContext { - get => field ?? throw ApplicationStateGuard.Unreachable(); + get => field ?? (ITestContext?)TestTools.UnitTesting.TestContext.Current ?? throw ApplicationStateGuard.Unreachable(); set; } From 5a3d6200f52283a586e7f682dd48780c5719f0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Mon, 20 Apr 2026 12:17:18 +0200 Subject: [PATCH 4/8] Fix test --- .../Execution/TestMethodRunnerTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index e6f1d41c86..ceac7fd81b 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -422,6 +422,11 @@ 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) { From 6af9a6ee2740b1bb5c3f25bb082762ee3ef9b8fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:49:46 +0000 Subject: [PATCH 5/8] Add timeout and async continuations in unsafe thread test helper Agent-Logs-Url: https://github.com/microsoft/testfx/sessions/c216287c-de48-4a49-8c08-d7e369d2edea Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com> --- .../Execution/TestMethodRunnerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index ceac7fd81b..4d3c6ae736 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -446,7 +446,7 @@ private sealed class ExecutionContextUnsafeThreadTestMethodAttribute : TestMetho { public override async Task ExecuteAsync(ITestMethod testMethod) { - var taskCompletionSource = new TaskCompletionSource(); + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); ThreadPool.UnsafeQueueUserWorkItem( _ => { @@ -461,7 +461,7 @@ public override async Task ExecuteAsync(ITestMethod testMethod) }, null); - return [await taskCompletionSource.Task.ConfigureAwait(false)]; + return [await taskCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(10)).ConfigureAwait(false)]; } } From ad12f2ac3105baf21d488e6dc418f9c46936a02b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:51:35 +0000 Subject: [PATCH 6/8] Use named timeout constant in unsafe-thread test helper Agent-Logs-Url: https://github.com/microsoft/testfx/sessions/c216287c-de48-4a49-8c08-d7e369d2edea Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com> --- .../Execution/TestMethodRunnerTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 4d3c6ae736..3c0ed85a78 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -444,6 +444,8 @@ public async Task RunTestMethodShouldPassWhenAttributeInvokesTestMethodOnExecuti 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); @@ -461,7 +463,7 @@ public override async Task ExecuteAsync(ITestMethod testMethod) }, null); - return [await taskCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(10)).ConfigureAwait(false)]; + return [await taskCompletionSource.Task.WaitAsync(WaitTimeout).ConfigureAwait(false)]; } } From d206a41a74f7baaff29e4882354f1af9e4ca3b4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:28:58 +0000 Subject: [PATCH 7/8] Replace WaitAsync with Task.WhenAny timeout for net462 compatibility Agent-Logs-Url: https://github.com/microsoft/testfx/sessions/5400fae9-d407-494c-8bf1-c7b27ec6d7d0 Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com> --- .../Execution/TestMethodRunnerTests.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index 3c0ed85a78..da82d91fc3 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -463,7 +463,13 @@ public override async Task ExecuteAsync(ITestMethod testMethod) }, null); - return [await taskCompletionSource.Task.WaitAsync(WaitTimeout).ConfigureAwait(false)]; + Task completedTask = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(WaitTimeout)).ConfigureAwait(false); + if (completedTask != taskCompletionSource.Task) + { + throw new TimeoutException($"The execution did not complete within {WaitTimeout}."); + } + + return [await taskCompletionSource.Task.ConfigureAwait(false)]; } } From e24aa4e8e436685bde658ce08a45da8ea9af6ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Tue, 21 Apr 2026 10:22:32 +0200 Subject: [PATCH 8/8] Fix code --- .../Execution/TestMethodRunnerTests.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs index da82d91fc3..6186169e76 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Execution/TestMethodRunnerTests.cs @@ -464,12 +464,9 @@ public override async Task ExecuteAsync(ITestMethod testMethod) null); Task completedTask = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(WaitTimeout)).ConfigureAwait(false); - if (completedTask != taskCompletionSource.Task) - { - throw new TimeoutException($"The execution did not complete within {WaitTimeout}."); - } - - return [await taskCompletionSource.Task.ConfigureAwait(false)]; + return completedTask == taskCompletionSource.Task + ? [await taskCompletionSource.Task.ConfigureAwait(false)] + : throw new TimeoutException($"The execution did not complete within {WaitTimeout}."); } }