This commit was manufactured by cvs2svn to create branch 'mono-1-0'.
[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                 #endregion
79
80                 #region Properties
81
82                 public TextWriter Out
83                 {
84                         get { return outWriter; }
85                         set { outWriter = value; }
86                 }
87
88                 public TextWriter Error
89                 {
90                         get { return errorWriter; }
91                         set { errorWriter = value; }
92                 }
93
94                 private TestRunner Runner
95                 {
96                         get 
97                         {
98                                 if ( testRunner == null )
99                                         testRunner = MakeRemoteTestRunner( domain );
100
101                                 return testRunner; 
102                         }
103                 }
104
105                 public bool DisplayTestLabels
106                 {
107                         get { return Runner.DisplayTestLabels; }
108                         set { Runner.DisplayTestLabels = value; }
109                 }
110
111                 private TestRunner MakeRemoteTestRunner( AppDomain runnerDomain )
112                 {
113                         object obj = runnerDomain.CreateInstanceAndUnwrap(
114                                 typeof(RemoteTestRunner).Assembly.FullName, 
115                                 typeof(RemoteTestRunner).FullName,
116                                 false, BindingFlags.Default,null,null,null,null,null);
117                         
118                         RemoteTestRunner runner = (RemoteTestRunner) obj;
119
120                         runner.Out = this.outWriter;
121                         runner.Error = this.errorWriter;
122
123                         return runner;
124                 }
125
126                 public Version FrameworkVersion
127                 {
128                         get { return Runner.FrameworkVersion; }
129                 }
130
131                 #endregion
132
133                 #region Constructors
134
135                 public TestDomain( TextWriter outWriter, TextWriter errorWriter )
136                 { 
137                         this.outWriter = outWriter;
138                         this.errorWriter = errorWriter;
139                 }
140
141                 public TestDomain() : this( TextWriter.Null, TextWriter.Null ) { }
142
143                 #endregion
144
145                 #region Loading and Unloading Tests
146
147                 public Test Load( string assemblyFileName )
148                 {
149                         return Load( assemblyFileName, string.Empty );
150                 }
151
152                 public Test Load(string assemblyFileName, string testFixture)
153                 {
154                         Unload();
155
156                         try
157                         {
158                                 CreateDomain( assemblyFileName );
159                                 string assemblyPath = Path.GetFullPath( assemblyFileName );
160
161                                 if ( testFixture != null && testFixture != string.Empty )
162                                         return Runner.Load( assemblyPath, testFixture );
163                                 else
164                                         return Runner.Load( assemblyPath );
165                         }
166                         catch
167                         {
168                                 Unload();
169                                 throw;
170                         }
171                 }
172
173                 public Test Load( string testFileName, string[] assemblies )
174                 {
175                         return Load( testFileName, assemblies, null );
176                 }
177
178                 public Test Load( string testFileName, string[] assemblies, string testFixture )
179                 {
180                         FileInfo testFile = new FileInfo( testFileName );               
181                         return Load( testFileName, testFile.DirectoryName, testFile.FullName + ".config", GetBinPath(assemblies), assemblies, testFixture );
182                 }
183
184                 public Test Load( string testFileName, string appBase, string configFile, string binPath, string[] assemblies, string testFixture )
185                 {
186                         Unload();
187
188                         try
189                         {
190                                 CreateDomain( testFileName, appBase, configFile, binPath, assemblies );
191
192                                 if ( testFixture != null )
193                                         return Runner.Load( testFileName, assemblies, testFixture );
194                                 else
195                                         return Runner.Load( testFileName, assemblies );
196                         }
197                         catch
198                         {
199                                 Unload();
200                                 throw;
201                         }
202                 }
203
204                 public Test Load( NUnitProject project )
205                 {
206                         if ( project.IsAssemblyWrapper )
207                                 return Load( project.ActiveConfig.Assemblies[0].FullPath );
208                         else
209                                 return Load( project.ProjectPath, project.ActiveConfig.TestAssemblies );
210                 }
211
212                 public Test Load( NUnitProject project, string testFixture )
213                 {
214                         if ( project.IsAssemblyWrapper )
215                                 return Load( project.ActiveConfig.Assemblies[0].FullPath, testFixture );
216                         else
217                                 return Load( project.ProjectPath, project.ActiveConfig.TestAssemblies, testFixture );
218                 }
219
220                 public void Unload()
221                 {
222                         testRunner = null;
223
224                         if(domain != null) 
225                         {
226                                 try
227                                 {
228                                         AppDomain.Unload(domain);
229                                         DirectoryInfo cacheDir = new DirectoryInfo(cachePath);
230                                         if(cacheDir.Exists) cacheDir.Delete(true);
231                                 }
232                                 catch( CannotUnloadAppDomainException )
233                                 {
234                                         // TODO: Do something useful. For now we just
235                                         // leave the orphaned AppDomain "out there"
236                                         // rather than aborting the application.
237                                 }
238                                 finally
239                                 {
240                                         domain = null;
241                                 }
242                         }
243                 }
244
245                 public static string GetBinPath( string[] assemblies )
246                 {
247                         ArrayList dirs = new ArrayList();
248                         string binPath = null;
249
250                         foreach( string path in assemblies )
251                         {
252                                 string dir = Path.GetDirectoryName( Path.GetFullPath( path ) );
253                                 if ( !dirs.Contains( dir ) )
254                                 {
255                                         dirs.Add( dir );
256
257                                         if ( binPath == null )
258                                                 binPath = dir;
259                                         else
260                                                 binPath = binPath + ";" + dir;
261                                 }
262                         }
263
264                         return binPath;
265                 }
266
267                 #endregion
268
269                 #region Counting Tests
270
271                 public int CountTestCases()
272                 {
273                         return Runner.CountTestCases();
274                 }
275
276                 public int CountTestCases( string testName )
277                 {
278                         return Runner.CountTestCases( testName );
279                 }
280
281                 
282                 public int CountTestCases( string[] testNames )
283                 {
284                         return Runner.CountTestCases( testNames );
285                 }
286
287                 #endregion
288
289                 public ICollection GetCategories()
290                 {
291                         return Runner.GetCategories();
292                 }
293
294                 #region Running Tests
295
296 //              public TestResult Run(NUnit.Core.EventListener listener, IFilter filter)
297 //              {
298 //                      return Runner.Run( listener, filter );
299 //              }
300
301                 public void SetFilter( IFilter filter )
302                 {
303                         Runner.SetFilter( filter );
304                 }
305
306                 public TestResult Run(NUnit.Core.EventListener listener)
307                 {
308                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
309                         {
310                                 this.listener = listener;
311                                 return Runner.Run( listener );
312                         }
313                 }
314                 
315                 public TestResult Run(NUnit.Core.EventListener listener, string testName)
316                 {
317                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
318                         {
319                                 this.listener = listener;
320                                 return Runner.Run( listener, testName );
321                         }
322                 }
323
324                 public TestResult[] Run(NUnit.Core.EventListener listener, string[] testNames)
325                 {
326                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
327                         {
328                                 this.listener = listener;
329                                 return Runner.Run( listener, testNames );
330                         }
331                 }
332
333                 public void RunTest(NUnit.Core.EventListener listener )
334                 {
335                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
336                         {
337                                 this.listener = listener;
338                                 Runner.RunTest( listener );
339                         }
340                 }
341                 
342                 public void RunTest(NUnit.Core.EventListener listener, string testName )
343                 {
344                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
345                         {
346                                 this.listener = listener;
347                                 Runner.RunTest( listener, testName );
348                         }
349                 }
350
351                 public void RunTest(NUnit.Core.EventListener listener, string[] testNames)
352                 {
353                         using( new TestExceptionHandler( new UnhandledExceptionEventHandler( OnUnhandledException ) ) )
354                         {
355                                 this.listener = listener;
356                                 Runner.RunTest( listener, testNames );
357                         }
358                 }
359
360                 public void CancelRun()
361                 {
362                         Runner.CancelRun();
363                 }
364
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 )
368                 {
369                         this.listener.UnhandledException( (Exception)e.ExceptionObject );
370                 }
371
372                 #endregion
373
374                 #region Helpers Used in AppDomain Creation
375
376                 /// <summary>
377                 /// Construct an application domain for testing a single assembly
378                 /// </summary>
379                 /// <param name="assemblyFileName">The assembly file name</param>
380                 private void CreateDomain( string assemblyFileName )
381                 {
382                         FileInfo testFile = new FileInfo( assemblyFileName );
383                         
384                         string assemblyPath = Path.GetFullPath( assemblyFileName );
385                         string domainName = string.Format( "domain-{0}", Path.GetFileName( assemblyFileName ) );
386
387                         domain = MakeAppDomain( domainName, testFile.DirectoryName, testFile.FullName + ".config", testFile.DirectoryName );
388                 }
389
390                 /// <summary>
391                 /// Construct an application domain for testing multiple assemblies
392                 /// </summary>
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 )
399                 {
400                         string domainName = string.Format( "domain-{0}", Path.GetFileName( testFileName ) );
401                         domain = MakeAppDomain( testFileName, appBase, configFile, binPath );
402                 }
403
404                 private void CreateDomain( NUnitProject project )
405                 {
406                         ProjectConfig cfg = project.ActiveConfig;
407
408                         if ( project.IsAssemblyWrapper )
409                                 CreateDomain( cfg.Assemblies[0].FullPath );
410                         else
411                                 CreateDomain( project.ProjectPath, cfg.BasePath, cfg.ConfigurationFilePath, cfg.PrivateBinPath, cfg.TestAssemblies );
412                 }
413
414                 /// <summary>
415                 /// This method creates appDomains for the framework.
416                 /// </summary>
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 )
423                 {
424                         Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
425                         Evidence evidence = new Evidence(baseEvidence);
426
427                         AppDomainSetup setup = new AppDomainSetup();
428
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;
436
437                         setup.ApplicationBase = appBase;
438                         setup.ConfigurationFile =  configFile;
439                         setup.PrivateBinPath = binPath;
440
441                         AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
442                         
443                         ConfigureCachePath(runnerDomain);
444
445                         return runnerDomain;
446                 }
447
448                 /// <summary>
449                 /// Set the location for caching and delete any old cache info
450                 /// </summary>
451                 /// <param name="domain">Our domain</param>
452                 private void ConfigureCachePath(AppDomain domain)
453                 {
454                         cachePath = String.Format(@"{0}\{1}", 
455                                 ConfigurationSettings.AppSettings["shadowfiles.path"], DateTime.Now.Ticks);
456                         cachePath = Environment.ExpandEnvironmentVariables(cachePath);
457
458                         DirectoryInfo dir = new DirectoryInfo(cachePath);
459                         if(dir.Exists) dir.Delete(true);
460
461                         domain.SetCachePath(cachePath);
462
463                         return;
464                 }
465
466                 #endregion
467         }
468 }