WorkItem
A WorkItem may be an individual test case, a fixture or
a higher level grouping of tests. All WorkItems inherit
from the abstract WorkItem class, which uses the template
pattern to allow derived classes to perform work in
whatever way is needed.
A WorkItem is created with a particular TestExecutionContext
and is responsible for re-establishing that context in the
current thread before it begins or resumes execution.
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal.Commands;
using NUnit.Framework.Internal.Extensions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace NUnit.Framework.Internal.Execution
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public abstract class WorkItem : IDisposable
{
private static readonly Logger Log = InternalTrace.GetLogger("WorkItem");
private ParallelExecutionStrategy? _executionStrategy;
private readonly ManualResetEventSlim _completionEvent = new ManualResetEventSlim();
[System.Runtime.CompilerServices.Nullable(2)]
private Thread _thread;
public WorkItemState State { get; set; }
public Test Test { get; }
public virtual string Name => Test.Name;
public ITestFilter Filter { get; }
public TestExecutionContext Context { get; set; }
[System.Runtime.CompilerServices.Nullable(2)]
[field: System.Runtime.CompilerServices.Nullable(2)]
public TestWorker TestWorker {
[System.Runtime.CompilerServices.NullableContext(2)]
get;
[System.Runtime.CompilerServices.NullableContext(2)]
internal set;
}
public virtual ParallelExecutionStrategy ExecutionStrategy {
get {
if (!_executionStrategy.HasValue)
_executionStrategy = GetExecutionStrategy();
return _executionStrategy.Value;
}
}
public virtual bool IsolateChildTests { get; }
public TestResult Result { get; set; }
public ParallelScope ParallelScope { get; }
internal ApartmentState TargetApartment { get; set; }
private ApartmentState CurrentApartment { get; set; }
[System.Runtime.CompilerServices.Nullable(2)]
[method: System.Runtime.CompilerServices.NullableContext(2)]
[field: System.Runtime.CompilerServices.Nullable(2)]
public event EventHandler Completed;
public WorkItem(Test test, ITestFilter filter)
{
Test = test;
Filter = filter;
Result = test.MakeTestResult();
State = WorkItemState.Ready;
ParallelScope = Test.Properties.TryGet("ParallelScope", ParallelScope.Default);
TargetApartment = GetTargetApartment(Test);
State = WorkItemState.Ready;
Context = null;
}
public WorkItem(WorkItem wrappedItem)
{
Test = wrappedItem.Test;
Result = wrappedItem.Result;
Filter = wrappedItem.Filter;
Context = wrappedItem.Context;
ParallelScope = wrappedItem.ParallelScope;
TestWorker = wrappedItem.TestWorker;
TargetApartment = wrappedItem.TargetApartment;
State = WorkItemState.Ready;
}
public void InitializeContext(TestExecutionContext context)
{
Guard.OperationValid(Context == null, "The context has already been initialized");
Context = context;
}
public virtual void Execute()
{
Guard.OperationValid(Context != null, "Context must be set by InitializeContext");
CurrentApartment = Thread.CurrentThread.GetApartmentState();
ApartmentState apartmentState = (TargetApartment == ApartmentState.Unknown) ? CurrentApartment : TargetApartment;
bool flag = apartmentState != CurrentApartment;
if (Test.RequiresThread | flag) {
if (Context.IsSingleThreaded) {
string message = Test.RequiresThread ? "RequiresThreadAttribute may not be specified on a test within a single-SingleThreadedAttribute fixture." : "Tests in a single-threaded fixture may not specify a different apartment";
Log.Error(message);
Result.SetResult(ResultState.NotRunnable, message);
WorkItemComplete();
} else {
Log.Debug("Running on separate thread because {0} is specified.", Test.RequiresThread ? "RequiresThread" : "different Apartment");
RunOnSeparateThread(apartmentState);
}
} else
RunOnCurrentThread();
}
public void WaitForCompletion()
{
_completionEvent.Wait();
}
public void MarkNotRunnable(string reason)
{
Result.SetResult(ResultState.NotRunnable, reason);
WorkItemComplete();
}
public virtual void Cancel(bool force)
{
if (Context != null)
Context.ExecutionStatus = ((!force) ? TestExecutionStatus.StopRequested : TestExecutionStatus.AbortRequested);
}
public void Dispose()
{
_completionEvent?.Dispose();
}
protected abstract void PerformWork();
protected void WorkItemComplete()
{
State = WorkItemState.Complete;
Result.StartTime = Context.StartTime;
Result.EndTime = DateTime.UtcNow;
Result.Duration = Context.Duration;
Result.AssertCount += Context.AssertCount;
Context.Listener.TestFinished(Result);
this.Completed?.Invoke(this, EventArgs.Empty);
_completionEvent.Set();
Context.TestObject = null;
Test.Fixture = null;
}
protected List<SetUpTearDownItem> BuildSetUpTearDownList(IMethodInfo[] setUpMethods, IMethodInfo[] tearDownMethods, [System.Runtime.CompilerServices.Nullable(2)] IMethodValidator methodValidator = null)
{
Guard.ArgumentNotNull(setUpMethods, "setUpMethods");
Guard.ArgumentNotNull(tearDownMethods, "tearDownMethods");
List<SetUpTearDownItem> list = new List<SetUpTearDownItem>();
Type type = Test.TypeInfo?.Type;
if ((object)type == null)
return list;
while ((object)type != null && type != typeof(object)) {
SetUpTearDownItem setUpTearDownItem = BuildNode(type, setUpMethods, tearDownMethods, methodValidator);
if (setUpTearDownItem.HasMethods)
list.Add(setUpTearDownItem);
type = type.BaseType;
}
return list;
}
private static SetUpTearDownItem BuildNode(Type fixtureType, IList<IMethodInfo> setUpMethods, IList<IMethodInfo> tearDownMethods, [System.Runtime.CompilerServices.Nullable(2)] IMethodValidator methodValidator)
{
List<IMethodInfo> setUpMethods2 = SelectMethodsByDeclaringType(fixtureType, setUpMethods);
List<IMethodInfo> tearDownMethods2 = SelectMethodsByDeclaringType(fixtureType, tearDownMethods);
return new SetUpTearDownItem(setUpMethods2, tearDownMethods2, methodValidator);
}
private static List<IMethodInfo> SelectMethodsByDeclaringType(Type type, IList<IMethodInfo> methods)
{
List<IMethodInfo> list = new List<IMethodInfo>();
foreach (IMethodInfo method in methods) {
if (method.TypeInfo.Type == type)
list.Add(method);
}
return list;
}
protected void ChangeResult(ResultState resultState, string message)
{
Log.Debug("Changing result from {0} to {1}", Result.ResultState, resultState);
Result.SetResult(resultState, message);
}
private void RunOnSeparateThread(ApartmentState apartment)
{
_thread = new Thread((ThreadStart)delegate {
Thread.CurrentThread.CurrentCulture = Context.CurrentCulture;
Thread.CurrentThread.CurrentUICulture = Context.CurrentUICulture;
RunOnCurrentThread();
});
if (OperatingSystem.IsWindows()) {
_thread.SetApartmentState(apartment);
_thread.Start();
_thread.Join();
} else {
Log.Error("Apartment state cannot be set on this platform.");
Result.SetResult(ResultState.Skipped, "Apartment state cannot be set on this platform.");
WorkItemComplete();
}
}
private void RunOnCurrentThread()
{
Context.CurrentTest = Test;
Context.CurrentResult = Result;
Context.Listener.TestStarted(Test);
Context.StartTime = DateTime.UtcNow;
Context.StartTicks = Stopwatch.GetTimestamp();
Context.TestWorker = TestWorker;
Context.EstablishExecutionEnvironment();
State = WorkItemState.Running;
PerformWork();
}
private ParallelExecutionStrategy GetExecutionStrategy()
{
if (Test.TypeInfo == null)
return ParallelExecutionStrategy.Direct;
if (Context.IsSingleThreaded)
return ParallelExecutionStrategy.Direct;
if (ParallelScope.HasFlag(ParallelScope.None))
return ParallelExecutionStrategy.NonParallel;
if (ParallelScope.HasFlag(ParallelScope.Self))
return ParallelExecutionStrategy.Parallel;
if (Context.ParallelScope.HasFlag(ParallelScope.Children) || (Test is TestFixture && Context.ParallelScope.HasFlag(ParallelScope.Fixtures)))
return ParallelExecutionStrategy.Parallel;
if (!(this is SimpleWorkItem))
return ParallelExecutionStrategy.NonParallel;
return ParallelExecutionStrategy.Direct;
}
private static ApartmentState GetTargetApartment(ITest test)
{
ApartmentState apartmentState = test.Properties.TryGet("ApartmentState", ApartmentState.Unknown);
if (apartmentState == ApartmentState.Unknown && test.Parent != null)
return GetTargetApartment(test.Parent);
return apartmentState;
}
}
}