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 )
124 object obj = runnerDomain.CreateInstanceAndUnwrap(
125 typeof(RemoteTestRunner).Assembly.FullName,
126 typeof(RemoteTestRunner).FullName,
127 false, BindingFlags.Default,null,null,null,null,null);
129 RemoteTestRunner runner = (RemoteTestRunner) obj;
131 RemoteTestRunner runner = new RemoteTestRunner();
134 runner.Out = this.outWriter;
135 runner.Error = this.errorWriter;
140 public Version FrameworkVersion
142 get { return Runner.FrameworkVersion; }
145 public bool ShadowCopyFiles
147 get { return shadowCopyFiles; }
150 if ( this.domain != null )
151 throw new ArgumentException( "ShadowCopyFiles may not be set after domain is created" );
152 shadowCopyFiles = value;
156 public TestResult[] Results
158 get { return Runner.Results; }
161 public TestResult Result
163 get { return Runner.Result; }
170 public TestDomain( TextWriter outWriter, TextWriter errorWriter )
172 this.outWriter = outWriter;
173 this.errorWriter = errorWriter;
176 public TestDomain() : this( TextWriter.Null, TextWriter.Null ) { }
180 #region Loading and Unloading Tests
182 public Test Load( string assemblyFileName )
184 return Load( assemblyFileName, string.Empty );
187 public Test Load(string assemblyFileName, string testFixture)
193 CreateDomain( assemblyFileName );
194 string assemblyPath = Path.GetFullPath( assemblyFileName );
196 if ( testFixture != null && testFixture != string.Empty )
197 return Runner.Load( assemblyPath, testFixture );
199 return Runner.Load( assemblyPath );
208 public Test Load( string testFileName, string[] assemblies )
210 return Load( testFileName, assemblies, null );
213 public Test Load( string testFileName, string[] assemblies, string testFixture )
215 FileInfo testFile = new FileInfo( testFileName );
216 return Load( testFileName, testFile.DirectoryName, testFile.FullName + ".config", GetBinPath(assemblies), assemblies, testFixture );
219 public Test Load( string testFileName, string appBase, string configFile, string binPath, string[] assemblies, string testFixture )
225 CreateDomain( testFileName, appBase, configFile, binPath, assemblies );
227 if ( testFixture != null )
228 return Runner.Load( testFileName, assemblies, testFixture );
230 return Runner.Load( testFileName, assemblies );
239 public Test Load( NUnitProject project )
241 return Load( project, null );
244 public Test Load( NUnitProject project, string testFixture )
246 ProjectConfig cfg = project.ActiveConfig;
248 if ( project.IsAssemblyWrapper )
249 return Load( cfg.Assemblies[0].FullPath, testFixture );
251 return Load( project.ProjectPath, cfg.BasePath, cfg.ConfigurationFile, cfg.PrivateBinPath, cfg.TestAssemblies, testFixture );
262 AppDomain.Unload(domain);
263 if ( this.ShadowCopyFiles )
264 DeleteCacheDir( new DirectoryInfo( cachePath ) );
266 catch( CannotUnloadAppDomainException )
268 // TODO: Do something useful. For now we just
269 // leave the orphaned AppDomain "out there"
270 // rather than aborting the application.
280 public static string GetBinPath( string[] assemblies )
282 ArrayList dirs = new ArrayList();
283 string binPath = null;
285 foreach( string path in assemblies )
287 string dir = Path.GetDirectoryName( Path.GetFullPath( path ) );
288 if ( !dirs.Contains( dir ) )
292 if ( binPath == null )
295 binPath = binPath + ";" + dir;
304 #region Counting Tests
306 public int CountTestCases()
308 return Runner.CountTestCases();
311 public int CountTestCases( string testName )
313 return Runner.CountTestCases( testName );
317 public int CountTestCases( string[] testNames )
319 return Runner.CountTestCases( testNames );
324 public ICollection GetCategories()
326 return Runner.GetCategories();
329 #region Running Tests
331 // public TestResult Run(NUnit.Core.EventListener listener, IFilter filter)
333 // return Runner.Run( listener, filter );
336 public void SetFilter( IFilter filter )
338 Runner.SetFilter( filter );
341 public TestResult Run(NUnit.Core.EventListener listener)
343 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
345 this.listener = listener;
346 return Runner.Run( listener );
350 public TestResult Run(NUnit.Core.EventListener listener, string testName)
352 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
354 this.listener = listener;
355 return Runner.Run( listener, testName );
359 public TestResult[] Run(NUnit.Core.EventListener listener, string[] testNames)
361 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
363 this.listener = listener;
364 return Runner.Run( listener, testNames );
368 public void RunTest(NUnit.Core.EventListener listener )
370 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
372 this.listener = listener;
373 Runner.RunTest( listener );
377 public void RunTest(NUnit.Core.EventListener listener, string testName )
379 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
381 this.listener = listener;
382 Runner.RunTest( listener, testName );
386 public void RunTest(NUnit.Core.EventListener listener, string[] testNames)
388 using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
390 this.listener = listener;
391 Runner.RunTest( listener, testNames );
395 public void CancelRun()
405 // For now, just publish any unhandled exceptions and let the listener
406 // figure out what to do with them.
407 private void OnUnhandledException( object sender, UnhandledExceptionEventArgs e )
409 this.listener.UnhandledException( (Exception)e.ExceptionObject );
414 #region Helpers Used in AppDomain Creation and Removal
417 /// Construct an application domain for testing a single assembly
419 /// <param name="assemblyFileName">The assembly file name</param>
420 private void CreateDomain( string assemblyFileName )
422 FileInfo testFile = new FileInfo( assemblyFileName );
424 string assemblyPath = Path.GetFullPath( assemblyFileName );
425 string domainName = string.Format( "domain-{0}", Path.GetFileName( assemblyFileName ) );
427 domain = MakeAppDomain( domainName, testFile.DirectoryName, testFile.FullName + ".config", testFile.DirectoryName );
431 /// Construct an application domain for testing multiple assemblies
433 /// <param name="testFileName">The file name of the project file</param>
434 /// <param name="appBase">The application base path</param>
435 /// <param name="configFile">The configuration file to use</param>
436 /// <param name="binPath">The private bin path</param>
437 /// <param name="assemblies">A collection of assemblies to load</param>
438 private void CreateDomain( string testFileName, string appBase, string configFile, string binPath, string[] assemblies )
440 string domainName = string.Format( "domain-{0}", Path.GetFileName( testFileName ) );
441 domain = MakeAppDomain( testFileName, appBase, configFile, binPath );
445 /// This method creates appDomains for the framework.
447 /// <param name="domainName">Name of the domain</param>
448 /// <param name="appBase">ApplicationBase for the domain</param>
449 /// <param name="configFile">ConfigurationFile for the domain</param>
450 /// <param name="binPath">PrivateBinPath for the domain</param>
451 /// <returns></returns>
452 private AppDomain MakeAppDomain( string domainName, string appBase, string configFile, string binPath )
454 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
455 Evidence evidence = new Evidence(baseEvidence);
457 AppDomainSetup setup = new AppDomainSetup();
459 // We always use the same application name
460 setup.ApplicationName = "Tests";
461 // Note that we do NOT
462 // set ShadowCopyDirectories because we rely on the default
463 // setting of ApplicationBase plus PrivateBinPath
464 if ( this.ShadowCopyFiles )
466 setup.ShadowCopyFiles = "true";
467 setup.ShadowCopyDirectories = appBase;
471 setup.ShadowCopyFiles = "false";
474 setup.ApplicationBase = appBase;
475 setup.ConfigurationFile = configFile;
476 setup.PrivateBinPath = binPath;
478 AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
480 if ( this.ShadowCopyFiles )
481 ConfigureCachePath(runnerDomain);
486 private AppDomain MakeAppDomain( string domainName, string appBase, string configFile, string binPath )
488 return AppDomain.CurrentDomain;
493 /// Set the location for caching and delete any old cache info
495 /// <param name="domain">Our domain</param>
496 private void ConfigureCachePath(AppDomain domain)
498 cachePath = String.Format(@"{0}\{1}",
499 ConfigurationSettings.AppSettings["shadowfiles.path"], DateTime.Now.Ticks);
500 cachePath = Environment.ExpandEnvironmentVariables(cachePath);
501 if(cachePath.IndexOf ("%temp%\\") != -1) {
502 cachePath = cachePath.Replace ("%temp%\\", Path.GetTempPath());
503 if (Path.DirectorySeparatorChar == '/') {
504 cachePath = cachePath.Replace ('\\', '/');
508 DirectoryInfo dir = new DirectoryInfo(cachePath);
509 if(dir.Exists) dir.Delete(true);
511 domain.SetCachePath(cachePath);
517 /// Helper method to delete the cache dir. This method deals
518 /// with a bug that occurs when pdb files are marked read-only.
520 /// <param name="cacheDir"></param>
521 private void DeleteCacheDir( DirectoryInfo cacheDir )
525 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )
527 dirInfo.Attributes &= ~FileAttributes.ReadOnly;
528 DeleteCacheDir( dirInfo );
531 foreach( FileInfo fileInfo in cacheDir.GetFiles() )
533 fileInfo.Attributes &= ~FileAttributes.ReadOnly;
536 cacheDir.Delete(true);