CompositeWorkItem
A CompositeWorkItem represents a test suite and
encapsulates the execution of the suite as well
as all its child tests.
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal.Commands;
using NUnit.Framework.Internal.Extensions;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
namespace NUnit.Framework.Internal.Execution
{
[NullableContext(1)]
[Nullable(0)]
public class CompositeWorkItem : WorkItem
{
[Nullable(0)]
public class OneTimeTearDownWorkItem : WorkItem
{
private readonly CompositeWorkItem _originalWorkItem;
private readonly object _teardownLock = new object();
public override string Name => base.Name + " OneTimeTearDown";
public override ParallelExecutionStrategy ExecutionStrategy => _originalWorkItem.ExecutionStrategy;
public OneTimeTearDownWorkItem(CompositeWorkItem originalItem)
: base(originalItem)
{
_originalWorkItem = originalItem;
}
public override void Execute()
{
lock (_teardownLock) {
if (base.Test.TestType == "Theory" && base.Result.ResultState == ResultState.Success && base.Result.PassCount == 0)
base.Result.SetResult(ResultState.Failure, "No test cases were provided");
if (base.Context.ExecutionStatus != TestExecutionStatus.AbortRequested)
_originalWorkItem.PerformOneTimeTearDown();
foreach (ITestResult child in base.Result.Children) {
if (child.ResultState == ResultState.Cancelled) {
base.Result.SetResult(ResultState.Cancelled, "Cancelled by user");
break;
}
}
_originalWorkItem.WorkItemComplete();
}
}
protected override void PerformWork()
{
}
internal void WorkItemCancelled()
{
base.Result.SetResult(ResultState.Cancelled, "Test run cancelled by user");
_originalWorkItem.WorkItemComplete();
}
}
private readonly TestSuite _suite;
private readonly TestSuiteResult _suiteResult;
[Nullable(2)]
private TestCommand _setupCommand;
[Nullable(2)]
private TestCommand _teardownCommand;
[Nullable(2)]
private CountdownEvent _childTestCountdown;
private readonly object _childCompletionLock = new object();
private readonly object _cancelLock = new object();
public List<WorkItem> Children { get; } = new List<WorkItem>();
public override bool IsolateChildTests {
get {
if (ExecutionStrategy == ParallelExecutionStrategy.NonParallel)
return base.Context.Dispatcher.LevelOfParallelism > 0;
return false;
}
}
public CompositeWorkItem(TestSuite suite, ITestFilter childFilter)
: base(suite, childFilter)
{
_suite = suite;
_suiteResult = (TestSuiteResult)base.Result;
}
protected override void PerformWork()
{
if (!CheckForCancellation()) {
if (base.Test.RunState != RunState.Explicit || base.Filter.IsExplicitMatch(base.Test)) {
switch (base.Test.RunState) {
default:
base.Result.SetResult(ResultState.Success);
if (Children.Count > 0) {
InitializeSetUpAndTearDownCommands();
PerformOneTimeSetUp();
if (!CheckForCancellation()) {
switch (base.Result.ResultState.Status) {
case TestStatus.Passed:
case TestStatus.Warning:
RunChildren();
return;
case TestStatus.Inconclusive:
case TestStatus.Skipped:
case TestStatus.Failed:
SkipChildren(this, base.Result.ResultState.WithSite(FailureSite.Parent), "OneTimeSetUp: " + base.Result.Message, base.Result.StackTrace);
break;
}
}
if (base.Context.ExecutionStatus != TestExecutionStatus.AbortRequested)
PerformOneTimeTearDown();
} else if (base.Test.TestType == "Theory") {
base.Result.SetResult(ResultState.Failure, "No test cases were provided");
}
break;
case RunState.Skipped:
SkipFixture(ResultState.Skipped, GetSkipReason(), null);
break;
case RunState.Ignored:
SkipFixture(ResultState.Ignored, GetSkipReason(), null);
break;
case RunState.NotRunnable:
SkipFixture(ResultState.NotRunnable, GetSkipReason(), GetProviderStackTrace());
break;
}
} else
SkipFixture(ResultState.Explicit, GetSkipReason(), null);
}
WorkItemComplete();
}
private bool CheckForCancellation()
{
if (base.Context.ExecutionStatus != 0) {
base.Result.SetResult(ResultState.Cancelled, "Test cancelled by user");
return true;
}
return false;
}
private void InitializeSetUpAndTearDownCommands()
{
StaticMethodValidator methodValidator = base.Test.HasLifeCycle(LifeCycle.InstancePerTestCase) ? new StaticMethodValidator("Only static OneTimeSetUp and OneTimeTearDown are allowed for InstancePerTestCase mode.") : null;
List<SetUpTearDownItem> list = BuildSetUpTearDownList(_suite.OneTimeSetUpMethods, _suite.OneTimeTearDownMethods, methodValidator);
List<TestActionItem> list2 = new List<TestActionItem>();
ITestAction[] actions = base.Test.Actions;
foreach (ITestAction testAction in actions) {
bool flag = testAction.Targets.HasFlag(ActionTargets.Suite) || (testAction.Targets == ActionTargets.Default && !(base.Test is ParameterizedMethodSuite));
bool flag2 = testAction.Targets.HasFlag(ActionTargets.Test) && !(base.Test is ParameterizedMethodSuite);
if (flag)
list2.Add(new TestActionItem(testAction));
if (flag2)
base.Context.UpstreamActions.Add(testAction);
}
_setupCommand = MakeOneTimeSetUpCommand(list, list2);
_teardownCommand = MakeOneTimeTearDownCommand(list, list2);
}
private TestCommand MakeOneTimeSetUpCommand(List<SetUpTearDownItem> setUpTearDown, List<TestActionItem> actions)
{
TestCommand testCommand = new EmptyTestCommand(base.Test);
int num = actions.Count;
while (--num >= 0) {
testCommand = new BeforeTestActionCommand(testCommand, actions[num]);
}
if (base.Test.TypeInfo != null) {
foreach (SetUpTearDownItem item in setUpTearDown) {
testCommand = new OneTimeSetUpCommand(testCommand, item);
}
if (!base.Test.TypeInfo.IsStaticClass && !base.Test.HasLifeCycle(LifeCycle.InstancePerTestCase))
testCommand = new ConstructFixtureCommand(testCommand);
}
IApplyToContext[] customAttributes = base.Test.GetCustomAttributes<IApplyToContext>(true);
foreach (IApplyToContext change in customAttributes) {
testCommand = new ApplyChangesToContextCommand(testCommand, change);
}
return testCommand;
}
private TestCommand MakeOneTimeTearDownCommand(List<SetUpTearDownItem> setUpTearDownItems, List<TestActionItem> actions)
{
TestCommand testCommand = new EmptyTestCommand(base.Test);
if (base.Test.TestType == "Theory")
testCommand = new TheoryResultCommand(testCommand);
int num = actions.Count;
while (--num >= 0) {
testCommand = new AfterTestActionCommand(testCommand, actions[num]);
}
foreach (SetUpTearDownItem setUpTearDownItem in setUpTearDownItems) {
testCommand = new OneTimeTearDownCommand(testCommand, setUpTearDownItem);
}
if (base.Test is IDisposableFixture && base.Test.TypeInfo != null && DisposeHelper.IsDisposable(base.Test.TypeInfo.Type) && !base.Test.HasLifeCycle(LifeCycle.InstancePerTestCase))
testCommand = new DisposeFixtureCommand(testCommand);
return testCommand;
}
private void PerformOneTimeSetUp()
{
try {
_setupCommand?.Execute(base.Context);
base.Context.UpdateContextFromEnvironment();
} catch (Exception exception) {
base.Result.RecordException(exception.Unwrap(), FailureSite.SetUp);
}
}
private void RunChildren()
{
if (base.Test.TestType == "Theory")
base.Result.SetResult(ResultState.Inconclusive);
int num = Children.Count;
if (num == 0)
throw new InvalidOperationException("RunChildren called but item has no children");
_childTestCountdown = new CountdownEvent(num);
foreach (WorkItem child in Children) {
if (CheckForCancellation())
break;
child.Completed += OnChildItemCompleted;
child.InitializeContext(new TestExecutionContext(base.Context));
child.TestWorker = base.TestWorker;
base.Context.Dispatcher.Dispatch(child);
num--;
}
if (num > 0) {
lock (_childCompletionLock) {
_childTestCountdown.Signal(num);
if (_childTestCountdown.CurrentCount == 0)
OnAllChildItemsCompleted();
}
}
}
private void SkipFixture(ResultState resultState, string message, [Nullable(2)] string stackTrace)
{
base.Result.SetResult(resultState.WithSite(FailureSite.SetUp), message, StackFilter.DefaultFilter.Filter(stackTrace));
SkipChildren(this, resultState.WithSite(FailureSite.Parent), "OneTimeSetUp: " + message, stackTrace);
}
private void SkipChildren(CompositeWorkItem workItem, ResultState resultState, string message, [Nullable(2)] string stackTrace)
{
foreach (WorkItem child in workItem.Children) {
SetChildWorkItemSkippedResult(child.Result, resultState, message, stackTrace);
_suiteResult.AddResult(child.Result);
base.Context.Listener.TestFinished(child.Result);
CompositeWorkItem compositeWorkItem = child as CompositeWorkItem;
if (compositeWorkItem != null)
SkipChildren(compositeWorkItem, resultState, message, stackTrace);
}
}
private void SetChildWorkItemSkippedResult(TestResult result, ResultState resultState, string message, [Nullable(2)] string stackTrace)
{
result.SetResult(resultState, message, stackTrace);
result.StartTime = base.Context.StartTime;
result.EndTime = DateTime.UtcNow;
result.Duration = base.Context.Duration;
}
private void PerformOneTimeTearDown()
{
base.Context.EstablishExecutionEnvironment();
_teardownCommand?.Execute(base.Context);
}
private string GetSkipReason()
{
return ((string)base.Test.Properties.Get("_SKIPREASON")) ?? string.Empty;
}
[NullableContext(2)]
private string GetProviderStackTrace()
{
return (string)base.Test.Properties.Get("_PROVIDERSTACKTRACE");
}
private void OnChildItemCompleted([Nullable(2)] object sender, EventArgs e)
{
lock (_childCompletionLock) {
WorkItem workItem = sender as WorkItem;
if (workItem != null) {
workItem.Completed -= OnChildItemCompleted;
_suiteResult.AddResult(workItem.Result);
if (base.Context.StopOnError && workItem.Result.ResultState.Status == TestStatus.Failed)
base.Context.ExecutionStatus = TestExecutionStatus.StopRequested;
_childTestCountdown.Signal();
if (_childTestCountdown.CurrentCount == 0)
OnAllChildItemsCompleted();
}
}
}
private void OnAllChildItemsCompleted()
{
if (base.Context.ExecutionStatus == TestExecutionStatus.AbortRequested)
WorkItemComplete();
else
base.Context.Dispatcher.Dispatch(new OneTimeTearDownWorkItem(this));
}
public override void Cancel(bool force)
{
lock (_cancelLock) {
foreach (WorkItem child in Children) {
TestExecutionContext context = child.Context;
if (context != null)
context.ExecutionStatus = ((!force) ? TestExecutionStatus.StopRequested : TestExecutionStatus.AbortRequested);
if (child.State == WorkItemState.Running)
child.Cancel(force);
}
}
}
}
}