1 #region Copyright (c) 2002-2003, James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole, Philip A. Craig
2 /************************************************************************************
4 ' Copyright © 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
5 ' Copyright © 2000-2003 Philip A. Craig
7 ' This software is provided 'as-is', without any express or implied warranty. In no
8 ' event will the authors be held liable for any damages arising from the use of this
11 ' Permission is granted to anyone to use this software for any purpose, including
12 ' commercial applications, and to alter it and redistribute it freely, subject to the
13 ' following restrictions:
15 ' 1. The origin of this software must not be misrepresented; you must not claim that
16 ' you wrote the original software. If you use this software in a product, an
17 ' acknowledgment (see the following) in the product documentation is required.
19 ' Portions Copyright © 2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
20 ' or Copyright © 2000-2003 Philip A. Craig
22 ' 2. Altered source versions must be plainly marked as such, and must not be
23 ' misrepresented as being the original software.
25 ' 3. This notice may not be removed or altered from any source distribution.
27 '***********************************************************************************/
34 using System.Runtime.Remoting;
35 using System.Security.Policy;
36 using System.Reflection;
37 using System.Collections;
38 using System.Collections.Specialized;
39 using System.Configuration;
44 public class TestDomain : TestRunner
46 #region Instance Variables
49 /// The appdomain used to load tests
51 private AppDomain domain;
54 /// The path to our cache
56 private string cachePath;
59 /// The remote runner loaded in the test appdomain
61 private TestRunner testRunner;
64 /// Writer for console standard output
66 private TextWriter outWriter;
69 /// Writer for console error output
71 private TextWriter errorWriter;
74 /// Holds the event listener while we are running
76 private EventListener listener;
84 get { return outWriter; }
85 set { outWriter = value; }
88 public TextWriter Error
90 get { return errorWriter; }
91 set { errorWriter = value; }
94 private TestRunner Runner
98 if ( testRunner == null )
99 testRunner = MakeRemoteTestRunner( domain );
105 public bool DisplayTestLabels
107 get { return Runner.DisplayTestLabels; }
108 set { Runner.DisplayTestLabels = value; }
111 private TestRunner MakeRemoteTestRunner( AppDomain runnerDomain )
113 object obj = runnerDomain.CreateInstanceAndUnwrap(
114 typeof(RemoteTestRunner).Assembly.FullName,
115 typeof(RemoteTestRunner).FullName,
116 false, BindingFlags.Default,null,null,null,null,null);
118 RemoteTestRunner runner = (RemoteTestRunner) obj;
120 runner.Out = this.outWriter;
121 runner.Error = this.errorWriter;
126 public Version FrameworkVersion
128 get { return Runner.FrameworkVersion; }
135 public TestDomain( TextWriter outWriter, TextWriter errorWriter )
137 this.outWriter = outWriter;
138 this.errorWriter = errorWriter;
141 public TestDomain() : this( TextWriter.Null, TextWriter.Null ) { }
145 #region Loading and Unloading Tests
147 public Test Load( string assemblyFileName )
149 return Load( assemblyFileName, string.Empty );
152 public Test Load(string assemblyFileName, string testFixture)
158 CreateDomain( assemblyFileName );
159 string assemblyPath = Path.GetFullPath( assemblyFileName );
161 if ( testFixture != null && testFixture != string.Empty )
162 return Runner.Load( assemblyPath, testFixture );
164 return Runner.Load( assemblyPath );
173 public Test Load( string testFileName, string[] assemblies )
175 return Load( testFileName, assemblies, null );
178 public Test Load( string testFileName, string[] assemblies, string testFixture )
180 FileInfo testFile = new FileInfo( testFileName );
181 return Load( testFileName, testFile.DirectoryName, testFile.FullName + ".config", GetBinPath(assemblies), assemblies, testFixture );
184 public Test Load( string testFileName, string appBase, string configFile, string binPath, string[] assemblies, string testFixture )
190 CreateDomain( testFileName, appBase, configFile, binPath, assemblies );
192 if ( testFixture != null )
193 return Runner.Load( testFileName, assemblies, testFixture );
195 return Runner.Load( testFileName, assemblies );
204 public Test Load( NUnitProject project )
206 if ( project.IsAssemblyWrapper )
207 return Load( project.ActiveConfig.Assemblies[0].FullPath );
209 return Load( project.ProjectPath, project.ActiveConfig.TestAssemblies );
212 public Test Load( NUnitProject project, string testFixture )
214 if ( project.IsAssemblyWrapper )
215 return Load( project.ActiveConfig.Assemblies[0].FullPath, testFixture );
217 return Load( project.ProjectPath, project.ActiveConfig.TestAssemblies, testFixture );
228 AppDomain.Unload(domain);
229 DirectoryInfo cacheDir = new DirectoryInfo(cachePath);
230 if(cacheDir.Exists) cacheDir.Delete(true);
232 catch( CannotUnloadAppDomainException )
234 // TODO: Do something useful. For now we just
235 // leave the orphaned AppDomain "out there"
236 // rather than aborting the application.
245 public static string GetBinPath( string[] assemblies )
247 ArrayList dirs = new ArrayList();
248 string binPath = null;
250 foreach( string path in assemblies )
252 string dir = Path.GetDirectoryName( Path.GetFullPath( path ) );
253 if ( !dirs.Contains( dir ) )
257 if ( binPath == null )
260 binPath = binPath + ";" + dir;
269 #region Counting Tests
271 public int CountTestCases()
273 return Runner.CountTestCases();
276 public int CountTestCases( string testName )
278 return Runner.CountTestCases( testName );
282 public int CountTestCases( string[] testNames )
284 return Runner.CountTestCases( testNames );
289 public ICollection GetCategories()
291 return Runner.GetCategories();
294 #region Running Tests
296 // public TestResult Run(NUnit.Core.EventListener listener, IFilter filter)
298 // return Runner.Run( listener, filter );
301 public void SetFilter( IFilter filter )
303 Runner.SetFilter( filter );
306 public TestResult Run(NUnit.Core.EventListener listener)
308 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
310 this.listener = listener;
311 return Runner.Run( listener );
315 public TestResult Run(NUnit.Core.EventListener listener, string testName)
317 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
319 this.listener = listener;
320 return Runner.Run( listener, testName );
324 public TestResult[] Run(NUnit.Core.EventListener listener, string[] testNames)
326 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
328 this.listener = listener;
329 return Runner.Run( listener, testNames );
333 public void RunTest(NUnit.Core.EventListener listener )
335 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
337 this.listener = listener;
338 Runner.RunTest( listener );
342 public void RunTest(NUnit.Core.EventListener listener, string testName )
344 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
346 this.listener = listener;
347 Runner.RunTest( listener, testName );
351 public void RunTest(NUnit.Core.EventListener listener, string[] testNames)
353 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
355 this.listener = listener;
356 Runner.RunTest( listener, testNames );
360 public void CancelRun()
365 // For now, just publish any unhandled exceptions and let the listener
366 // figure out what to do with them.
367 private void OnUnhandledException( object sender, UnhandledExceptionEventArgs e )
369 this.listener.UnhandledException( (Exception)e.ExceptionObject );
374 #region Helpers Used in AppDomain Creation
377 /// Construct an application domain for testing a single assembly
379 /// <param name="assemblyFileName">The assembly file name</param>
380 private void CreateDomain( string assemblyFileName )
382 FileInfo testFile = new FileInfo( assemblyFileName );
384 string assemblyPath = Path.GetFullPath( assemblyFileName );
385 string domainName = string.Format( "domain-{0}", Path.GetFileName( assemblyFileName ) );
387 domain = MakeAppDomain( domainName, testFile.DirectoryName, testFile.FullName + ".config", testFile.DirectoryName );
391 /// Construct an application domain for testing multiple assemblies
393 /// <param name="testFileName">The file name of the project file</param>
394 /// <param name="appBase">The application base path</param>
395 /// <param name="configFile">The configuration file to use</param>
396 /// <param name="binPath">The private bin path</param>
397 /// <param name="assemblies">A collection of assemblies to load</param>
398 private void CreateDomain( string testFileName, string appBase, string configFile, string binPath, string[] assemblies )
400 string domainName = string.Format( "domain-{0}", Path.GetFileName( testFileName ) );
401 domain = MakeAppDomain( testFileName, appBase, configFile, binPath );
404 private void CreateDomain( NUnitProject project )
406 ProjectConfig cfg = project.ActiveConfig;
408 if ( project.IsAssemblyWrapper )
409 CreateDomain( cfg.Assemblies[0].FullPath );
411 CreateDomain( project.ProjectPath, cfg.BasePath, cfg.ConfigurationFilePath, cfg.PrivateBinPath, cfg.TestAssemblies );
415 /// This method creates appDomains for the framework.
417 /// <param name="domainName">Name of the domain</param>
418 /// <param name="appBase">ApplicationBase for the domain</param>
419 /// <param name="configFile">ConfigurationFile for the domain</param>
420 /// <param name="binPath">PrivateBinPath for the domain</param>
421 /// <returns></returns>
422 private AppDomain MakeAppDomain( string domainName, string appBase, string configFile, string binPath )
424 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
425 Evidence evidence = new Evidence(baseEvidence);
427 AppDomainSetup setup = new AppDomainSetup();
429 // We always use the same application name
430 setup.ApplicationName = "Tests";
431 // We always want to do shadow copying. Note that we do NOT
432 // set ShadowCopyDirectories because we rely on the default
433 // setting of ApplicationBase plus PrivateBinPath
434 setup.ShadowCopyFiles = "true";
435 setup.ShadowCopyDirectories = appBase;
437 setup.ApplicationBase = appBase;
438 setup.ConfigurationFile = configFile;
439 setup.PrivateBinPath = binPath;
441 AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
443 ConfigureCachePath(runnerDomain);
449 /// Set the location for caching and delete any old cache info
451 /// <param name="domain">Our domain</param>
452 private void ConfigureCachePath(AppDomain domain)
454 cachePath = String.Format(@"{0}\{1}",
455 ConfigurationSettings.AppSettings["shadowfiles.path"], DateTime.Now.Ticks);
456 cachePath = Environment.ExpandEnvironmentVariables(cachePath);
458 DirectoryInfo dir = new DirectoryInfo(cachePath);
459 if(dir.Exists) dir.Delete(true);
461 domain.SetCachePath(cachePath);