#region Copyright (c) 2002-2003, James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole, Philip A. Craig /************************************************************************************ ' ' Copyright © 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole ' Copyright © 2000-2003 Philip A. Craig ' ' This software is provided 'as-is', without any express or implied warranty. In no ' event will the authors be held liable for any damages arising from the use of this ' software. ' ' Permission is granted to anyone to use this software for any purpose, including ' commercial applications, and to alter it and redistribute it freely, subject to the ' following restrictions: ' ' 1. The origin of this software must not be misrepresented; you must not claim that ' you wrote the original software. If you use this software in a product, an ' acknowledgment (see the following) in the product documentation is required. ' ' Portions Copyright © 2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole ' or Copyright © 2000-2003 Philip A. Craig ' ' 2. Altered source versions must be plainly marked as such, and must not be ' misrepresented as being the original software. ' ' 3. This notice may not be removed or altered from any source distribution. ' '***********************************************************************************/ #endregion namespace NUnit.Core { using System; using System.IO; using System.Collections; using System.Reflection; using System.Threading; using System.Runtime.Remoting; /// /// Summary description for RemoteTestRunner. /// /// [Serializable] public class RemoteTestRunner : LongLivingMarshalByRefObject, TestRunner, EventListener { #region Instance variables /// /// The loaded test suite /// private TestSuite suite; /// /// TestRunner thread used for asynchronous running /// private TestRunnerThread runningThread; /// /// Our writer for standard output /// private TextWriter outText; /// /// Our writer for error output /// private TextWriter errorText; /// /// Buffered standard output writer created for each test run /// private BufferedStringTextWriter outBuffer; /// /// Buffered error writer created for each test run /// private BufferedStringTextWriter errorBuffer; /// /// Console standard output to restore after a run /// private TextWriter saveOut; /// /// Console error output to restore after a run /// private TextWriter saveError; /// /// Saved current directory to restore after a run /// private string currentDirectory; /// /// Saved paths of the assemblies we loaded - used to set /// current directory when we are running the tests. /// private string[] assemblies; /// /// Dispatcher used to put out runner's test events /// private TestEventDispatcher events = new TestEventDispatcher(); private EventListener listener; // Temp private Version frameworkVersion; private IFilter filter; private bool displayTestLabels; /// /// Results from the last test run /// private TestResult[] results; #endregion #region Constructors /// /// Construct with stdOut and stdErr writers /// public RemoteTestRunner( TextWriter outText, TextWriter errorText ) { this.outText = outText; this.errorText = errorText; } /// /// Default constructor uses Null writers. /// public RemoteTestRunner() : this( TextWriter.Null, TextWriter.Null ) { } #endregion #region Properties /// /// Writer for standard output - this is a public property /// so that we can set it when creating an instance /// in another AppDomain. /// public TextWriter Out { get { return outText; } set { outText = value; } } /// /// Writer for error output - this is a public property /// so that we can set it when creating an instance /// in another AppDomain. /// public TextWriter Error { get { return errorText; } set { errorText = value; } } /// /// Interface to the events sourced by the runner /// public ITestEvents Events { get { return events; } } public Version FrameworkVersion { get { return frameworkVersion; } } public bool DisplayTestLabels { get { return displayTestLabels; } set { displayTestLabels = value; } } /// /// Results from the last test run /// public TestResult[] Results { get { return results; } } /// /// First (or only) result from the last test run /// public TestResult Result { get { return results == null ? null : results[0]; } } #endregion #region Methods for Loading Tests /// /// Load an assembly /// /// public Test Load( string assemblyName ) { this.assemblies = new string[] { assemblyName }; TestSuiteBuilder builder = new TestSuiteBuilder(); suite = builder.Build( assemblyName ); frameworkVersion = builder.FrameworkVersion; return suite; } /// /// Load a particular test in an assembly /// public Test Load( string assemblyName, string testName ) { this.assemblies = new string[] { assemblyName }; TestSuiteBuilder builder = new TestSuiteBuilder(); suite = builder.Build( assemblyName, testName ); frameworkVersion = builder.FrameworkVersion; return suite; } /// /// Load multiple assemblies /// public Test Load( string projectName, string[] assemblies ) { this.assemblies = (string[])assemblies.Clone(); TestSuiteBuilder builder = new TestSuiteBuilder(); suite = builder.Build( projectName, assemblies ); frameworkVersion = builder.FrameworkVersion; return suite; } public Test Load( string projectName, string[] assemblies, string testName ) { this.assemblies = (string[])assemblies.Clone(); TestSuiteBuilder builder = new TestSuiteBuilder(); suite = builder.Build( assemblies, testName ); frameworkVersion = builder.FrameworkVersion; return suite; } public void Unload() { suite = null; // All for now frameworkVersion = null; } #endregion #region Methods for Counting TestCases public int CountTestCases() { return suite.CountTestCases(); } public int CountTestCases( string testName ) { Test test = FindTest( suite, testName ); return test == null ? 0 : test.CountTestCases(); } public int CountTestCases(string[] testNames ) { int count = 0; foreach( string testName in testNames) count += CountTestCases( testName ); return count; } public ICollection GetCategories() { return CategoryManager.Categories; } #endregion #region Methods for Running Tests public void SetFilter( IFilter filter ) { this.filter = filter; } public TestResult Run( EventListener listener ) { return Run( listener, suite ); } public TestResult Run(NUnit.Core.EventListener listener, string testName ) { if ( testName == null || testName.Length == 0 ) return Run( listener, suite ); else return Run( listener, FindTest( suite, testName ) ); } public TestResult[] Run(NUnit.Core.EventListener listener, string[] testNames) { if ( testNames == null || testNames.Length == 0 ) return new TestResult[] { Run( listener, suite ) }; else return Run( listener, FindTests( suite, testNames ) ); } public void RunTest(NUnit.Core.EventListener listener ) { runningThread = new TestRunnerThread( this ); runningThread.Run( listener ); } public void RunTest(NUnit.Core.EventListener listener, string testName ) { runningThread = new TestRunnerThread( this ); runningThread.Run( listener, testName ); } public void RunTest(NUnit.Core.EventListener listener, string[] testNames) { runningThread = new TestRunnerThread( this ); runningThread.Run( listener, testNames ); } public void CancelRun() { if ( runningThread != null ) runningThread.Cancel(); CleanUpAfterTestRun(); } public void Wait() { if ( runningThread != null ) runningThread.Wait(); } #endregion #region Helper Routines /// /// Private method to run a single test /// private TestResult Run( EventListener listener, Test test ) { // Create array with the one test Test[] tests = new Test[] { test }; // Call our workhorse method results = Run( listener, tests ); // Return the first result we got return results[0]; } /// /// Private method to run a set of tests. This routine is the workhorse /// that is called anytime tests are run. /// private TestResult[] Run( EventListener listener, Test[] tests ) { // Create buffered writers for efficiency outBuffer = new BufferedStringTextWriter( outText ); errorBuffer = new BufferedStringTextWriter( errorText ); // Save previous state of Console. This is needed because Console.Out and // Console.Error are static. In the case where the test itself calls this // method, we can lose output if we don't save and restore their values. // This is exactly what happens when we are testing NUnit itself. saveOut = Console.Out; saveError = Console.Error; #if !TARGET_JVM // Set Console to go to our buffers. Note that any changes made by // the user in the test code or the code it calls will defeat this. Console.SetOut( outBuffer ); Console.SetError( errorBuffer ); #endif // Save the current directory so we can run each test in // the same directory as its assembly currentDirectory = Environment.CurrentDirectory; try { // Create an array for the resuls results = new TestResult[ tests.Length ]; // Signal that we are starting the run this.listener = listener; listener.RunStarted( tests ); // TODO: Get rid of count int count = 0; foreach( Test test in tests ) count += filter == null ? test.CountTestCases() : test.CountTestCases( filter ); events.FireRunStarting( tests, count ); // Run each test, saving the results int index = 0; foreach( Test test in tests ) { string assemblyDirectory = Path.GetDirectoryName( this.assemblies[test.AssemblyKey] ); if ( assemblyDirectory != null && assemblyDirectory != string.Empty ) Environment.CurrentDirectory = assemblyDirectory; results[index++] = test.Run( this, filter ); } // Signal that we are done listener.RunFinished( results ); events.FireRunFinished( results ); // Return result array return results; } catch( Exception exception ) { // Signal that we finished with an exception listener.RunFinished( exception ); events.FireRunFinished( exception ); // Rethrow - should we do this? throw; } finally { CleanUpAfterTestRun(); } } private Test FindTest(Test test, string fullName) { if(test.UniqueName.Equals(fullName)) return test; if(test.FullName.Equals(fullName)) return test; Test result = null; if(test is TestSuite) { TestSuite suite = (TestSuite)test; foreach(Test testCase in suite.Tests) { result = FindTest(testCase, fullName); if(result != null) break; } } return result; } private Test[] FindTests( Test test, string[] names ) { Test[] tests = new Test[ names.Length ]; int index = 0; foreach( string name in names ) tests[index++] = FindTest( test, name ); return tests; } private void CleanUpAfterTestRun() { // Restore the directory we saved if ( currentDirectory != null ) { Environment.CurrentDirectory = currentDirectory; currentDirectory = null; } // Close our output buffers if ( outBuffer != null ) { outBuffer.Close(); outBuffer = null; } if ( errorBuffer != null ) { errorBuffer.Close(); errorBuffer = null; } // Restore previous console values if ( saveOut != null ) { Console.SetOut( saveOut ); saveOut = null; } if ( saveError != null ) { Console.SetError( saveError ); saveError = null; } } #endregion #region EventListener Members public void RunStarted(Test[] tests) { // TODO: Remove } void NUnit.Core.EventListener.RunFinished(TestResult[] results) { // TODO: Remove outText.Close(); } void NUnit.Core.EventListener.RunFinished(Exception exception) { // TODO: Remove outText.Close(); } public void TestStarted(TestCase testCase) { if ( displayTestLabels ) outText.WriteLine("***** {0}", testCase.FullName ); this.listener.TestStarted( testCase ); events.FireTestStarting( testCase ); } void NUnit.Core.EventListener.TestFinished(TestCaseResult result) { listener.TestFinished( result ); events.FireTestFinished( result ); } public void SuiteStarted(TestSuite suite) { listener.SuiteStarted( suite ); events.FireSuiteStarting( suite ); } void NUnit.Core.EventListener.SuiteFinished(TestSuiteResult result) { listener.SuiteFinished( result ); events.FireSuiteFinished( result ); } public void UnhandledException(Exception exception) { listener.UnhandledException( exception ); events.FireTestException( exception ); } #endregion } }