// **************************************************************** // Copyright 2007, Charlie Poole // This is free software licensed under the NUnit license. You may // obtain a copy of the license at http://nunit.org/?p=license&r=2.4 // **************************************************************** using System; using System.IO; using System.Threading; using System.Collections; using System.Collections.Specialized; using NUnit.Core.Filters; using System.Reflection; namespace NUnit.Core { /// /// SimpleTestRunner is the simplest direct-running TestRunner. It /// passes the event listener interface that is provided on to the tests /// to use directly and does nothing to redirect text output. Both /// Run and BeginRun are actually synchronous, although the client /// can usually ignore this. BeginRun + EndRun operates as expected. /// public class SimpleTestRunner : MarshalByRefObject, TestRunner { #region Instance Variables /// /// Identifier for this runner. Must be unique among all /// active runners in order to locate tests. Default /// value of 0 is adequate in applications with a single /// runner or a non-branching chain of runners. /// private int runnerID = 0; /// /// The loaded test suite /// private Test test; /// /// The builder we use to load tests, created for each load /// private TestSuiteBuilder builder; /// /// Results from the last test run /// private TestResult testResult; /// /// The thread on which Run was called. Set to the /// current thread while a run is in process. /// private Thread runThread; #endregion #region Constructor public SimpleTestRunner() : this( 0 ) { } public SimpleTestRunner( int runnerID ) { this.runnerID = runnerID; } #endregion #region Properties public virtual int ID { get { return runnerID; } } public IList AssemblyInfo { get { return builder.AssemblyInfo; } } public ITest Test { get { return test == null ? null : new TestNode( test ); } } /// /// Results from the last test run /// public TestResult TestResult { get { return testResult; } } public virtual bool Running { get { return runThread != null && runThread.IsAlive; } } #endregion #region Methods for Loading Tests /// /// Load a TestPackage /// /// The package to be loaded /// True on success, false on failure public bool Load( TestPackage package ) { this.builder = new TestSuiteBuilder(); this.test = builder.Build( package ); if ( test == null ) return false; test.SetRunnerID( this.runnerID, true ); return true; } /// /// Unload all tests previously loaded /// public void Unload() { this.test = null; // All for now } #endregion #region CountTestCases public int CountTestCases( ITestFilter filter ) { return test.CountTestCases( filter ); } #endregion #region Methods for Running Tests public virtual TestResult Run( EventListener listener ) { return Run( listener, TestFilter.Empty ); } public virtual TestResult Run( EventListener listener, ITestFilter filter ) { try { // Take note of the fact that we are running this.runThread = Thread.CurrentThread; listener.RunStarted( this.Test.TestName.FullName, test.CountTestCases( filter ) ); testResult = test.Run( listener, filter ); // Signal that we are done listener.RunFinished( testResult ); // Return result array return testResult; } catch( Exception exception ) { // Signal that we finished with an exception listener.RunFinished( exception ); // Rethrow - should we do this? throw; } finally { runThread = null; } } public void BeginRun( EventListener listener ) { testResult = this.Run( listener ); } public void BeginRun( EventListener listener, ITestFilter filter ) { testResult = this.Run( listener, filter ); } public virtual TestResult EndRun() { return TestResult; } /// /// Wait is a NOP for SimpleTestRunner /// public virtual void Wait() { } public virtual void CancelRun() { if (this.runThread != null) { // Cancel Synchronous run only if on another thread if ( runThread == Thread.CurrentThread ) throw new InvalidOperationException( "May not CancelRun on same thread that is running the test" ); // Make a copy of runThread, which will be set to // null when the thread terminates. Thread cancelThread = this.runThread; // Tell the thread to abort this.runThread.Abort(); // Wake up the thread if necessary // Figure out if we need to do an interupt if ( (cancelThread.ThreadState & ThreadState.WaitSleepJoin ) != 0 ) cancelThread.Interrupt(); } } #endregion #region InitializeLifetimeService Override public override object InitializeLifetimeService() { return null; } #endregion } }