2009-06-12 Bill Holmes <billholmes54@gmail.com>
[mono.git] / mcs / nunit20 / util / TestDomain.cs
1 #region Copyright (c) 2002-2003, James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole, Philip A. Craig
2 /************************************************************************************
3 '
4 ' Copyright © 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
5 ' Copyright © 2000-2003 Philip A. Craig
6 '
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 
9 ' software.
10
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:
14 '
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.
18 '
19 ' Portions Copyright © 2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
20 ' or Copyright © 2000-2003 Philip A. Craig
21 '
22 ' 2. Altered source versions must be plainly marked as such, and must not be 
23 ' misrepresented as being the original software.
24 '
25 ' 3. This notice may not be removed or altered from any source distribution.
26 '
27 '***********************************************************************************/
28 #endregion
29
30 using System;
31
32 namespace NUnit.Util
33 {
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;
40         using System.IO;
41
42         using NUnit.Core;
43
44         public class TestDomain : TestRunner
45         {
46                 #region Instance Variables
47
48                 /// <summary>
49                 /// The appdomain used  to load tests
50                 /// </summary>
51                 private AppDomain domain; 
52
53                 /// <summary>
54                 /// The path to our cache
55                 /// </summary>
56                 private string cachePath;
57                 
58                 /// <summary>
59                 /// The remote runner loaded in the test appdomain
60                 /// </summary>
61                 private TestRunner testRunner;
62
63                 /// <summary>
64                 /// Writer for console standard output
65                 /// </summary>
66                 private TextWriter outWriter;
67
68                 /// <summary>
69                 /// Writer for console error output
70                 /// </summary>
71                 private TextWriter errorWriter;
72
73                 /// <summary>
74                 /// Holds the event listener while we are running
75                 /// </summary>
76                 private EventListener listener;
77
78                 /// <summary>
79                 /// Indicate whether files should be shadow copied
80                 /// </summary>
81                 private bool shadowCopyFiles = true;
82
83                 #endregion
84
85                 #region Properties
86
87                 public AppDomain AppDomain
88                 {
89                         get { return domain; }
90                 }
91
92                 public TextWriter Out
93                 {
94                         get { return outWriter; }
95                         set { outWriter = value; }
96                 }
97
98                 public TextWriter Error
99                 {
100                         get { return errorWriter; }
101                         set { errorWriter = value; }
102                 }
103
104                 private TestRunner Runner
105                 {
106                         get 
107                         {
108                                 if ( testRunner == null )
109                                         testRunner = MakeRemoteTestRunner( domain );
110
111                                 return testRunner; 
112                         }
113                 }
114
115                 public bool DisplayTestLabels
116                 {
117                         get { return Runner.DisplayTestLabels; }
118                         set { Runner.DisplayTestLabels = value; }
119                 }
120
121                 private TestRunner MakeRemoteTestRunner( AppDomain runnerDomain )
122                 {
123 #if !TARGET_JVM
124                         object obj = runnerDomain.CreateInstanceAndUnwrap(
125                                 typeof(RemoteTestRunner).Assembly.FullName, 
126                                 typeof(RemoteTestRunner).FullName,
127                                 false, BindingFlags.Default,null,null,null,null,null);
128                         
129                         RemoteTestRunner runner = (RemoteTestRunner) obj;
130 #else
131                         RemoteTestRunner runner = new RemoteTestRunner();
132 #endif
133
134                         runner.Out = this.outWriter;
135                         runner.Error = this.errorWriter;
136
137                         return runner;
138                 }
139
140                 public Version FrameworkVersion
141                 {
142                         get { return Runner.FrameworkVersion; }
143                 }
144
145                 public bool ShadowCopyFiles
146                 {
147                         get { return shadowCopyFiles; }
148                         set
149                         {
150                                 if ( this.domain != null )
151                                         throw new ArgumentException( "ShadowCopyFiles may not be set after domain is created" );
152                                 shadowCopyFiles = value;
153                         }
154                 }
155
156                 public TestResult[] Results
157                 {
158                         get { return Runner.Results; }
159                 }
160
161                 public TestResult Result
162                 {
163                         get { return Runner.Result; }
164                 }
165
166                 #endregion
167
168                 #region Constructors
169
170                 public TestDomain( TextWriter outWriter, TextWriter errorWriter )
171                 { 
172                         this.outWriter = outWriter;
173                         this.errorWriter = errorWriter;
174                 }
175
176                 public TestDomain() : this( TextWriter.Null, TextWriter.Null ) { }
177
178                 #endregion
179
180                 #region Loading and Unloading Tests
181
182                 public Test Load( string assemblyFileName )
183                 {
184                         return Load( assemblyFileName, string.Empty );
185                 }
186
187                 public Test Load(string assemblyFileName, string testFixture)
188                 {
189                         Unload();
190
191                         try
192                         {
193                                 CreateDomain( assemblyFileName );
194                                 string assemblyPath = Path.GetFullPath( assemblyFileName );
195
196                                 if ( testFixture != null && testFixture != string.Empty )
197                                         return Runner.Load( assemblyPath, testFixture );
198                                 else
199                                         return Runner.Load( assemblyPath );
200                         }
201                         catch
202                         {
203                                 Unload();
204                                 throw;
205                         }
206                 }
207
208                 public Test Load( string testFileName, string[] assemblies )
209                 {
210                         return Load( testFileName, assemblies, null );
211                 }
212
213                 public Test Load( string testFileName, string[] assemblies, string testFixture )
214                 {
215                         FileInfo testFile = new FileInfo( testFileName );               
216                         return Load( testFileName, testFile.DirectoryName, testFile.FullName + ".config", GetBinPath(assemblies), assemblies, testFixture );
217                 }
218
219                 public Test Load( string testFileName, string appBase, string configFile, string binPath, string[] assemblies, string testFixture )
220                 {
221                         Unload();
222
223                         try
224                         {
225                                 CreateDomain( testFileName, appBase, configFile, binPath, assemblies );
226
227                                 if ( testFixture != null )
228                                         return Runner.Load( testFileName, assemblies, testFixture );
229                                 else
230                                         return Runner.Load( testFileName, assemblies );
231                         }
232                         catch
233                         {
234                                 Unload();
235                                 throw;
236                         }
237                 }
238
239                 public Test Load( NUnitProject project )
240                 {
241                         return Load( project, null );
242                 }
243
244                 public Test Load( NUnitProject project, string testFixture )
245                 {
246                         ProjectConfig cfg = project.ActiveConfig;
247
248                         if ( project.IsAssemblyWrapper )
249                                 return Load( cfg.Assemblies[0].FullPath, testFixture );
250                         else
251                                 return Load( project.ProjectPath, cfg.BasePath, cfg.ConfigurationFile, cfg.PrivateBinPath, cfg.TestAssemblies, testFixture );
252                 }
253
254                 public void Unload()
255                 {
256                         testRunner = null;
257 #if !TARGET_JVM
258                         if(domain != null) 
259                         {
260                                 try
261                                 {
262                                         AppDomain.Unload(domain);
263                                         if ( this.ShadowCopyFiles )
264                                                 DeleteCacheDir( new DirectoryInfo( cachePath ) );
265                                 }
266                                 catch( CannotUnloadAppDomainException )
267                                 {
268                                         // TODO: Do something useful. For now we just
269                                         // leave the orphaned AppDomain "out there"
270                                         // rather than aborting the application.
271                                 }
272                                 finally
273                                 {
274                                         domain = null;
275                                 }
276                         }
277 #endif
278                 }
279
280                 public static string GetBinPath( string[] assemblies )
281                 {
282                         ArrayList dirs = new ArrayList();
283                         string binPath = null;
284
285                         foreach( string path in assemblies )
286                         {
287                                 string dir = Path.GetDirectoryName( Path.GetFullPath( path ) );
288                                 if ( !dirs.Contains( dir ) )
289                                 {
290                                         dirs.Add( dir );
291
292                                         if ( binPath == null )
293                                                 binPath = dir;
294                                         else
295                                                 binPath = binPath + ";" + dir;
296                                 }
297                         }
298
299                         return binPath;
300                 }
301
302                 #endregion
303
304                 #region Counting Tests
305
306                 public int CountTestCases()
307                 {
308                         return Runner.CountTestCases();
309                 }
310
311                 public int CountTestCases( string testName )
312                 {
313                         return Runner.CountTestCases( testName );
314                 }
315
316                 
317                 public int CountTestCases( string[] testNames )
318                 {
319                         return Runner.CountTestCases( testNames );
320                 }
321
322                 #endregion
323
324                 public ICollection GetCategories()
325                 {
326                         return Runner.GetCategories();
327                 }
328
329                 #region Running Tests
330
331 //              public TestResult Run(NUnit.Core.EventListener listener, IFilter filter)
332 //              {
333 //                      return Runner.Run( listener, filter );
334 //              }
335
336                 public void SetFilter( IFilter filter )
337                 {
338                         Runner.SetFilter( filter );
339                 }
340
341                 public TestResult Run(NUnit.Core.EventListener listener)
342                 {
343                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
344                         {
345                                 this.listener = listener;
346                                 return Runner.Run( listener );
347                         }
348                 }
349                 
350                 public TestResult Run(NUnit.Core.EventListener listener, string testName)
351                 {
352                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
353                         {
354                                 this.listener = listener;
355                                 return Runner.Run( listener, testName );
356                         }
357                 }
358
359                 public TestResult[] Run(NUnit.Core.EventListener listener, string[] testNames)
360                 {
361                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
362                         {
363                                 this.listener = listener;
364                                 return Runner.Run( listener, testNames );
365                         }
366                 }
367
368                 public void RunTest(NUnit.Core.EventListener listener )
369                 {
370                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
371                         {
372                                 this.listener = listener;
373                                 Runner.RunTest( listener );
374                         }
375                 }
376                 
377                 public void RunTest(NUnit.Core.EventListener listener, string testName )
378                 {
379                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
380                         {
381                                 this.listener = listener;
382                                 Runner.RunTest( listener, testName );
383                         }
384                 }
385
386                 public void RunTest(NUnit.Core.EventListener listener, string[] testNames)
387                 {
388                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
389                         {
390                                 this.listener = listener;
391                                 Runner.RunTest( listener, testNames );
392                         }
393                 }
394
395                 public void CancelRun()
396                 {
397                         Runner.CancelRun();
398                 }
399
400                 public void Wait()
401                 {
402                         Runner.Wait();
403                 }
404
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 )
408                 {
409                         this.listener.UnhandledException( (Exception)e.ExceptionObject );
410                 }
411
412                 #endregion
413
414                 #region Helpers Used in AppDomain Creation and Removal
415
416                 /// <summary>
417                 /// Construct an application domain for testing a single assembly
418                 /// </summary>
419                 /// <param name="assemblyFileName">The assembly file name</param>
420                 private void CreateDomain( string assemblyFileName )
421                 {
422                         FileInfo testFile = new FileInfo( assemblyFileName );
423                         
424                         string assemblyPath = Path.GetFullPath( assemblyFileName );
425                         string domainName = string.Format( "domain-{0}", Path.GetFileName( assemblyFileName ) );
426
427                         domain = MakeAppDomain( domainName, testFile.DirectoryName, testFile.FullName + ".config", testFile.DirectoryName );
428                 }
429
430                 /// <summary>
431                 /// Construct an application domain for testing multiple assemblies
432                 /// </summary>
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 )
439                 {
440                         string domainName = string.Format( "domain-{0}", Path.GetFileName( testFileName ) );
441                         domain = MakeAppDomain( testFileName, appBase, configFile, binPath );
442                 }
443 #if !TARGET_JVM
444                 /// <summary>
445                 /// This method creates appDomains for the framework.
446                 /// </summary>
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 )
453                 {
454                         Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
455                         Evidence evidence = new Evidence(baseEvidence);
456
457                         AppDomainSetup setup = new AppDomainSetup();
458
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 )
465                         {
466                                 setup.ShadowCopyFiles = "true";
467                                 setup.ShadowCopyDirectories = appBase;
468                         }
469                         else
470                         {
471                                 setup.ShadowCopyFiles = "false";
472                         }
473
474                         setup.ApplicationBase = appBase;
475                         setup.ConfigurationFile =  configFile;
476                         setup.PrivateBinPath = binPath;
477
478                         AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
479                         
480                         if ( this.ShadowCopyFiles )
481                                 ConfigureCachePath(runnerDomain);
482
483                         return runnerDomain;
484                 }
485 #else
486                 private AppDomain MakeAppDomain( string domainName, string appBase, string configFile, string binPath )
487                 {
488                         return AppDomain.CurrentDomain;
489                 }
490 #endif
491
492                 /// <summary>
493                 /// Set the location for caching and delete any old cache info
494                 /// </summary>
495                 /// <param name="domain">Our domain</param>
496                 private void ConfigureCachePath(AppDomain domain)
497                 {
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 ('\\', '/');
505                                 }
506                         }
507
508                         DirectoryInfo dir = new DirectoryInfo(cachePath);
509                         if(dir.Exists) dir.Delete(true);
510
511                         domain.SetCachePath(cachePath);
512
513                         return;
514                 }
515
516                 /// <summary>
517                 /// Helper method to delete the cache dir. This method deals 
518                 /// with a bug that occurs when pdb files are marked read-only.
519                 /// </summary>
520                 /// <param name="cacheDir"></param>
521                 private void DeleteCacheDir( DirectoryInfo cacheDir )
522                 {
523                         if(cacheDir.Exists)
524                         {
525                                 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )
526                                 {
527                                         dirInfo.Attributes &= ~FileAttributes.ReadOnly;
528                                         DeleteCacheDir( dirInfo );
529                                 }
530
531                                 foreach( FileInfo fileInfo in cacheDir.GetFiles() )
532                                 {
533                                         fileInfo.Attributes &= ~FileAttributes.ReadOnly;
534                                 }
535
536                                 cacheDir.Delete(true);
537                         }
538                 }
539                 
540                 #endregion
541         }
542 }