@@ -157,43 +157,49 @@ public virtual async Task<TestResult> InvokeAsync(object?[]? arguments)
157157
158158 ExecutionContext ? executionContext = Parent . ExecutionContext ?? Parent . Parent . ExecutionContext ;
159159
160- try
160+ var tcs = new TaskCompletionSource < object ? > ( ) ;
161+
162+ #pragma warning disable VSTHRD101 // Avoid unsupported async delegates
163+ ExecutionContextHelpers . RunOnContext ( executionContext , async ( ) =>
161164 {
162- ExecutionContextHelpers . RunOnContext ( executionContext , ( ) =>
165+ try
163166 {
164167 ThreadSafeStringWriter . CleanState ( ) ;
165168 listener = new LogMessageListener ( MSTestSettings . CurrentSettings . CaptureDebugTraces ) ;
166- executionContext = ExecutionContext . Capture ( ) ;
167- } ) ;
168169
169- result = IsTimeoutSet
170- ? await ExecuteInternalWithTimeoutAsync ( arguments , executionContext )
171- : await ExecuteInternalAsync ( arguments , executionContext , null ) ;
172- }
173- finally
174- {
175- // Handle logs & debug traces.
176- watch . Stop ( ) ;
177-
178- if ( result != null )
170+ result = IsTimeoutSet
171+ ? await ExecuteInternalWithTimeoutAsync ( arguments )
172+ : await ExecuteInternalAsync ( arguments , null ) ;
173+ tcs . SetResult ( null ) ;
174+ }
175+ catch ( Exception e )
179176 {
180- result . Duration = watch . Elapsed ;
181- if ( listener is not null )
177+ tcs . SetException ( e ) ;
178+ }
179+ finally
180+ {
181+ // Handle logs & debug traces.
182+ watch . Stop ( ) ;
183+
184+ if ( result != null )
182185 {
183- ExecutionContextHelpers . RunOnContext ( executionContext , ( ) =>
186+ result . Duration = watch . Elapsed ;
187+ if ( listener is not null )
184188 {
185189 result . DebugTrace = listener . GetAndClearDebugTrace ( ) ;
186190 result . LogOutput = listener . GetAndClearStandardOutput ( ) ;
187191 result . LogError = listener . GetAndClearStandardError ( ) ;
188192 result . TestContextMessages = TestContext ? . GetAndClearDiagnosticMessages ( ) ;
189193 result . ResultFiles = TestContext ? . GetResultFiles ( ) ;
190194 listener . Dispose ( ) ;
191- } ) ;
195+ }
192196 }
193197 }
194- }
198+ } ) ;
199+ #pragma warning restore VSTHRD101 // Avoid unsupported async delegates
195200
196- return result ;
201+ await tcs . Task ;
202+ return result ! ;
197203 }
198204
199205 internal void SetArguments ( object ? [ ] ? arguments ) => Arguments = arguments == null ? null : ResolveArguments ( arguments ) ;
@@ -394,22 +400,17 @@ private void ThrowMultipleAttributesException(string attributeName)
394400 /// Execute test without timeout.
395401 /// </summary>
396402 /// <param name="arguments">Arguments to be passed to the method.</param>
397- /// <param name="executionContext">The execution context to execute the test method on.</param>
398403 /// <param name="timeoutTokenSource">The timeout token source.</param>
399404 /// <returns>The result of the execution.</returns>
400405 [ SuppressMessage ( "Microsoft.Design" , "CA1031:DoNotCatchGeneralExceptionTypes" , Justification = "Requirement is to handle all kinds of user exceptions and message appropriately." ) ]
401- private async Task < TestResult > ExecuteInternalAsync ( object ? [ ] ? arguments , ExecutionContext ? executionContext , CancellationTokenSource ? timeoutTokenSource )
406+ private async Task < TestResult > ExecuteInternalAsync ( object ? [ ] ? arguments , CancellationTokenSource ? timeoutTokenSource )
402407 {
403408 DebugEx . Assert ( TestMethod != null , "UnitTestExecuter.DefaultTestMethodInvoke: testMethod = null." ) ;
404409
405410 var result = new TestResult ( ) ;
406411
407412 // TODO remove dry violation with TestMethodRunner
408- ExecutionContextHelpers . RunOnContext ( executionContext , ( ) =>
409- {
410- _classInstance = CreateTestClassInstance ( result ) ;
411- executionContext = ExecutionContext . Capture ( ) ;
412- } ) ;
413+ _classInstance = CreateTestClassInstance ( result ) ;
413414 bool isExceptionThrown = false ;
414415 bool hasTestInitializePassed = false ;
415416 Exception ? testRunnerException = null ;
@@ -424,36 +425,19 @@ private async Task<TestResult> ExecuteInternalAsync(object?[]? arguments, Execut
424425 // For any failure after this point, we must run TestCleanup
425426 _isTestContextSet = true ;
426427
427- if ( RunTestInitializeMethod ( _classInstance , result , ref executionContext , timeoutTokenSource ) )
428+ if ( RunTestInitializeMethod ( _classInstance , result , timeoutTokenSource ) )
428429 {
429430 hasTestInitializePassed = true ;
430- var tcs = new TaskCompletionSource < object ? > ( ) ;
431- #pragma warning disable VSTHRD101 // Avoid unsupported async delegates
432- ExecutionContextHelpers . RunOnContext ( executionContext , async ( ) =>
433- {
434- try
435- {
436- object ? invokeResult = TestMethod . GetInvokeResult ( _classInstance , arguments ) ;
437- if ( invokeResult is Task task )
438- {
439- await task ;
440- }
441- else if ( invokeResult is ValueTask valueTask )
442- {
443- await valueTask ;
444- }
445-
446- executionContext = ExecutionContext . Capture ( ) ;
447- tcs . SetResult ( null ) ;
448- }
449- catch ( Exception ex )
450- {
451- tcs . SetException ( ex ) ;
452- }
453- } ) ;
454- #pragma warning restore VSTHRD101 // Avoid unsupported async delegates
455431
456- await tcs . Task ;
432+ object ? invokeResult = TestMethod . GetInvokeResult ( _classInstance , arguments ) ;
433+ if ( invokeResult is Task task )
434+ {
435+ await task ;
436+ }
437+ else if ( invokeResult is ValueTask valueTask )
438+ {
439+ await valueTask ;
440+ }
457441
458442 result . Outcome = UTF . UnitTestOutcome . Passed ;
459443 }
@@ -532,7 +516,7 @@ private async Task<TestResult> ExecuteInternalAsync(object?[]? arguments, Execut
532516 // Pulling it out so extension writers can abort custom cleanups if need be. Having this in a finally block
533517 // does not allow a thread abort exception to be raised within the block but throws one after finally is executed
534518 // crashing the process. This was blocking writing an extension for Dynamic Timeout in VSO.
535- RunTestCleanupMethod ( result , executionContext , timeoutTokenSource ) ;
519+ RunTestCleanupMethod ( result , timeoutTokenSource ) ;
536520
537521 return testRunnerException != null ? throw testRunnerException : result ;
538522 }
@@ -677,10 +661,9 @@ private static TestFailedException HandleMethodException(Exception ex, Exception
677661 /// Runs TestCleanup methods of parent TestClass and base classes.
678662 /// </summary>
679663 /// <param name="result">Instance of TestResult.</param>
680- /// <param name="executionContext">The execution context to execute the test cleanup on.</param>
681664 /// <param name="timeoutTokenSource">The timeout token source.</param>
682665 [ SuppressMessage ( "Microsoft.Design" , "CA1031:DoNotCatchGeneralExceptionTypes" , Justification = "Requirement is to handle all kinds of user exceptions and message appropriately." ) ]
683- private void RunTestCleanupMethod ( TestResult result , ExecutionContext ? executionContext , CancellationTokenSource ? timeoutTokenSource )
666+ private void RunTestCleanupMethod ( TestResult result , CancellationTokenSource ? timeoutTokenSource )
684667 {
685668 DebugEx . Assert ( result != null , "result != null" ) ;
686669
@@ -708,13 +691,13 @@ private void RunTestCleanupMethod(TestResult result, ExecutionContext? execution
708691 // Test cleanups are called in the order of discovery
709692 // Current TestClass -> Parent -> Grandparent
710693 testCleanupException = testCleanupMethod is not null
711- ? InvokeCleanupMethod ( testCleanupMethod , _classInstance , ref executionContext , timeoutTokenSource )
694+ ? InvokeCleanupMethod ( testCleanupMethod , _classInstance , timeoutTokenSource )
712695 : null ;
713696 var baseTestCleanupQueue = new Queue < MethodInfo > ( Parent . BaseTestCleanupMethodsQueue ) ;
714697 while ( baseTestCleanupQueue . Count > 0 && testCleanupException is null )
715698 {
716699 testCleanupMethod = baseTestCleanupQueue . Dequeue ( ) ;
717- testCleanupException = InvokeCleanupMethod ( testCleanupMethod , _classInstance , ref executionContext , timeoutTokenSource ) ;
700+ testCleanupException = InvokeCleanupMethod ( testCleanupMethod , _classInstance , timeoutTokenSource ) ;
718701 }
719702 }
720703 finally
@@ -723,20 +706,12 @@ private void RunTestCleanupMethod(TestResult result, ExecutionContext? execution
723706 if ( _classInstance is IAsyncDisposable classInstanceAsAsyncDisposable )
724707 {
725708 // If you implement IAsyncDisposable without calling the DisposeAsync this would result a resource leak.
726- ExecutionContextHelpers . RunOnContext ( executionContext , ( ) =>
727- {
728- classInstanceAsAsyncDisposable . DisposeAsync ( ) . AsTask ( ) . Wait ( ) ;
729- executionContext = ExecutionContext . Capture ( ) ;
730- } ) ;
709+ classInstanceAsAsyncDisposable . DisposeAsync ( ) . AsTask ( ) . Wait ( ) ;
731710 }
732711#endif
733712 if ( _classInstance is IDisposable classInstanceAsDisposable )
734713 {
735- ExecutionContextHelpers . RunOnContext ( executionContext , ( ) =>
736- {
737- classInstanceAsDisposable . Dispose ( ) ;
738- executionContext = ExecutionContext . Capture ( ) ;
739- } ) ;
714+ classInstanceAsDisposable . Dispose ( ) ;
740715 }
741716 }
742717 }
@@ -775,11 +750,10 @@ private void RunTestCleanupMethod(TestResult result, ExecutionContext? execution
775750 /// </summary>
776751 /// <param name="classInstance">Instance of TestClass.</param>
777752 /// <param name="result">Instance of TestResult.</param>
778- /// <param name="executionContext">The execution context to execute the test initialize on.</param>
779753 /// <param name="timeoutTokenSource">The timeout token source.</param>
780754 /// <returns>True if the TestInitialize method(s) did not throw an exception.</returns>
781755 [ SuppressMessage ( "Microsoft.Design" , "CA1031:DoNotCatchGeneralExceptionTypes" , Justification = "Requirement is to handle all kinds of user exceptions and message appropriately." ) ]
782- private bool RunTestInitializeMethod ( object classInstance , TestResult result , ref ExecutionContext ? executionContext , CancellationTokenSource ? timeoutTokenSource )
756+ private bool RunTestInitializeMethod ( object classInstance , TestResult result , CancellationTokenSource ? timeoutTokenSource )
783757 {
784758 DebugEx . Assert ( classInstance != null , "classInstance != null" ) ;
785759 DebugEx . Assert ( result != null , "result != null" ) ;
@@ -796,7 +770,7 @@ private bool RunTestInitializeMethod(object classInstance, TestResult result, re
796770 {
797771 testInitializeMethod = baseTestInitializeStack . Pop ( ) ;
798772 testInitializeException = testInitializeMethod is not null
799- ? InvokeInitializeMethod ( testInitializeMethod , classInstance , ref executionContext , timeoutTokenSource )
773+ ? InvokeInitializeMethod ( testInitializeMethod , classInstance , timeoutTokenSource )
800774 : null ;
801775 if ( testInitializeException is not null )
802776 {
@@ -808,7 +782,7 @@ private bool RunTestInitializeMethod(object classInstance, TestResult result, re
808782 {
809783 testInitializeMethod = Parent . TestInitializeMethod ;
810784 testInitializeException = testInitializeMethod is not null
811- ? InvokeInitializeMethod ( testInitializeMethod , classInstance , ref executionContext , timeoutTokenSource )
785+ ? InvokeInitializeMethod ( testInitializeMethod , classInstance , timeoutTokenSource )
812786 : null ;
813787 }
814788 }
@@ -857,66 +831,49 @@ private bool RunTestInitializeMethod(object classInstance, TestResult result, re
857831 return false ;
858832 }
859833
860- private TestFailedException ? InvokeInitializeMethod ( MethodInfo methodInfo , object classInstance , ref ExecutionContext ? executionContext , CancellationTokenSource ? timeoutTokenSource )
834+ private TestFailedException ? InvokeInitializeMethod ( MethodInfo methodInfo , object classInstance , CancellationTokenSource ? timeoutTokenSource )
861835 {
862836 TimeoutInfo ? timeout = null ;
863837 if ( Parent . TestInitializeMethodTimeoutMilliseconds . TryGetValue ( methodInfo , out TimeoutInfo localTimeout ) )
864838 {
865839 timeout = localTimeout ;
866840 }
867841
868- ExecutionContext ? updatedExecutionContext = executionContext ;
869-
870842 TestFailedException ? result = FixtureMethodRunner . RunWithTimeoutAndCancellation (
871- ( ) =>
872- {
873- methodInfo . InvokeAsSynchronousTask ( classInstance , null ) ;
874- // **After** we have executed the current test initialize (it could be from the current class or from base class), we save the current context.
875- // This context will contain async locals set by the test initialize method.
876- updatedExecutionContext = ExecutionContext . Capture ( ) ;
877- } ,
843+ ( ) => methodInfo . InvokeAsSynchronousTask ( classInstance , null ) ,
878844 TestContext ! . Context . CancellationTokenSource ,
879845 timeout ,
880846 methodInfo ,
881- executionContext ,
847+ null ,
882848 Resource . TestInitializeWasCancelled ,
883849 Resource . TestInitializeTimedOut ,
884850 timeoutTokenSource is null
885851 ? null
886852 : ( timeoutTokenSource , TimeoutInfo . Timeout ) ) ;
887853
888- executionContext = updatedExecutionContext ;
889854 return result ;
890855 }
891856
892- private TestFailedException ? InvokeCleanupMethod ( MethodInfo methodInfo , object classInstance , ref ExecutionContext ? executionContext , CancellationTokenSource ? timeoutTokenSource )
857+ private TestFailedException ? InvokeCleanupMethod ( MethodInfo methodInfo , object classInstance , CancellationTokenSource ? timeoutTokenSource )
893858 {
894859 TimeoutInfo ? timeout = null ;
895860 if ( Parent . TestCleanupMethodTimeoutMilliseconds . TryGetValue ( methodInfo , out TimeoutInfo localTimeout ) )
896861 {
897862 timeout = localTimeout ;
898863 }
899864
900- ExecutionContext ? updatedExecutionContext = executionContext ;
901865 TestFailedException ? result = FixtureMethodRunner . RunWithTimeoutAndCancellation (
902- ( ) =>
903- {
904- methodInfo . InvokeAsSynchronousTask ( classInstance , null ) ;
905- // **After** we have executed the current test cleanup (it could be from the current class or from base class), we save the current context.
906- // This context will contain async locals set by the test cleanup method.
907- updatedExecutionContext = ExecutionContext . Capture ( ) ;
908- } ,
866+ ( ) => methodInfo . InvokeAsSynchronousTask ( classInstance , null ) ,
909867 TestContext ! . Context . CancellationTokenSource ,
910868 timeout ,
911869 methodInfo ,
912- executionContext ,
870+ null ,
913871 Resource . TestCleanupWasCancelled ,
914872 Resource . TestCleanupTimedOut ,
915873 timeoutTokenSource is null
916874 ? null
917875 : ( timeoutTokenSource , TimeoutInfo . Timeout ) ) ;
918876
919- executionContext = updatedExecutionContext ;
920877 return result ;
921878 }
922879
@@ -1031,10 +988,9 @@ private bool SetTestContext(object classInstance, TestResult result)
1031988 /// Execute test with a timeout.
1032989 /// </summary>
1033990 /// <param name="arguments">The arguments to be passed.</param>
1034- /// <param name="executionContext">The execution context to execute the test method on.</param>
1035991 /// <returns>The result of execution.</returns>
1036992 [ SuppressMessage ( "Microsoft.Design" , "CA1031:DoNotCatchGeneralExceptionTypes" , Justification = "Requirement is to handle all kinds of user exceptions and message appropriately." ) ]
1037- private async Task < TestResult > ExecuteInternalWithTimeoutAsync ( object ? [ ] ? arguments , ExecutionContext ? executionContext )
993+ private async Task < TestResult > ExecuteInternalWithTimeoutAsync ( object ? [ ] ? arguments )
1038994 {
1039995 DebugEx . Assert ( IsTimeoutSet , "Timeout should be set" ) ;
1040996
@@ -1058,7 +1014,7 @@ private async Task<TestResult> ExecuteInternalWithTimeoutAsync(object?[]? argume
10581014
10591015 try
10601016 {
1061- return await ExecuteInternalAsync ( arguments , executionContext , timeoutTokenSource ) ;
1017+ return await ExecuteInternalAsync ( arguments , timeoutTokenSource ) ;
10621018 }
10631019 catch ( OperationCanceledException )
10641020 {
@@ -1096,7 +1052,7 @@ private async Task<TestResult> ExecuteInternalWithTimeoutAsync(object?[]? argume
10961052
10971053 // It's possible that some failures happened and that the cleanup wasn't executed, so we need to run it here.
10981054 // The method already checks if the cleanup was already executed.
1099- RunTestCleanupMethod ( result , executionContext , null ) ;
1055+ RunTestCleanupMethod ( result , null ) ;
11001056 return result ;
11011057 }
11021058
@@ -1120,7 +1076,7 @@ private async Task<TestResult> ExecuteInternalWithTimeoutAsync(object?[]? argume
11201076
11211077 // We don't know when the cancellation happened so it's possible that the cleanup wasn't executed, so we need to run it here.
11221078 // The method already checks if the cleanup was already executed.
1123- RunTestCleanupMethod ( timeoutResult , executionContext , null ) ;
1079+ RunTestCleanupMethod ( timeoutResult , null ) ;
11241080 return timeoutResult ;
11251081
11261082 // Local functions
@@ -1135,7 +1091,7 @@ void ExecuteAsyncAction()
11351091 // dispatched back to the SynchronizationContext which offloads the work to the UI thread.
11361092 // However, the GetAwaiter().GetResult() here will block the current thread which is also the UI thread.
11371093 // So, the continuations will not be able, thus this task never completes.
1138- result = ExecuteInternalAsync ( arguments , executionContext , null ) . GetAwaiter ( ) . GetResult ( ) ;
1094+ result = ExecuteInternalAsync ( arguments , null ) . GetAwaiter ( ) . GetResult ( ) ;
11391095 }
11401096 catch ( Exception ex )
11411097 {
0 commit comments