/// <summary> /// Invokes the Steps given Background, Scenario, and TearDown steps. /// </summary> /// <param name="backgroundSteps"></param> /// <param name="scenarioSteps"></param> /// <param name="tearDownSteps"></param> /// <returns></returns> private async Task <RunSummary> InvokeStepsAsync(ICollection <IStepDefinition> backgroundSteps , ICollection <IStepDefinition> scenarioSteps, ICollection <IStepDefinition> tearDownSteps) { var scenarioTypeInfo = this._scenarioClass.GetTypeInfo(); var filters = scenarioTypeInfo.Assembly.GetCustomAttributes(typeof(Attribute)) .Concat(scenarioTypeInfo.GetCustomAttributes(typeof(Attribute))) .Concat(this._scenarioMethod.GetCustomAttributes(typeof(Attribute))) .OfType <IFilter <IStepDefinition> >(); var summary = new RunSummary(); string skipReason = null; var scenarioRollbacks = new List <(StepContext context, Func <IStepContext, Task> callback)>(); var stepNumber = 0; foreach (var stepDefinition in filters.Aggregate(backgroundSteps.Concat(scenarioSteps).Concat(tearDownSteps) , (current, filter) => filter.Filter(current))) { stepDefinition.SkipReason = stepDefinition.SkipReason ?? skipReason; var stepDisplayName = GetStepDisplayName(this._scenario.DisplayName , ++stepNumber , stepDefinition.OnDisplayText?.Invoke( stepDefinition.Text, stepDefinition.StepDefinitionType) ); var step = new StepTest(this._scenario, stepDisplayName); // #1 MWP 2020-07-01 11:56:45 AM: After testing, this should be fine, and better behaved we think. using (var interceptingBus = new DelegatingMessageBus( this._messageBus , message => { if (message is ITestFailed && stepDefinition.FailureBehavior == RemainingSteps.Skip) { skipReason = $"Failed to execute preceding step: {step.DisplayName}"; } }) ) { var stepContext = new StepContext(step); // TODO: TBD: #1 MWP 2020-07-01 11:57:26 AM: it is an xUnit thing, could possibly be IDisposable itself... // TODO: TBD: including assumed ownership of the IDisposable messageBus, for instance, TODO: TBD: but that is outside the scope of xWellBehaved... var stepRunner = new StepTestRunner( stepContext , stepDefinition.Body , step , interceptingBus , this._scenarioClass , this._constructorArguments , this._scenarioMethod , this._scenarioMethodArguments , stepDefinition.SkipReason , Array.Empty <BeforeAfterTestAttribute>() , new ExceptionAggregator(this._aggregator) , this._cancellationTokenSource); summary.Aggregate(await stepRunner.RunAsync()); // TODO: TBD: could we use disposable?.Dispose() here? var stepRollbacks = stepContext.Disposables .Where(disposable => disposable != null) .Select((Func <IDisposable, Func <IStepContext, Task> >)(disposable => context => { disposable.Dispose(); return(Task.FromResult(0)); })) .Concat(stepDefinition.Rollbacks) .Where(onRollback => onRollback != null) .Select(onRollback => (stepContext, onRollback)); scenarioRollbacks.AddRange(stepRollbacks); } } if (scenarioRollbacks.Any()) { scenarioRollbacks.Reverse(); var rollbackTimer = new ExecutionTimer(); var rollbackAggregator = new ExceptionAggregator(); // "Teardowns" not to be confused with TearDown versus Background. foreach (var(context, onRollback) in scenarioRollbacks) { await Invoker.Invoke(() => onRollback.Invoke(context), rollbackAggregator, rollbackTimer); } summary.Time += rollbackTimer.Total; if (rollbackAggregator.HasExceptions) { summary.Failed++; summary.Total++; var stepDisplayName = GetStepDisplayName(this._scenario.DisplayName, ++stepNumber, $"({StepType.Rollback})"); this._messageBus.Queue(new StepTest(this._scenario, stepDisplayName) , test => new TestFailed(test, rollbackTimer.Total, null, rollbackAggregator.ToException()) , this._cancellationTokenSource); } } return(summary); }
private async Task <RunSummary> InvokeStepsAsync( ICollection <IStepDefinition> backGroundStepDefinitions, ICollection <IStepDefinition> scenarioStepDefinitions) { var scenarioTypeInfo = this.scenarioClass.GetTypeInfo(); var filters = scenarioTypeInfo.Assembly.GetCustomAttributes(typeof(Attribute)) .Concat(scenarioTypeInfo.GetCustomAttributes(typeof(Attribute))) .Concat(this.scenarioMethod.GetCustomAttributes(typeof(Attribute))) .OfType <IFilter <IStepDefinition> >(); var summary = new RunSummary(); string skipReason = null; var scenarioTeardowns = new List <Tuple <StepContext, Func <IStepContext, Task> > >(); var stepNumber = 0; foreach (var stepDefinition in filters.Aggregate( backGroundStepDefinitions.Concat(scenarioStepDefinitions), (current, filter) => filter.Filter(current))) { stepDefinition.SkipReason = stepDefinition.SkipReason ?? skipReason; var stepDisplayName = GetStepDisplayName( this.scenario.DisplayName, ++stepNumber, stepDefinition.DisplayTextFunc?.Invoke(stepDefinition.Text, stepNumber <= backGroundStepDefinitions.Count)); var step = new StepTest(this.scenario, stepDisplayName); using (var interceptingBus = new DelegatingMessageBus( this.messageBus, message => { if (message is ITestFailed && stepDefinition.FailureBehavior == RemainingSteps.Skip) { skipReason = $"Failed to execute preceding step: {step.DisplayName}"; } })) { var stepContext = new StepContext(step); var stepRunner = new StepTestRunner( stepContext, stepDefinition.Body, step, interceptingBus, this.scenarioClass, this.constructorArguments, this.scenarioMethod, this.scenarioMethodArguments, stepDefinition.SkipReason, new BeforeAfterTestAttribute[0], new ExceptionAggregator(this.aggregator), this.cancellationTokenSource); summary.Aggregate(await stepRunner.RunAsync()); var stepTeardowns = stepContext.Disposables .Where(disposable => disposable != null) .Select((Func <IDisposable, Func <IStepContext, Task> >)(disposable => context => { disposable.Dispose(); return(Task.FromResult(0)); })) .Concat(stepDefinition.Teardowns) .Where(teardown => teardown != null) .Select(teardown => Tuple.Create(stepContext, teardown)); scenarioTeardowns.AddRange(stepTeardowns); } } if (scenarioTeardowns.Any()) { scenarioTeardowns.Reverse(); var teardownTimer = new ExecutionTimer(); var teardownAggregator = new ExceptionAggregator(); foreach (var teardown in scenarioTeardowns) { await Invoker.Invoke(() => teardown.Item2(teardown.Item1), teardownAggregator, teardownTimer); } summary.Time += teardownTimer.Total; if (teardownAggregator.HasExceptions) { summary.Failed++; summary.Total++; var stepDisplayName = GetStepDisplayName(this.scenario.DisplayName, ++stepNumber, "(Teardown)"); this.messageBus.Queue( new StepTest(this.scenario, stepDisplayName), test => new TestFailed(test, teardownTimer.Total, null, teardownAggregator.ToException()), this.cancellationTokenSource); } } return(summary); }
private async Task <RunSummary> InvokeStepsAsync( ICollection <IStepDefinition> backGroundStepDefinitions, ICollection <IStepDefinition> scenarioStepDefinitions) { var filters = this.scenarioClass.Assembly.GetCustomAttributes(typeof(Attribute)) .Concat(this.scenarioClass.GetCustomAttributes(typeof(Attribute))) .Concat(this.scenarioMethod.GetCustomAttributes(typeof(Attribute))) .OfType <IFilter <IStepDefinition> >(); var stepDefinitions = filters .Aggregate( backGroundStepDefinitions.Concat(scenarioStepDefinitions), (current, filter) => filter.Filter(current)) .ToArray(); var summary = new RunSummary(); string skipReason = null; var teardowns = new List <Action>(); var stepNumber = 0; foreach (var stepDefinition in stepDefinitions) { stepDefinition.SkipReason = stepDefinition.SkipReason ?? skipReason; var stepDisplayName = GetStepDisplayName( this.scenario.DisplayName, ++stepNumber, stepNumber <= backGroundStepDefinitions.Count, stepDefinition.Text, this.scenarioMethodArguments); var step = new Step(this.scenario, stepDisplayName); var interceptingBus = new DelegatingMessageBus( this.messageBus, message => { if (message is ITestFailed && stepDefinition.FailureBehavior == RemainingSteps.Skip) { skipReason = string.Format( CultureInfo.InvariantCulture, "Failed to execute preceding step: {0}", step.DisplayName); } }); var stepRunner = new StepRunner( step, stepDefinition.Body, interceptingBus, this.scenarioClass, this.constructorArguments, this.scenarioMethod, this.scenarioMethodArguments, stepDefinition.SkipReason, new ExceptionAggregator(this.aggregator), this.cancellationTokenSource); summary.Aggregate(await stepRunner.RunAsync()); teardowns.AddRange(stepRunner.Disposables.Select(disposable => (Action)disposable.Dispose) .Concat(stepDefinition.Teardowns.Where(teardown => teardown != null)).ToArray()); } if (teardowns.Any()) { teardowns.Reverse(); var teardownTimer = new ExecutionTimer(); var teardownAggregator = new ExceptionAggregator(); foreach (var teardown in teardowns) { teardownTimer.Aggregate(() => teardownAggregator.Run(() => teardown())); } summary.Time += teardownTimer.Total; if (teardownAggregator.HasExceptions) { summary.Failed++; summary.Total++; var stepDisplayName = GetStepDisplayName( this.scenario.DisplayName, ++stepNumber, false, "(Teardown)", this.scenarioMethodArguments); this.messageBus.Queue( new Step(this.scenario, stepDisplayName), test => new TestFailed(test, teardownTimer.Total, null, teardownAggregator.ToException()), this.cancellationTokenSource); } } return(summary); }