1 // ***********************************************************************
2 // Copyright (c) 2012 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
30 using System.Collections.Generic;
31 using System.Diagnostics;
32 using System.Threading;
33 using NUnit.Framework.Interfaces;
35 namespace NUnit.Framework.Internal.Execution
38 /// A WorkItem may be an individual test case, a fixture or
39 /// a higher level grouping of tests. All WorkItems inherit
40 /// from the abstract WorkItem class, which uses the template
41 /// pattern to allow derived classes to perform work in
42 /// whatever way is needed.
44 /// A WorkItem is created with a particular TestExecutionContext
45 /// and is responsible for re-establishing that context in the
46 /// current thread before it begins or resumes execution.
48 public abstract class WorkItem
50 static Logger log = InternalTrace.GetLogger("WorkItem");
52 #region Static Factory Method
55 /// Creates a work item.
57 /// <param name="test">The test for which this WorkItem is being created.</param>
58 /// <param name="filter">The filter to be used in selecting any child Tests.</param>
59 /// <returns></returns>
60 static public WorkItem CreateWorkItem(ITest test, ITestFilter filter)
62 TestSuite suite = test as TestSuite;
64 return new CompositeWorkItem(suite, filter);
66 return new SimpleWorkItem((TestMethod)test, filter);
71 #region Construction and Initialization
74 /// Construct a WorkItem for a particular test.
76 /// <param name="test">The test that the WorkItem will run</param>
77 public WorkItem(Test test)
80 Result = test.MakeTestResult();
81 State = WorkItemState.Ready;
82 Actions = new List<ITestAction>();
83 #if !PORTABLE && !SILVERLIGHT && !NETCF
84 TargetApartment = Test.Properties.ContainsKey(PropertyNames.ApartmentState)
85 ? (ApartmentState)Test.Properties.Get(PropertyNames.ApartmentState)
86 : ApartmentState.Unknown;
91 /// Initialize the TestExecutionContext. This must be done
92 /// before executing the WorkItem.
95 /// Originally, the context was provided in the constructor
96 /// but delaying initialization of the context until the item
97 /// is about to be dispatched allows changes in the parent
98 /// context during OneTimeSetUp to be reflected in the child.
100 /// <param name="context">The TestExecutionContext to use</param>
101 public void InitializeContext(TestExecutionContext context)
103 Guard.OperationValid(Context == null, "The context has already been initialized");
107 //if (Test is TestAssembly)
108 // Actions.AddRange(ActionsHelper.GetActionsFromAttributeProvider(((TestAssembly)Test).Assembly));
109 //else if (Test is ParameterizedMethodSuite)
110 // Actions.AddRange(ActionsHelper.GetActionsFromAttributeProvider(Test.Method.MethodInfo));
111 //else if (Test.TypeInfo != null)
112 // Actions.AddRange(ActionsHelper.GetActionsFromTypesAttributes(Test.TypeInfo.Type));
117 #region Properties and Events
120 /// Event triggered when the item is complete
122 public event EventHandler Completed;
125 /// Gets the current state of the WorkItem
127 public WorkItemState State { get; private set; }
130 /// The test being executed by the work item
132 public Test Test { get; private set; }
135 /// The execution context
137 public TestExecutionContext Context { get; private set; }
140 /// The unique id of the worker executing this item.
142 public string WorkerId {get; internal set;}
145 /// The test actions to be performed before and after this test
147 public List<ITestAction> Actions { get; private set; }
151 /// Indicates whether this WorkItem may be run in parallel
153 public bool IsParallelizable
157 ParallelScope scope = ParallelScope.None;
159 if (Test.Properties.ContainsKey(PropertyNames.ParallelScope))
161 scope = (ParallelScope)Test.Properties.Get(PropertyNames.ParallelScope);
163 if ((scope & ParallelScope.Self) != 0)
168 scope = Context.ParallelScope;
170 if ((scope & ParallelScope.Children) != 0)
174 if (Test is TestFixture && (scope & ParallelScope.Fixtures) != 0)
177 // Special handling for the top level TestAssembly.
178 // If it has any scope specified other than None,
179 // we will use the parallel queue. This heuristic
180 // is intended to minimize creation of unneeded
181 // queues and workers, since the assembly and
182 // namespace level tests can easily run in any queue.
183 if (Test is TestAssembly && scope != ParallelScope.None)
194 public TestResult Result { get; protected set; }
196 #if !SILVERLIGHT && !NETCF && !PORTABLE
197 internal ApartmentState TargetApartment { get; set; }
198 private ApartmentState CurrentApartment { get; set; }
203 #region OwnThreadReason Enumeration
206 private enum OwnThreadReason
211 DifferentApartment = 4
216 #region Public Methods
219 /// Execute the current work item, including any
220 /// child work items.
222 public virtual void Execute()
224 // Timeout set at a higher level
225 int timeout = Context.TestCaseTimeout;
227 // Timeout set on this test
228 if (Test.Properties.ContainsKey(PropertyNames.Timeout))
229 timeout = (int)Test.Properties.Get(PropertyNames.Timeout);
231 // Unless the context is single threaded, a supplementary thread
232 // is created on the various platforms...
233 // 1. If the test used the RequiresThreadAttribute.
234 // 2. If a test method has a timeout.
235 // 3. If the test needs to run in a different apartment.
237 // NOTE: We want to eliminate or significantly reduce
238 // cases 2 and 3 in the future.
240 // Case 2 requires the ability to stop and start test workers
241 // dynamically. We would cancel the worker thread, dispose of
242 // the worker and start a new worker on a new thread.
244 // Case 3 occurs when using either dispatcher whenever a
245 // child test calls for a different apartment from the one
246 // used by it's parent. It routinely occurs under the simple
247 // dispatcher (--workers=0 option). Under the parallel dispatcher
248 // it is needed when test cases are not enabled for parallel
249 // execution. Currently, test cases are always run sequentially,
250 // so this continues to apply fairly generally.
252 var ownThreadReason = OwnThreadReason.NotNeeded;
255 if (Test.RequiresThread)
256 ownThreadReason |= OwnThreadReason.RequiresThread;
257 if (timeout > 0 && Test is TestMethod)
258 ownThreadReason |= OwnThreadReason.Timeout;
259 #if !SILVERLIGHT && !NETCF
260 CurrentApartment = Thread.CurrentThread.GetApartmentState();
261 if (CurrentApartment != TargetApartment && TargetApartment != ApartmentState.Unknown)
262 ownThreadReason |= OwnThreadReason.DifferentApartment;
266 if (ownThreadReason == OwnThreadReason.NotNeeded)
268 else if (Context.IsSingleThreaded)
270 var msg = "Test is not runnable in single-threaded context. " + ownThreadReason;
272 Result.SetResult(ResultState.NotRunnable, msg);
277 log.Debug("Running test on own thread. " + ownThreadReason);
278 #if SILVERLIGHT || NETCF
279 RunTestOnOwnThread(timeout);
281 var apartment = (ownThreadReason | OwnThreadReason.DifferentApartment) != 0
284 RunTestOnOwnThread(timeout, apartment);
289 #if SILVERLIGHT || NETCF
290 private Thread thread;
292 private void RunTestOnOwnThread(int timeout)
294 thread = new Thread(RunTest);
299 #if !SILVERLIGHT && !NETCF && !PORTABLE
300 private Thread thread;
302 private void RunTestOnOwnThread(int timeout, ApartmentState apartment)
304 thread = new Thread(new ThreadStart(RunTest));
305 thread.SetApartmentState(apartment);
311 private void RunThread(int timeout)
314 thread.CurrentCulture = Context.CurrentCulture;
315 thread.CurrentUICulture = Context.CurrentUICulture;
320 if (!Test.IsAsynchronous || timeout > 0)
323 timeout = Timeout.Infinite;
325 if (!thread.Join(timeout))
337 if (Context.ExecutionStatus == TestExecutionStatus.AbortRequested)
340 log.Debug("Killing thread {0}, which exceeded timeout", tThread.ManagedThreadId);
341 ThreadUtility.Kill(tThread);
343 // NOTE: Without the use of Join, there is a race condition here.
344 // The thread sets the result to Cancelled and our code below sets
345 // it to Failure. In order for the result to be shown as a failure,
346 // we need to ensure that the following code executes after the
347 // thread has terminated. There is a risk here: the test code might
348 // refuse to terminate. However, it's more important to deal with
349 // the normal rather than a pathological case.
352 log.Debug("Changing result from {0} to Timeout Failure", Result.ResultState);
354 Result.SetResult(ResultState.Failure,
355 string.Format("Test exceeded Timeout value of {0}ms", timeout));
363 private void RunTest()
365 Context.CurrentTest = this.Test;
366 Context.CurrentResult = this.Result;
367 Context.Listener.TestStarted(this.Test);
368 Context.StartTime = DateTime.UtcNow;
369 Context.StartTicks = Stopwatch.GetTimestamp();
370 Context.WorkerId = this.WorkerId;
371 Context.EstablishExecutionEnvironment();
373 State = WorkItemState.Running;
378 private object threadLock = new object();
381 /// Cancel (abort or stop) a WorkItem
383 /// <param name="force">true if the WorkItem should be aborted, false if it should run to completion</param>
384 public virtual void Cancel(bool force)
387 Context.ExecutionStatus = force ? TestExecutionStatus.AbortRequested : TestExecutionStatus.StopRequested;
404 if (!tThread.Join(0))
406 log.Debug("Killing thread {0} for cancel", tThread.ManagedThreadId);
407 ThreadUtility.Kill(tThread);
411 log.Debug("Changing result from {0} to Cancelled", Result.ResultState);
413 Result.SetResult(ResultState.Cancelled, "Cancelled by user");
422 #region Protected Methods
425 /// Method that performs actually performs the work. It should
426 /// set the State to WorkItemState.Complete when done.
428 protected abstract void PerformWork();
431 /// Method called by the derived class when all work is complete
433 protected void WorkItemComplete()
435 State = WorkItemState.Complete;
437 Result.StartTime = Context.StartTime;
438 Result.EndTime = DateTime.UtcNow;
440 long tickCount = Stopwatch.GetTimestamp() - Context.StartTicks;
441 double seconds = (double)tickCount / Stopwatch.Frequency;
442 Result.Duration = seconds;
444 // We add in the assert count from the context. If
445 // this item is for a test case, we are adding the
446 // test assert count to zero. If it's a fixture, we
447 // are adding in any asserts that were run in the
448 // fixture setup or teardown. Each context only
449 // counts the asserts taking place in that context.
450 // Each result accumulates the count from child
451 // results along with it's own asserts.
452 Result.AssertCount += Context.AssertCount;
454 Context.Listener.TestFinished(Result);
456 if (Completed != null)
457 Completed(this, EventArgs.Empty);
459 //Clear references to test objects to reduce memory usage
460 Context.TestObject = null;