private async Task<IDisposable[]> InvokeBodyAsync() { var stepContext = new StepContext(this.step); if (this.body != null) { var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(oldSyncContext); SetSynchronizationContext(asyncSyncContext); await this.aggregator.RunAsync( () => this.timer.AggregateAsync( async () => { await this.body(stepContext); var ex = await asyncSyncContext.WaitForCompletionAsync(); if (ex != null) { this.aggregator.Add(ex); } })); } finally { SetSynchronizationContext(oldSyncContext); } } return stepContext.Disposables.ToArray(); }
public override void Execute() { var teardowns = Enumerable.Empty<Action>(); try { Exception exception = null; var @event = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(o => { var oldSyncContext = SynchronizationContext.Current; using (var syncContext = new AsyncTestSyncContext()) { SynchronizationContext.SetSynchronizationContext(syncContext); try { this.body.Invoke(); exception = syncContext.WaitForCompletion(); } catch (Exception ex) { exception = ex; } finally { SynchronizationContext.SetSynchronizationContext(oldSyncContext); @event.Set(); } } }); // NOTE: we do not call the WaitOne(int) overload because it wasn't introduced until .NET 3.5 SP1 and we want to support pre-SP1 if ([email protected](this.MillisecondsTimeout, false)) { throw new Xunit.Sdk.TimeoutException(this.MillisecondsTimeout); } if (exception != null) { ExceptionUtility.RethrowWithNoStackTraceLoss(exception); } } finally { foreach (var disposable in this.ExtractDisposables) { CurrentScenario.AddTeardown(() => disposable.Dispose()); } foreach (var teardown in this.Teardowns) { CurrentScenario.AddTeardown(teardown); } } }
/// <summary> /// Invokes the test method on the given test class instance. This method sets up support for "async void" /// test methods, ensures that the test method has the correct number of arguments, then calls <see cref="CallTestMethod"/> /// to do the actual method invocation. It ensure that any async test method is fully completed before returning, and /// returns the measured clock time that the invocation took. /// </summary> /// <param name="testClassInstance">The test class instance</param> /// <returns>Returns the time taken to invoke the test method</returns> protected virtual async Task <decimal> InvokeTestMethodAsync(object testClassInstance) { var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(oldSyncContext); SetSynchronizationContext(asyncSyncContext); await Aggregator.RunAsync( () => Timer.AggregateAsync( async() => { var parameterCount = TestMethod.GetParameters().Length; var valueCount = TestMethodArguments == null ? 0 : TestMethodArguments.Length; if (parameterCount != valueCount) { Aggregator.Add( new InvalidOperationException( $"The test method expected {parameterCount} parameter value{(parameterCount == 1 ? "" : "s")}, but {valueCount} parameter value{(valueCount == 1 ? "" : "s")} {(valueCount == 1 ? "was" : "were")} provided." ) ); } else { var result = CallTestMethod(testClassInstance); var task = GetTaskFromResult(result); if (task != null) { if (task.Status == TaskStatus.Created) { throw new InvalidOperationException("Test method returned a non-started Task (tasks must be started before being returned)"); } await task; } else { var ex = await asyncSyncContext.WaitForCompletionAsync(); if (ex != null) { Aggregator.Add(ex); } } } } ) ); } finally { SetSynchronizationContext(oldSyncContext); } return(Timer.Total); }
/// <summary> /// Invokes the test method on the given test class instance. /// </summary> /// <param name="testClassInstance">The test class instance</param> /// <returns>Returns the time taken to invoke the test method</returns> public virtual async Task <decimal> InvokeTestMethodAsync(object testClassInstance) { var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(oldSyncContext); SetSynchronizationContext(asyncSyncContext); await Aggregator.RunAsync( () => Timer.AggregateAsync( async() => { var parameterCount = TestMethod.GetParameters().Length; var valueCount = TestMethodArguments == null ? 0 : TestMethodArguments.Length; if (parameterCount != valueCount) { Aggregator.Add( new InvalidOperationException( String.Format("The test method expected {0} parameter value{1}, but {2} parameter value{3} {4} provided.", parameterCount, parameterCount == 1 ? "" : "s", valueCount, valueCount == 1 ? "" : "s", valueCount == 1 ? "was" : "were")) ); } else { var result = TestMethod.Invoke(testClassInstance, TestMethodArguments); var task = result as Task; if (task != null) { await task; } else { var ex = await asyncSyncContext.WaitForCompletionAsync(); if (ex != null) { Aggregator.Add(ex); } } } } ) ); } finally { SetSynchronizationContext(oldSyncContext); } return(Timer.Total); }
/// <summary> /// Invokes the test method on the given test class instance. /// </summary> /// <param name="testClassInstance">The test class instance</param> /// <returns>Returns the time taken to invoke the test method</returns> public virtual async Task <decimal> InvokeTestMethodAsync(object testClassInstance) { var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(oldSyncContext); SetSynchronizationContext(asyncSyncContext); await Aggregator.RunAsync( () => Timer.AggregateAsync( async() => { var result = TestMethod.Invoke(testClassInstance, TestMethodArguments); var task = result as Task; if (task != null) { await task; } else { var ex = await asyncSyncContext.WaitForCompletionAsync(); if (ex != null) { Aggregator.Add(ex); } } } ) ); } finally { SetSynchronizationContext(oldSyncContext); } return(Timer.Total); }
/// <summary> /// For unit testing purposes only. /// </summary> protected virtual async Task InvokeTestMethodAsync(object testClassInstance) { var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(); SetSynchronizationContext(asyncSyncContext); await Aggregator.RunAsync( () => Timer.AggregateAsync( async() => { var result = TestMethod.Invoke(testClassInstance, TestMethodArguments); var task = result as Task; if (task != null) { await task.ConfigureAwait(false); } else { var ex = await asyncSyncContext.WaitForCompletionAsync().ConfigureAwait(false); if (ex != null) { Aggregator.Add(ex); } } } ) ); // Don't use configure await false here, as we need to be on the orig context here to reset in the finally } finally { SetSynchronizationContext(oldSyncContext); } }
/// <summary> /// Runs a single test for a given test method. /// </summary> /// <param name="messageSink">The message sink to send results to.</param> /// <param name="classUnderTest">The class under test.</param> /// <param name="constructorArguments">The arguments to pass to the constructor.</param> /// <param name="methodUnderTest">The method under test.</param> /// <param name="testMethodArguments">The arguments to pass to the test method.</param> /// <param name="displayName">The display name for the test.</param> /// <param name="beforeAfterAttributes">The <see cref="BeforeAfterTestAttribute"/> instances attached to the test.</param> /// <param name="parentAggregator">The parent aggregator that contains the exceptions up to this point.</param> /// <param name="cancellationTokenSource">The cancellation token source that indicates whether cancellation has been requested.</param> /// <param name="executionTime">The time spent executing the tests.</param> protected void RunTestWithArguments(IMessageSink messageSink, Type classUnderTest, object[] constructorArguments, MethodInfo methodUnderTest, object[] testMethodArguments, string displayName, List<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator parentAggregator, CancellationTokenSource cancellationTokenSource, ref decimal executionTime) { var aggregator = new ExceptionAggregator(parentAggregator); if (!messageSink.OnMessage(new TestStarting(this, displayName))) cancellationTokenSource.Cancel(); else { if (!String.IsNullOrEmpty(SkipReason)) { if (!messageSink.OnMessage(new TestSkipped(this, displayName, SkipReason))) cancellationTokenSource.Cancel(); } else { var beforeAttributesRun = new Stack<BeforeAfterTestAttribute>(); var stopwatch = Stopwatch.StartNew(); if (!aggregator.HasExceptions) aggregator.Run(() => { object testClass = null; if (!methodUnderTest.IsStatic) { if (!messageSink.OnMessage(new TestClassConstructionStarting(this, displayName))) cancellationTokenSource.Cancel(); try { if (!cancellationTokenSource.IsCancellationRequested) testClass = Activator.CreateInstance(classUnderTest, constructorArguments); } finally { if (!messageSink.OnMessage(new TestClassConstructionFinished(this, displayName))) cancellationTokenSource.Cancel(); } } if (!cancellationTokenSource.IsCancellationRequested) { aggregator.Run(() => { foreach (var beforeAfterAttribute in beforeAfterAttributes) { var attributeName = beforeAfterAttribute.GetType().Name; if (!messageSink.OnMessage(new BeforeTestStarting(this, displayName, attributeName))) cancellationTokenSource.Cancel(); else { try { beforeAfterAttribute.Before(methodUnderTest); beforeAttributesRun.Push(beforeAfterAttribute); } finally { if (!messageSink.OnMessage(new BeforeTestFinished(this, displayName, attributeName))) cancellationTokenSource.Cancel(); } } if (cancellationTokenSource.IsCancellationRequested) return; } if (!cancellationTokenSource.IsCancellationRequested) { var parameterTypes = methodUnderTest.GetParameters().Select(p => p.ParameterType).ToArray(); var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(); SetSynchronizationContext(asyncSyncContext); aggregator.Run(() => { var result = methodUnderTest.Invoke(testClass, ConvertArguments(testMethodArguments ?? EmptyArray, parameterTypes)); var task = result as Task; if (task != null) task.GetAwaiter().GetResult(); else { var ex = asyncSyncContext.WaitForCompletion(); if (ex != null) aggregator.Add(ex); } }); } finally { SetSynchronizationContext(oldSyncContext); } } }); foreach (var beforeAfterAttribute in beforeAttributesRun) { var attributeName = beforeAfterAttribute.GetType().Name; if (!messageSink.OnMessage(new AfterTestStarting(this, displayName, attributeName))) cancellationTokenSource.Cancel(); aggregator.Run(() => beforeAfterAttribute.After(methodUnderTest)); if (!messageSink.OnMessage(new AfterTestFinished(this, displayName, attributeName))) cancellationTokenSource.Cancel(); } } aggregator.Run(() => { IDisposable disposable = testClass as IDisposable; if (disposable != null) { if (!messageSink.OnMessage(new TestClassDisposeStarting(this, displayName))) cancellationTokenSource.Cancel(); try { disposable.Dispose(); } finally { if (!messageSink.OnMessage(new TestClassDisposeFinished(this, displayName))) cancellationTokenSource.Cancel(); } } }); }); stopwatch.Stop(); if (!cancellationTokenSource.IsCancellationRequested) { executionTime = (decimal)stopwatch.Elapsed.TotalSeconds; var exception = aggregator.ToException(); var testResult = exception == null ? (TestResultMessage)new TestPassed(this, displayName, executionTime) : new TestFailed(this, displayName, executionTime, exception); if (!messageSink.OnMessage(testResult)) cancellationTokenSource.Cancel(); } } } if (!messageSink.OnMessage(new TestFinished(this, displayName, executionTime))) cancellationTokenSource.Cancel(); }
/// <inheritdoc/> protected override async Task<decimal> InvokeTestMethodAsync(object testClassInstance) { var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(oldSyncContext); SetSynchronizationContext(asyncSyncContext); Exception testException = null; await Aggregator.RunAsync( () => Timer.AggregateAsync( async () => { var parameterCount = TestMethod.GetParameters().Length; var valueCount = TestMethodArguments == null ? 0 : TestMethodArguments.Length; if (parameterCount != valueCount) { Aggregator.Add( new InvalidOperationException( $"The test method expected {parameterCount} parameter value{(parameterCount == 1 ? "" : "s")}," + $"but {valueCount} parameter value{(valueCount == 1 ? "" : "s")} {(valueCount == 1 ? "was" : "were")} provided.") ); } else { try { var result = TestMethod.Invoke(testClassInstance, TestMethodArguments); var task = result as Task; if (task != null) await task; else { var ex = await asyncSyncContext.WaitForCompletionAsync(); if (ex != null) { testException = ex; Aggregator.Add(ex); } } } catch (Exception ex) { testException = ex; throw; } } } ) ); if (testException != null) { var handleTestFailure = testClassInstance as INeedToKnowTestFailure; if (handleTestFailure != null) { await Aggregator.RunAsync( () => handleTestFailure.HandleFailureAsync(Test, testException)); } } } finally { SetSynchronizationContext(oldSyncContext); } return Timer.Total; }
/// <summary> /// Runs a single test for a given test method. /// </summary> /// <param name="messageBus">The message bus to send results to.</param> /// <param name="classUnderTest">The class under test.</param> /// <param name="constructorArguments">The arguments to pass to the constructor.</param> /// <param name="methodUnderTest">The method under test.</param> /// <param name="testMethodArguments">The arguments to pass to the test method.</param> /// <param name="displayName">The display name for the test.</param> /// <param name="beforeAfterAttributes">The <see cref="BeforeAfterTestAttribute"/> instances attached to the test.</param> /// <param name="parentAggregator">The parent aggregator that contains the exceptions up to this point.</param> /// <param name="cancellationTokenSource">The cancellation token source that indicates whether cancellation has been requested.</param> protected async Task <decimal> RunTestWithArgumentsAsync(IMessageBus messageBus, Type classUnderTest, object[] constructorArguments, MethodInfo methodUnderTest, object[] testMethodArguments, string displayName, List <BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator parentAggregator, CancellationTokenSource cancellationTokenSource) { var executionTimeInSeconds = 0.0m; var aggregator = new ExceptionAggregator(parentAggregator); var output = String.Empty; // TODO: Add output facilities for v2 if (!messageBus.QueueMessage(new TestStarting(this, displayName))) { cancellationTokenSource.Cancel(); } else { if (!String.IsNullOrEmpty(SkipReason)) { if (!messageBus.QueueMessage(new TestSkipped(this, displayName, SkipReason))) { cancellationTokenSource.Cancel(); } } else { var beforeAttributesRun = new Stack <BeforeAfterTestAttribute>(); var executionTime = new ExecutionTime(); if (!aggregator.HasExceptions) { await aggregator.RunAsync(async() => { object testClass = null; if (!methodUnderTest.IsStatic) { if (!messageBus.QueueMessage(new TestClassConstructionStarting(this, displayName))) { cancellationTokenSource.Cancel(); } try { if (!cancellationTokenSource.IsCancellationRequested) { executionTime.Aggregate(() => testClass = Activator.CreateInstance(classUnderTest, constructorArguments)); } } finally { if (!messageBus.QueueMessage(new TestClassConstructionFinished(this, displayName))) { cancellationTokenSource.Cancel(); } } } if (!cancellationTokenSource.IsCancellationRequested) { await aggregator.RunAsync(async() => { foreach (var beforeAfterAttribute in beforeAfterAttributes) { var attributeName = beforeAfterAttribute.GetType().Name; if (!messageBus.QueueMessage(new BeforeTestStarting(this, displayName, attributeName))) { cancellationTokenSource.Cancel(); } else { try { executionTime.Aggregate(() => beforeAfterAttribute.Before(methodUnderTest)); beforeAttributesRun.Push(beforeAfterAttribute); } finally { if (!messageBus.QueueMessage(new BeforeTestFinished(this, displayName, attributeName))) { cancellationTokenSource.Cancel(); } } } if (cancellationTokenSource.IsCancellationRequested) { return; } } if (!cancellationTokenSource.IsCancellationRequested) { var parameterTypes = methodUnderTest.GetParameters().Select(p => p.ParameterType).ToArray(); var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(); SetSynchronizationContext(asyncSyncContext); await aggregator.RunAsync(async() => { await executionTime.AggregateAsync(async() => { var result = methodUnderTest.Invoke(testClass, Reflector.ConvertArguments(testMethodArguments, parameterTypes)); var task = result as Task; if (task != null) { await task; } else { var ex = await asyncSyncContext.WaitForCompletionAsync(); if (ex != null) { aggregator.Add(ex); } } }); }); } finally { SetSynchronizationContext(oldSyncContext); } } }); foreach (var beforeAfterAttribute in beforeAttributesRun) { var attributeName = beforeAfterAttribute.GetType().Name; if (!messageBus.QueueMessage(new AfterTestStarting(this, displayName, attributeName))) { cancellationTokenSource.Cancel(); } aggregator.Run(() => executionTime.Aggregate(() => beforeAfterAttribute.After(methodUnderTest))); if (!messageBus.QueueMessage(new AfterTestFinished(this, displayName, attributeName))) { cancellationTokenSource.Cancel(); } } } aggregator.Run(() => { var disposable = testClass as IDisposable; if (disposable != null) { if (!messageBus.QueueMessage(new TestClassDisposeStarting(this, displayName))) { cancellationTokenSource.Cancel(); } try { executionTime.Aggregate(disposable.Dispose); } finally { if (!messageBus.QueueMessage(new TestClassDisposeFinished(this, displayName))) { cancellationTokenSource.Cancel(); } } } }); }); } if (!cancellationTokenSource.IsCancellationRequested) { executionTimeInSeconds = (decimal)executionTime.Total.TotalSeconds; var exception = aggregator.ToException(); var testResult = exception == null ? (TestResultMessage) new TestPassed(this, displayName, executionTimeInSeconds, output) : new TestFailed(this, displayName, executionTimeInSeconds, output, exception); if (!messageBus.QueueMessage(testResult)) { cancellationTokenSource.Cancel(); } } } } if (!messageBus.QueueMessage(new TestFinished(this, displayName, executionTimeInSeconds, output))) { cancellationTokenSource.Cancel(); } return(executionTimeInSeconds); }
/// <summary> /// Runs a single test for a given test method. /// </summary> /// <param name="messageBus">The message bus to send results to.</param> /// <param name="classUnderTest">The class under test.</param> /// <param name="constructorArguments">The arguments to pass to the constructor.</param> /// <param name="methodUnderTest">The method under test.</param> /// <param name="testMethodArguments">The arguments to pass to the test method.</param> /// <param name="displayName">The display name for the test.</param> /// <param name="beforeAfterAttributes">The <see cref="BeforeAfterTestAttribute"/> instances attached to the test.</param> /// <param name="parentAggregator">The parent aggregator that contains the exceptions up to this point.</param> /// <param name="cancellationTokenSource">The cancellation token source that indicates whether cancellation has been requested.</param> protected async Task<decimal> RunTestWithArgumentsAsync(IMessageBus messageBus, Type classUnderTest, object[] constructorArguments, MethodInfo methodUnderTest, object[] testMethodArguments, string displayName, List<BeforeAfterTestAttribute> beforeAfterAttributes, ExceptionAggregator parentAggregator, CancellationTokenSource cancellationTokenSource) { var executionTimeInSeconds = 0.0m; var aggregator = new ExceptionAggregator(parentAggregator); var output = String.Empty; // TODO: Add output facilities for v2 if (!messageBus.QueueMessage(new TestStarting(this, displayName))) cancellationTokenSource.Cancel(); else { if (!String.IsNullOrEmpty(SkipReason)) { if (!messageBus.QueueMessage(new TestSkipped(this, displayName, SkipReason))) cancellationTokenSource.Cancel(); } else { var beforeAttributesRun = new Stack<BeforeAfterTestAttribute>(); var executionTime = new ExecutionTime(); if (!aggregator.HasExceptions) await aggregator.RunAsync(async () => { object testClass = null; if (!methodUnderTest.IsStatic) { if (!messageBus.QueueMessage(new TestClassConstructionStarting(this, displayName))) cancellationTokenSource.Cancel(); try { if (!cancellationTokenSource.IsCancellationRequested) executionTime.Aggregate(() => testClass = Activator.CreateInstance(classUnderTest, constructorArguments)); } finally { if (!messageBus.QueueMessage(new TestClassConstructionFinished(this, displayName))) cancellationTokenSource.Cancel(); } } if (!cancellationTokenSource.IsCancellationRequested) { await aggregator.RunAsync(async () => { foreach (var beforeAfterAttribute in beforeAfterAttributes) { var attributeName = beforeAfterAttribute.GetType().Name; if (!messageBus.QueueMessage(new BeforeTestStarting(this, displayName, attributeName))) cancellationTokenSource.Cancel(); else { try { executionTime.Aggregate(() => beforeAfterAttribute.Before(methodUnderTest)); beforeAttributesRun.Push(beforeAfterAttribute); } finally { if (!messageBus.QueueMessage(new BeforeTestFinished(this, displayName, attributeName))) cancellationTokenSource.Cancel(); } } if (cancellationTokenSource.IsCancellationRequested) return; } if (!cancellationTokenSource.IsCancellationRequested) { var parameterTypes = methodUnderTest.GetParameters().Select(p => p.ParameterType).ToArray(); var oldSyncContext = SynchronizationContext.Current; try { var asyncSyncContext = new AsyncTestSyncContext(); SetSynchronizationContext(asyncSyncContext); await aggregator.RunAsync(async () => { await executionTime.AggregateAsync(async () => { var result = methodUnderTest.Invoke(testClass, Reflector.ConvertArguments(testMethodArguments, parameterTypes)); var task = result as Task; if (task != null) await task; else { var ex = await asyncSyncContext.WaitForCompletionAsync(); if (ex != null) aggregator.Add(ex); } }); }); } finally { SetSynchronizationContext(oldSyncContext); } } }); foreach (var beforeAfterAttribute in beforeAttributesRun) { var attributeName = beforeAfterAttribute.GetType().Name; if (!messageBus.QueueMessage(new AfterTestStarting(this, displayName, attributeName))) cancellationTokenSource.Cancel(); aggregator.Run(() => executionTime.Aggregate(() => beforeAfterAttribute.After(methodUnderTest))); if (!messageBus.QueueMessage(new AfterTestFinished(this, displayName, attributeName))) cancellationTokenSource.Cancel(); } } aggregator.Run(() => { var disposable = testClass as IDisposable; if (disposable != null) { if (!messageBus.QueueMessage(new TestClassDisposeStarting(this, displayName))) cancellationTokenSource.Cancel(); try { executionTime.Aggregate(disposable.Dispose); } finally { if (!messageBus.QueueMessage(new TestClassDisposeFinished(this, displayName))) cancellationTokenSource.Cancel(); } } }); }); if (!cancellationTokenSource.IsCancellationRequested) { executionTimeInSeconds = (decimal)executionTime.Total.TotalSeconds; var exception = aggregator.ToException(); var testResult = exception == null ? (TestResultMessage)new TestPassed(this, displayName, executionTimeInSeconds, output) : new TestFailed(this, displayName, executionTimeInSeconds, output, exception); if (!messageBus.QueueMessage(testResult)) cancellationTokenSource.Cancel(); } } } if (!messageBus.QueueMessage(new TestFinished(this, displayName, executionTimeInSeconds, output))) cancellationTokenSource.Cancel(); return executionTimeInSeconds; }