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;
79 /// Indicate whether files should be shadow copied
81 private bool shadowCopyFiles = true;
87 public AppDomain AppDomain
89 get { return domain; }
94 get { return outWriter; }
95 set { outWriter = value; }
98 public TextWriter Error
100 get { return errorWriter; }
101 set { errorWriter = value; }
104 private TestRunner Runner
108 if ( testRunner == null )
109 testRunner = MakeRemoteTestRunner( domain );
115 public bool DisplayTestLabels
117 get { return Runner.DisplayTestLabels; }
118 set { Runner.DisplayTestLabels = value; }
121 private TestRunner MakeRemoteTestRunner( AppDomain runnerDomain )
123 object obj = runnerDomain.CreateInstanceAndUnwrap(
124 typeof(RemoteTestRunner).Assembly.FullName,
125 typeof(RemoteTestRunner).FullName,
126 false, BindingFlags.Default,null,null,null,null,null);
128 RemoteTestRunner runner = (RemoteTestRunner) obj;
130 runner.Out = this.outWriter;
131 runner.Error = this.errorWriter;
136 public Version FrameworkVersion
138 get { return Runner.FrameworkVersion; }
141 public bool ShadowCopyFiles
143 get { return shadowCopyFiles; }
146 if ( this.domain != null )
147 throw new ArgumentException( "ShadowCopyFiles may not be set after domain is created" );
148 shadowCopyFiles = value;
152 public TestResult[] Results
154 get { return Runner.Results; }
157 public TestResult Result
159 get { return Runner.Result; }
166 public TestDomain( TextWriter outWriter, TextWriter errorWriter )
168 this.outWriter = outWriter;
169 this.errorWriter = errorWriter;
172 public TestDomain() : this( TextWriter.Null, TextWriter.Null ) { }
176 #region Loading and Unloading Tests
178 public Test Load( string assemblyFileName )
180 return Load( assemblyFileName, string.Empty );
183 public Test Load(string assemblyFileName, string testFixture)
189 CreateDomain( assemblyFileName );
190 string assemblyPath = Path.GetFullPath( assemblyFileName );
192 if ( testFixture != null && testFixture != string.Empty )
193 return Runner.Load( assemblyPath, testFixture );
195 return Runner.Load( assemblyPath );
204 public Test Load( string testFileName, string[] assemblies )
206 return Load( testFileName, assemblies, null );
209 public Test Load( string testFileName, string[] assemblies, string testFixture )
211 FileInfo testFile = new FileInfo( testFileName );
212 return Load( testFileName, testFile.DirectoryName, testFile.FullName + ".config", GetBinPath(assemblies), assemblies, testFixture );
215 public Test Load( string testFileName, string appBase, string configFile, string binPath, string[] assemblies, string testFixture )
221 CreateDomain( testFileName, appBase, configFile, binPath, assemblies );
223 if ( testFixture != null )
224 return Runner.Load( testFileName, assemblies, testFixture );
226 return Runner.Load( testFileName, assemblies );
235 public Test Load( NUnitProject project )
237 return Load( project, null );
240 public Test Load( NUnitProject project, string testFixture )
242 ProjectConfig cfg = project.ActiveConfig;
244 if ( project.IsAssemblyWrapper )
245 return Load( cfg.Assemblies[0].FullPath, testFixture );
247 return Load( project.ProjectPath, cfg.BasePath, cfg.ConfigurationFile, cfg.PrivateBinPath, cfg.TestAssemblies, testFixture );
258 AppDomain.Unload(domain);
259 if ( this.ShadowCopyFiles )
260 DeleteCacheDir( new DirectoryInfo( cachePath ) );
262 catch( CannotUnloadAppDomainException )
264 // TODO: Do something useful. For now we just
265 // leave the orphaned AppDomain "out there"
266 // rather than aborting the application.
275 public static string GetBinPath( string[] assemblies )
277 ArrayList dirs = new ArrayList();
278 string binPath = null;
280 foreach( string path in assemblies )
282 string dir = Path.GetDirectoryName( Path.GetFullPath( path ) );
283 if ( !dirs.Contains( dir ) )
287 if ( binPath == null )
290 binPath = binPath + ";" + dir;
299 #region Counting Tests
301 public int CountTestCases()
303 return Runner.CountTestCases();
306 public int CountTestCases( string testName )
308 return Runner.CountTestCases( testName );
312 public int CountTestCases( string[] testNames )
314 return Runner.CountTestCases( testNames );
319 public ICollection GetCategories()
321 return Runner.GetCategories();
324 #region Running Tests
326 // public TestResult Run(NUnit.Core.EventListener listener, IFilter filter)
328 // return Runner.Run( listener, filter );
331 public void SetFilter( IFilter filter )
333 Runner.SetFilter( filter );
336 public TestResult Run(NUnit.Core.EventListener listener)
338 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
340 this.listener = listener;
341 return Runner.Run( listener );
345 public TestResult Run(NUnit.Core.EventListener listener, string testName)
347 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
349 this.listener = listener;
350 return Runner.Run( listener, testName );
354 public TestResult[] Run(NUnit.Core.EventListener listener, string[] testNames)
356 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
358 this.listener = listener;
359 return Runner.Run( listener, testNames );
363 public void RunTest(NUnit.Core.EventListener listener )
365 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
367 this.listener = listener;
368 Runner.RunTest( listener );
372 public void RunTest(NUnit.Core.EventListener listener, string testName )
374 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
376 this.listener = listener;
377 Runner.RunTest( listener, testName );
381 public void RunTest(NUnit.Core.EventListener listener, string[] testNames)
383 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
385 this.listener = listener;
386 Runner.RunTest( listener, testNames );
390 public void CancelRun()
400 // For now, just publish any unhandled exceptions and let the listener
401 // figure out what to do with them.
402 private void OnUnhandledException( object sender, UnhandledExceptionEventArgs e )
404 this.listener.UnhandledException( (Exception)e.ExceptionObject );
409 #region Helpers Used in AppDomain Creation and Removal
412 /// Construct an application domain for testing a single assembly
414 /// <param name="assemblyFileName">The assembly file name</param>
415 private void CreateDomain( string assemblyFileName )
417 FileInfo testFile = new FileInfo( assemblyFileName );
419 string assemblyPath = Path.GetFullPath( assemblyFileName );
420 string domainName = string.Format( "domain-{0}", Path.GetFileName( assemblyFileName ) );
422 domain = MakeAppDomain( domainName, testFile.DirectoryName, testFile.FullName + ".config", testFile.DirectoryName );
426 /// Construct an application domain for testing multiple assemblies
428 /// <param name="testFileName">The file name of the project file</param>
429 /// <param name="appBase">The application base path</param>
430 /// <param name="configFile">The configuration file to use</param>
431 /// <param name="binPath">The private bin path</param>
432 /// <param name="assemblies">A collection of assemblies to load</param>
433 private void CreateDomain( string testFileName, string appBase, string configFile, string binPath, string[] assemblies )
435 string domainName = string.Format( "domain-{0}", Path.GetFileName( testFileName ) );
436 domain = MakeAppDomain( testFileName, appBase, configFile, binPath );
440 /// This method creates appDomains for the framework.
442 /// <param name="domainName">Name of the domain</param>
443 /// <param name="appBase">ApplicationBase for the domain</param>
444 /// <param name="configFile">ConfigurationFile for the domain</param>
445 /// <param name="binPath">PrivateBinPath for the domain</param>
446 /// <returns></returns>
447 private AppDomain MakeAppDomain( string domainName, string appBase, string configFile, string binPath )
449 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
450 Evidence evidence = new Evidence(baseEvidence);
452 AppDomainSetup setup = new AppDomainSetup();
454 // We always use the same application name
455 setup.ApplicationName = "Tests";
456 // Note that we do NOT
457 // set ShadowCopyDirectories because we rely on the default
458 // setting of ApplicationBase plus PrivateBinPath
459 if ( this.ShadowCopyFiles )
461 setup.ShadowCopyFiles = "true";
462 setup.ShadowCopyDirectories = appBase;
466 setup.ShadowCopyFiles = "false";
469 setup.ApplicationBase = appBase;
470 setup.ConfigurationFile = configFile;
471 setup.PrivateBinPath = binPath;
473 AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
475 if ( this.ShadowCopyFiles )
476 ConfigureCachePath(runnerDomain);
482 /// Set the location for caching and delete any old cache info
484 /// <param name="domain">Our domain</param>
485 private void ConfigureCachePath(AppDomain domain)
487 cachePath = String.Format(@"{0}\{1}",
488 ConfigurationSettings.AppSettings["shadowfiles.path"], DateTime.Now.Ticks);
489 cachePath = Environment.ExpandEnvironmentVariables(cachePath);
491 DirectoryInfo dir = new DirectoryInfo(cachePath);
492 if(dir.Exists) dir.Delete(true);
494 domain.SetCachePath(cachePath);
500 /// Helper method to delete the cache dir. This method deals
501 /// with a bug that occurs when pdb files are marked read-only.
503 /// <param name="cacheDir"></param>
504 private void DeleteCacheDir( DirectoryInfo cacheDir )
508 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )
510 dirInfo.Attributes &= ~FileAttributes.ReadOnly;
511 DeleteCacheDir( dirInfo );
514 foreach( FileInfo fileInfo in cacheDir.GetFiles() )
516 fileInfo.Attributes &= ~FileAttributes.ReadOnly;
519 cacheDir.Delete(true);