1 // ****************************************************************
2 // Copyright 2007, Charlie Poole
3 // This is free software licensed under the NUnit license. You may
4 // obtain a copy of the license at http://nunit.org/?p=license&r=2.4
5 // ****************************************************************
9 using System
.Collections
;
11 using System
.Configuration
;
12 using System
.Diagnostics
;
13 using System
.Security
.Policy
;
19 /// The DomainManager class handles the creation and unloading
20 /// of domains as needed and keeps track of all existing domains.
22 public class DomainManager
: IService
25 private static string shadowCopyPath
;
26 public static string ShadowCopyPath
30 if ( shadowCopyPath
== null )
32 shadowCopyPath
= ConfigurationSettings
.AppSettings
["shadowfiles.path"];
33 if ( shadowCopyPath
== "" || shadowCopyPath
== null )
34 shadowCopyPath
= Path
.Combine( Path
.GetTempPath(), @"nunit20\ShadowCopyCache" );
36 shadowCopyPath
= Environment
.ExpandEnvironmentVariables(shadowCopyPath
);
38 // FIXME: we know that in the config file we have %temp%...
39 if( shadowCopyPath
.IndexOf ( "%temp%\\" ) != -1) {
40 shadowCopyPath
= shadowCopyPath
.Replace( "%temp%\\", Path
.GetTempPath() );
41 if ( Path
.DirectorySeparatorChar
== '/' )
42 shadowCopyPath
= shadowCopyPath
.Replace ( '\\', '/' );
46 return shadowCopyPath
;
51 #region Create and Unload Domains
53 /// Construct an application domain for running a test package
55 /// <param name="package">The TestPackage to be run</param>
56 public AppDomain
CreateDomain( TestPackage package
)
58 FileInfo testFile
= new FileInfo( package
.FullName
);
60 AppDomainSetup setup
= new AppDomainSetup();
62 // We always use the same application name
63 setup
.ApplicationName
= "Tests";
65 string appBase
= package
.BasePath
;
66 if ( appBase
== null || appBase
== string.Empty
)
67 appBase
= testFile
.DirectoryName
;
68 setup
.ApplicationBase
= appBase
;
70 string configFile
= package
.ConfigurationFile
;
71 if ( configFile
== null || configFile
== string.Empty
)
72 configFile
= NUnitProject
.IsProjectFile(testFile
.Name
)
73 ? Path
.GetFileNameWithoutExtension( testFile
.Name
) + ".config"
74 : testFile
.Name
+ ".config";
75 // Note: Mono needs full path to config file...
76 setup
.ConfigurationFile
= Path
.Combine( appBase
, configFile
);
78 string binPath
= package
.PrivateBinPath
;
79 if ( package
.AutoBinPath
)
80 binPath
= GetPrivateBinPath( appBase
, package
.Assemblies
);
81 setup
.PrivateBinPath
= binPath
;
83 if ( package
.GetSetting( "ShadowCopyFiles", true ) )
85 setup
.ShadowCopyFiles
= "true";
86 setup
.ShadowCopyDirectories
= appBase
;
87 setup
.CachePath
= GetCachePath();
90 string domainName
= "domain-" + package
.Name
;
91 Evidence baseEvidence
= AppDomain
.CurrentDomain
.Evidence
;
92 Evidence evidence
= new Evidence(baseEvidence
);
93 AppDomain runnerDomain
= AppDomain
.CreateDomain(domainName
, evidence
, setup
);
95 // Inject assembly resolver into remote domain to help locate our assemblies
96 AssemblyResolver assemblyResolver
= (AssemblyResolver
)runnerDomain
.CreateInstanceFromAndUnwrap(
97 typeof(AssemblyResolver
).Assembly
.CodeBase
,
98 typeof(AssemblyResolver
).FullName
);
100 // Tell resolver to use our core assemblies in the test domain
101 assemblyResolver
.AddFile( typeof( NUnit
.Core
.RemoteTestRunner
).Assembly
.Location
);
102 assemblyResolver
.AddFile( typeof( NUnit
.Core
.ITest
).Assembly
.Location
);
104 // No reference to extensions, so we do it a different way
105 string moduleName
= System
.Diagnostics
.Process
.GetCurrentProcess().MainModule
.FileName
;
106 string nunitDirPath
= Path
.GetDirectoryName(moduleName
);
107 // string coreExtensions = Path.Combine(nunitDirPath, "nunit.core.extensions.dll");
108 // assemblyResolver.AddFile( coreExtensions );
109 //assemblyResolver.AddFiles( nunitDirPath, "*.dll" );
111 string addinsDirPath
= Path
.Combine(nunitDirPath
, "addins");
112 assemblyResolver
.AddDirectory( addinsDirPath
);
114 // HACK: Only pass down our AddinRegistry one level so that tests of NUnit
115 // itself start without any addins defined.
116 if ( !IsTestDomain( AppDomain
.CurrentDomain
) )
117 runnerDomain
.SetData("AddinRegistry", Services
.AddinRegistry
);
122 public void Unload( AppDomain domain
)
124 bool shadowCopy
= domain
.ShadowCopyFiles
;
125 string cachePath
= domain
.SetupInformation
.CachePath
;
126 string domainName
= domain
.FriendlyName
;
130 AppDomain
.Unload(domain
);
134 // We assume that the tests did something bad and just leave
135 // the orphaned AppDomain "out there".
136 // TODO: Something useful.
137 Trace
.WriteLine("Unable to unload AppDomain {0}", domainName
);
138 Trace
.WriteLine(ex
.ToString());
143 DeleteCacheDir(new DirectoryInfo(cachePath
));
148 #region Helper Methods
150 /// Get the location for caching and delete any old cache info
152 private string GetCachePath()
154 int processId
= Process
.GetCurrentProcess().Id
;
155 long ticks
= DateTime
.Now
.Ticks
;
156 string cachePath
= Path
.Combine( ShadowCopyPath
, processId
.ToString() + "_" + ticks
.ToString() );
160 DirectoryInfo dir
= new DirectoryInfo(cachePath
);
161 if(dir
.Exists
) dir
.Delete(true);
165 throw new ApplicationException(
166 string.Format( "Invalid cache path: {0}",cachePath
),
174 /// Helper method to delete the cache dir. This method deals
175 /// with a bug that occurs when files are marked read-only
176 /// and deletes each file separately in order to give better
177 /// exception information when problems occur.
179 /// TODO: This entire method is problematic. Should we be doing it?
181 /// <param name="cacheDir"></param>
182 private void DeleteCacheDir( DirectoryInfo cacheDir
)
184 // Debug.WriteLine( "Modules:");
185 // foreach( ProcessModule module in Process.GetCurrentProcess().Modules )
186 // Debug.WriteLine( module.ModuleName );
191 foreach( DirectoryInfo dirInfo
in cacheDir
.GetDirectories() )
192 DeleteCacheDir( dirInfo
);
194 foreach( FileInfo fileInfo
in cacheDir
.GetFiles() )
196 fileInfo
.Attributes
= FileAttributes
.Normal
;
201 catch( Exception ex
)
203 Debug
.WriteLine( string.Format(
204 "Error deleting {0}, {1}", fileInfo
.Name
, ex
.Message
) );
208 cacheDir
.Attributes
= FileAttributes
.Normal
;
214 catch( Exception ex
)
216 Debug
.WriteLine( string.Format(
217 "Error deleting {0}, {1}", cacheDir
.Name
, ex
.Message
) );
222 private bool IsTestDomain(AppDomain domain
)
224 return domain
.FriendlyName
.StartsWith( "domain-" );
227 public static string GetPrivateBinPath( string basePath
, IList assemblies
)
229 StringBuilder sb
= new StringBuilder(200);
230 ArrayList dirList
= new ArrayList();
232 foreach( string assembly
in assemblies
)
234 string dir
= PathUtils
.RelativePath( basePath
, Path
.GetDirectoryName( assembly
) );
235 if ( dir
!= null && dir
!= "." && !dirList
.Contains( dir
) )
239 sb
.Append( Path
.PathSeparator
);
244 return sb
.Length
== 0 ? null : sb
.ToString();
247 public static void DeleteShadowCopyPath()
249 if ( Directory
.Exists( ShadowCopyPath
) )
250 Directory
.Delete( ShadowCopyPath
, true );
254 #region IService Members
256 public void UnloadService()
258 // TODO: Add DomainManager.UnloadService implementation
261 public void InitializeService()
263 // TODO: Add DomainManager.InitializeService implementation