Copied remotely
[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                         object obj = runnerDomain.CreateInstanceAndUnwrap(
124                                 typeof(RemoteTestRunner).Assembly.FullName, 
125                                 typeof(RemoteTestRunner).FullName,
126                                 false, BindingFlags.Default,null,null,null,null,null);
127                         
128                         RemoteTestRunner runner = (RemoteTestRunner) obj;
129
130                         runner.Out = this.outWriter;
131                         runner.Error = this.errorWriter;
132
133                         return runner;
134                 }
135
136                 public Version FrameworkVersion
137                 {
138                         get { return Runner.FrameworkVersion; }
139                 }
140
141                 public bool ShadowCopyFiles
142                 {
143                         get { return shadowCopyFiles; }
144                         set
145                         {
146                                 if ( this.domain != null )
147                                         throw new ArgumentException( "ShadowCopyFiles may not be set after domain is created" );
148                                 shadowCopyFiles = value;
149                         }
150                 }
151
152                 public TestResult[] Results
153                 {
154                         get { return Runner.Results; }
155                 }
156
157                 public TestResult Result
158                 {
159                         get { return Runner.Result; }
160                 }
161
162                 #endregion
163
164                 #region Constructors
165
166                 public TestDomain( TextWriter outWriter, TextWriter errorWriter )
167                 { 
168                         this.outWriter = outWriter;
169                         this.errorWriter = errorWriter;
170                 }
171
172                 public TestDomain() : this( TextWriter.Null, TextWriter.Null ) { }
173
174                 #endregion
175
176                 #region Loading and Unloading Tests
177
178                 public Test Load( string assemblyFileName )
179                 {
180                         return Load( assemblyFileName, string.Empty );
181                 }
182
183                 public Test Load(string assemblyFileName, string testFixture)
184                 {
185                         Unload();
186
187                         try
188                         {
189                                 CreateDomain( assemblyFileName );
190                                 string assemblyPath = Path.GetFullPath( assemblyFileName );
191
192                                 if ( testFixture != null && testFixture != string.Empty )
193                                         return Runner.Load( assemblyPath, testFixture );
194                                 else
195                                         return Runner.Load( assemblyPath );
196                         }
197                         catch
198                         {
199                                 Unload();
200                                 throw;
201                         }
202                 }
203
204                 public Test Load( string testFileName, string[] assemblies )
205                 {
206                         return Load( testFileName, assemblies, null );
207                 }
208
209                 public Test Load( string testFileName, string[] assemblies, string testFixture )
210                 {
211                         FileInfo testFile = new FileInfo( testFileName );               
212                         return Load( testFileName, testFile.DirectoryName, testFile.FullName + ".config", GetBinPath(assemblies), assemblies, testFixture );
213                 }
214
215                 public Test Load( string testFileName, string appBase, string configFile, string binPath, string[] assemblies, string testFixture )
216                 {
217                         Unload();
218
219                         try
220                         {
221                                 CreateDomain( testFileName, appBase, configFile, binPath, assemblies );
222
223                                 if ( testFixture != null )
224                                         return Runner.Load( testFileName, assemblies, testFixture );
225                                 else
226                                         return Runner.Load( testFileName, assemblies );
227                         }
228                         catch
229                         {
230                                 Unload();
231                                 throw;
232                         }
233                 }
234
235                 public Test Load( NUnitProject project )
236                 {
237                         return Load( project, null );
238                 }
239
240                 public Test Load( NUnitProject project, string testFixture )
241                 {
242                         ProjectConfig cfg = project.ActiveConfig;
243
244                         if ( project.IsAssemblyWrapper )
245                                 return Load( cfg.Assemblies[0].FullPath, testFixture );
246                         else
247                                 return Load( project.ProjectPath, cfg.BasePath, cfg.ConfigurationFile, cfg.PrivateBinPath, cfg.TestAssemblies, testFixture );
248                 }
249
250                 public void Unload()
251                 {
252                         testRunner = null;
253
254                         if(domain != null) 
255                         {
256                                 try
257                                 {
258                                         AppDomain.Unload(domain);
259                                         if ( this.ShadowCopyFiles )
260                                                 DeleteCacheDir( new DirectoryInfo( cachePath ) );
261                                 }
262                                 catch( CannotUnloadAppDomainException )
263                                 {
264                                         // TODO: Do something useful. For now we just
265                                         // leave the orphaned AppDomain "out there"
266                                         // rather than aborting the application.
267                                 }
268                                 finally
269                                 {
270                                         domain = null;
271                                 }
272                         }
273                 }
274
275                 public static string GetBinPath( string[] assemblies )
276                 {
277                         ArrayList dirs = new ArrayList();
278                         string binPath = null;
279
280                         foreach( string path in assemblies )
281                         {
282                                 string dir = Path.GetDirectoryName( Path.GetFullPath( path ) );
283                                 if ( !dirs.Contains( dir ) )
284                                 {
285                                         dirs.Add( dir );
286
287                                         if ( binPath == null )
288                                                 binPath = dir;
289                                         else
290                                                 binPath = binPath + ";" + dir;
291                                 }
292                         }
293
294                         return binPath;
295                 }
296
297                 #endregion
298
299                 #region Counting Tests
300
301                 public int CountTestCases()
302                 {
303                         return Runner.CountTestCases();
304                 }
305
306                 public int CountTestCases( string testName )
307                 {
308                         return Runner.CountTestCases( testName );
309                 }
310
311                 
312                 public int CountTestCases( string[] testNames )
313                 {
314                         return Runner.CountTestCases( testNames );
315                 }
316
317                 #endregion
318
319                 public ICollection GetCategories()
320                 {
321                         return Runner.GetCategories();
322                 }
323
324                 #region Running Tests
325
326 //              public TestResult Run(NUnit.Core.EventListener listener, IFilter filter)
327 //              {
328 //                      return Runner.Run( listener, filter );
329 //              }
330
331                 public void SetFilter( IFilter filter )
332                 {
333                         Runner.SetFilter( filter );
334                 }
335
336                 public TestResult Run(NUnit.Core.EventListener listener)
337                 {
338                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
339                         {
340                                 this.listener = listener;
341                                 return Runner.Run( listener );
342                         }
343                 }
344                 
345                 public TestResult Run(NUnit.Core.EventListener listener, string testName)
346                 {
347                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
348                         {
349                                 this.listener = listener;
350                                 return Runner.Run( listener, testName );
351                         }
352                 }
353
354                 public TestResult[] Run(NUnit.Core.EventListener listener, string[] testNames)
355                 {
356                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
357                         {
358                                 this.listener = listener;
359                                 return Runner.Run( listener, testNames );
360                         }
361                 }
362
363                 public void RunTest(NUnit.Core.EventListener listener )
364                 {
365                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
366                         {
367                                 this.listener = listener;
368                                 Runner.RunTest( listener );
369                         }
370                 }
371                 
372                 public void RunTest(NUnit.Core.EventListener listener, string testName )
373                 {
374                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
375                         {
376                                 this.listener = listener;
377                                 Runner.RunTest( listener, testName );
378                         }
379                 }
380
381                 public void RunTest(NUnit.Core.EventListener listener, string[] testNames)
382                 {
383                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
384                         {
385                                 this.listener = listener;
386                                 Runner.RunTest( listener, testNames );
387                         }
388                 }
389
390                 public void CancelRun()
391                 {
392                         Runner.CancelRun();
393                 }
394
395                 public void Wait()
396                 {
397                         Runner.Wait();
398                 }
399
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 )
403                 {
404                         this.listener.UnhandledException( (Exception)e.ExceptionObject );
405                 }
406
407                 #endregion
408
409                 #region Helpers Used in AppDomain Creation and Removal
410
411                 /// <summary>
412                 /// Construct an application domain for testing a single assembly
413                 /// </summary>
414                 /// <param name="assemblyFileName">The assembly file name</param>
415                 private void CreateDomain( string assemblyFileName )
416                 {
417                         FileInfo testFile = new FileInfo( assemblyFileName );
418                         
419                         string assemblyPath = Path.GetFullPath( assemblyFileName );
420                         string domainName = string.Format( "domain-{0}", Path.GetFileName( assemblyFileName ) );
421
422                         domain = MakeAppDomain( domainName, testFile.DirectoryName, testFile.FullName + ".config", testFile.DirectoryName );
423                 }
424
425                 /// <summary>
426                 /// Construct an application domain for testing multiple assemblies
427                 /// </summary>
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 )
434                 {
435                         string domainName = string.Format( "domain-{0}", Path.GetFileName( testFileName ) );
436                         domain = MakeAppDomain( testFileName, appBase, configFile, binPath );
437                 }
438
439                 /// <summary>
440                 /// This method creates appDomains for the framework.
441                 /// </summary>
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 )
448                 {
449                         Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
450                         Evidence evidence = new Evidence(baseEvidence);
451
452                         AppDomainSetup setup = new AppDomainSetup();
453
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 )
460                         {
461                                 setup.ShadowCopyFiles = "true";
462                                 setup.ShadowCopyDirectories = appBase;
463                         }
464                         else
465                         {
466                                 setup.ShadowCopyFiles = "false";
467                         }
468
469                         setup.ApplicationBase = appBase;
470                         setup.ConfigurationFile =  configFile;
471                         setup.PrivateBinPath = binPath;
472
473                         AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
474                         
475                         if ( this.ShadowCopyFiles )
476                                 ConfigureCachePath(runnerDomain);
477
478                         return runnerDomain;
479                 }
480
481                 /// <summary>
482                 /// Set the location for caching and delete any old cache info
483                 /// </summary>
484                 /// <param name="domain">Our domain</param>
485                 private void ConfigureCachePath(AppDomain domain)
486                 {
487                         cachePath = String.Format(@"{0}\{1}", 
488                                 ConfigurationSettings.AppSettings["shadowfiles.path"], DateTime.Now.Ticks);
489                         cachePath = Environment.ExpandEnvironmentVariables(cachePath);
490
491                         DirectoryInfo dir = new DirectoryInfo(cachePath);
492                         if(dir.Exists) dir.Delete(true);
493
494                         domain.SetCachePath(cachePath);
495
496                         return;
497                 }
498
499                 /// <summary>
500                 /// Helper method to delete the cache dir. This method deals 
501                 /// with a bug that occurs when pdb files are marked read-only.
502                 /// </summary>
503                 /// <param name="cacheDir"></param>
504                 private void DeleteCacheDir( DirectoryInfo cacheDir )
505                 {
506                         if(cacheDir.Exists)
507                         {
508                                 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )
509                                 {
510                                         dirInfo.Attributes &= ~FileAttributes.ReadOnly;
511                                         DeleteCacheDir( dirInfo );
512                                 }
513
514                                 foreach( FileInfo fileInfo in cacheDir.GetFiles() )
515                                 {
516                                         fileInfo.Attributes &= ~FileAttributes.ReadOnly;
517                                 }
518
519                                 cacheDir.Delete(true);
520                         }
521                 }
522                 
523                 #endregion
524         }
525 }