async Task <StepExecutionState> IProgressStepOperation.RunAsync(CancellationToken cancellationToken, IProgressStepExecutionEvents progressCallback) { if (this.ExecutionState != StepExecutionState.NotStarted) { throw new InvalidOperationException(ProgressResources.StepOperationWasAlreadyExecuted); } if (this.Cancellable && cancellationToken.IsCancellationRequested) { return(this.ExecutionState = StepExecutionState.Cancelled); } VsTaskRunContext context = GetContext(this.Execution); StepExecutionState stepState = await VsThreadingHelper.RunTaskAsync <StepExecutionState>(this.controller, context, () => { DoStatefulExecution(progressCallback, cancellationToken); return(this.ExecutionState); }, cancellationToken); return(stepState); }
/// <summary> /// The <see cref="ProgressControllerStep"/> which are used by default will swallow the assert exceptions /// which means that investigating why something is failing requires more time and effort. /// This extension method will record the first <see cref="UnitTestAssertException"/> which was thrown during /// execution and will rethrow it on a way that will allow the test to fail and see the original stack /// that caused the test failure (on Finished event) /// </summary> /// <param name="controller">The controller to configure</param> /// <returns>The notifier that was used for configuration of the assert exception</returns> public static ConfigurableErrorNotifier ConfigureToThrowAssertExceptions(SequentialProgressController controller) { controller.Should().NotBeNull("Controller argument is required"); controller.Steps.Should().NotBeNull("Controller needs to be initialized"); ConfigurableErrorNotifier errorHandler = new ConfigurableErrorNotifier(); controller.ErrorNotificationManager.AddNotifier(errorHandler); UnitTestAssertException originalException = null; // Controller.Finished is executed out of the awaitable state machine and on the calling (UI) thread // which means that at this point the test runtime engine will be able to catch it and fail the test EventHandler <ProgressControllerFinishedEventArgs> onFinished = null; onFinished = (s, e) => { // Need to register on the UI thread VsThreadingHelper.RunTaskAsync(controller, Microsoft.VisualStudio.Shell.VsTaskRunContext.UIThreadNormalPriority, () => { controller.Finished -= onFinished; }).Wait(); // Satisfy the sequential controller verification code e.Handled(); if (originalException != null) { e.Result.Should().Be(ProgressControllerResult.Failed, "Expected to be failed since the assert failed which causes an exception"); throw new RestoredUnitTestAssertException(originalException.Message, originalException); } }; // Need to register on the UI thread VsThreadingHelper.RunTaskAsync(controller, Microsoft.VisualStudio.Shell.VsTaskRunContext.UIThreadNormalPriority, () => { controller.Finished += onFinished; }).Wait(); errorHandler.NotifyAction = (e) => { // Only the first one if (originalException == null) { originalException = e as UnitTestAssertException; } }; return(errorHandler); }
/// <summary> /// Starts executing the initialized steps. /// The method is not thread safe but can be called from any thread. /// </summary> /// <returns>An await-able</returns> public async TPL.Task <ProgressControllerResult> StartAsync() { if (this.IsStarted) { throw new InvalidOperationException(ProgressResources.AlreadyStartedException); } this.OnStarted(); ProgressControllerResult controllerResult = await VsThreadingHelper.RunTaskAsync <ProgressControllerResult>(this, VsTaskRunContext.BackgroundThread, () => { ThreadHelper.ThrowIfOnUIThread(); // By default can abort, the individual step may changed that this.CanAbort = true; ProgressControllerResult result = ProgressControllerResult.Cancelled; foreach (IProgressStepOperation operation in this.progressStepOperations) { // Try to cancel (in case the step itself will not cancel itself) if (this.cancellationTokenSource.IsCancellationRequested) { result = ProgressControllerResult.Cancelled; break; } this.CanAbort = operation.Step.Cancellable; IProgressStepExecutionEvents notifier = this.stepFactory.GetExecutionCallback(operation); // Give another try before running the operation (there's a test that covers cancellation // before running the operation which requires this check after CanAbort is set) if (this.cancellationTokenSource.IsCancellationRequested) { result = ProgressControllerResult.Cancelled; break; } StepExecutionState stepResult = operation.RunAsync(this.cancellationTokenSource.Token, notifier).Result; /* Not trying to cancel here intentionally. The reason being * in case the step was the last one, there's nothing to cancel really, * otherwise there will be an attempt to cancel just before the next * step execution*/ if (stepResult == StepExecutionState.Succeeded) { result = ProgressControllerResult.Succeeded; } else if (stepResult == StepExecutionState.Failed) { result = ProgressControllerResult.Failed; break; } else if (stepResult == StepExecutionState.Cancelled) { result = ProgressControllerResult.Cancelled; break; } else { Debug.Fail("Unexpected step execution result:" + stepResult); } } return(result); }, this.cancellationTokenSource.Token); this.OnFinished(controllerResult); return(controllerResult); }