1 // ****************************************************************
\r
2 // Copyright 2007, Charlie Poole
\r
3 // This is free software licensed under the NUnit license. You may
\r
4 // obtain a copy of the license at http://nunit.org/?p=license&r=2.4
\r
5 // ****************************************************************
\r
9 using System.Collections;
\r
11 using System.Threading;
\r
12 using System.Configuration;
\r
13 using System.Diagnostics;
\r
14 using System.Security.Policy;
\r
17 namespace NUnit.Util
\r
20 /// The DomainManager class handles the creation and unloading
\r
21 /// of domains as needed and keeps track of all existing domains.
\r
23 public class DomainManager : IService
\r
26 private static string shadowCopyPath;
\r
27 public static string ShadowCopyPath
\r
31 if ( shadowCopyPath == null )
\r
33 shadowCopyPath = ConfigurationSettings.AppSettings["shadowfiles.path"];
\r
34 if ( shadowCopyPath == "" || shadowCopyPath== null )
\r
35 shadowCopyPath = Path.Combine( Path.GetTempPath(), @"nunit20\ShadowCopyCache" );
\r
37 shadowCopyPath = Environment.ExpandEnvironmentVariables(shadowCopyPath);
\r
39 // FIXME: we know that in the config file we have %temp%...
\r
40 if( shadowCopyPath.IndexOf ( "%temp%\\" ) != -1) {
\r
41 shadowCopyPath = shadowCopyPath.Replace( "%temp%\\", Path.GetTempPath() );
\r
42 if ( Path.DirectorySeparatorChar == '/' )
\r
43 shadowCopyPath = shadowCopyPath.Replace ( '\\', '/' );
\r
47 return shadowCopyPath;
\r
52 #region Create and Unload Domains
\r
54 /// Construct an application domain for running a test package
\r
56 /// <param name="package">The TestPackage to be run</param>
\r
57 public AppDomain CreateDomain( TestPackage package )
\r
59 FileInfo testFile = new FileInfo( package.FullName );
\r
61 AppDomainSetup setup = new AppDomainSetup();
\r
63 // We always use the same application name
\r
64 setup.ApplicationName = "Tests";
\r
66 string appBase = package.BasePath;
\r
67 if ( appBase == null || appBase == string.Empty )
\r
68 appBase = testFile.DirectoryName;
\r
69 setup.ApplicationBase = appBase;
\r
71 string configFile = package.ConfigurationFile;
\r
72 if ( configFile == null || configFile == string.Empty )
\r
73 configFile = NUnitProject.IsProjectFile(testFile.Name)
\r
74 ? Path.GetFileNameWithoutExtension( testFile.Name ) + ".config"
\r
75 : testFile.Name + ".config";
\r
76 // Note: Mono needs full path to config file...
\r
77 setup.ConfigurationFile = Path.Combine( appBase, configFile );
\r
79 string binPath = package.PrivateBinPath;
\r
80 if ( package.AutoBinPath )
\r
81 binPath = GetPrivateBinPath( appBase, package.Assemblies );
\r
82 setup.PrivateBinPath = binPath;
\r
84 if ( package.GetSetting( "ShadowCopyFiles", true ) )
\r
86 setup.ShadowCopyFiles = "true";
\r
87 setup.ShadowCopyDirectories = appBase;
\r
88 setup.CachePath = GetCachePath();
\r
91 string domainName = "domain-" + package.Name;
\r
92 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
\r
93 Evidence evidence = new Evidence(baseEvidence);
\r
94 AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
\r
96 // Inject assembly resolver into remote domain to help locate our assemblies
\r
97 AssemblyResolver assemblyResolver = (AssemblyResolver)runnerDomain.CreateInstanceFromAndUnwrap(
\r
98 typeof(AssemblyResolver).Assembly.CodeBase,
\r
99 typeof(AssemblyResolver).FullName);
\r
101 // Tell resolver to use our core assemblies in the test domain
\r
102 assemblyResolver.AddFile( typeof( NUnit.Core.RemoteTestRunner ).Assembly.Location );
\r
103 assemblyResolver.AddFile( typeof( NUnit.Core.ITest ).Assembly.Location );
\r
105 // No reference to extensions, so we do it a different way
\r
106 string moduleName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
\r
107 string nunitDirPath = Path.GetDirectoryName(moduleName);
\r
108 // string coreExtensions = Path.Combine(nunitDirPath, "nunit.core.extensions.dll");
\r
109 // assemblyResolver.AddFile( coreExtensions );
\r
110 //assemblyResolver.AddFiles( nunitDirPath, "*.dll" );
\r
112 string addinsDirPath = Path.Combine(nunitDirPath, "addins");
\r
113 assemblyResolver.AddDirectory( addinsDirPath );
\r
115 // HACK: Only pass down our AddinRegistry one level so that tests of NUnit
\r
116 // itself start without any addins defined.
\r
117 if ( !IsTestDomain( AppDomain.CurrentDomain ) )
\r
118 runnerDomain.SetData("AddinRegistry", Services.AddinRegistry);
\r
120 return runnerDomain;
\r
123 public void Unload( AppDomain domain )
\r
125 new DomainUnloader(domain).Unload();
\r
129 #region Nested DomainUnloader Class
\r
130 class DomainUnloader
\r
132 private Thread thread;
\r
133 private AppDomain domain;
\r
135 public DomainUnloader(AppDomain domain)
\r
137 this.domain = domain;
\r
140 public void Unload()
\r
142 thread = new Thread(new ThreadStart(UnloadOnThread));
\r
144 if (!thread.Join(20000))
\r
146 Trace.WriteLine("Unable to unload AppDomain {0}", domain.FriendlyName);
\r
147 Trace.WriteLine("Unload thread timed out");
\r
151 private void UnloadOnThread()
\r
153 bool shadowCopy = domain.ShadowCopyFiles;
\r
154 string cachePath = domain.SetupInformation.CachePath;
\r
155 string domainName = domain.FriendlyName;
\r
159 AppDomain.Unload(domain);
\r
161 catch (Exception ex)
\r
163 // We assume that the tests did something bad and just leave
\r
164 // the orphaned AppDomain "out there".
\r
165 // TODO: Something useful.
\r
166 Trace.WriteLine("Unable to unload AppDomain {0}", domainName);
\r
167 Trace.WriteLine(ex.ToString());
\r
172 DeleteCacheDir(new DirectoryInfo(cachePath));
\r
178 #region Helper Methods
\r
180 /// Get the location for caching and delete any old cache info
\r
182 private string GetCachePath()
\r
184 int processId = Process.GetCurrentProcess().Id;
\r
185 long ticks = DateTime.Now.Ticks;
\r
186 string cachePath = Path.Combine( ShadowCopyPath, processId.ToString() + "_" + ticks.ToString() );
\r
190 DirectoryInfo dir = new DirectoryInfo(cachePath);
\r
191 if(dir.Exists) dir.Delete(true);
\r
193 catch( Exception ex)
\r
195 throw new ApplicationException(
\r
196 string.Format( "Invalid cache path: {0}",cachePath ),
\r
204 /// Helper method to delete the cache dir. This method deals
\r
205 /// with a bug that occurs when files are marked read-only
\r
206 /// and deletes each file separately in order to give better
\r
207 /// exception information when problems occur.
\r
209 /// TODO: This entire method is problematic. Should we be doing it?
\r
211 /// <param name="cacheDir"></param>
\r
212 private static void DeleteCacheDir( DirectoryInfo cacheDir )
\r
214 // Debug.WriteLine( "Modules:");
\r
215 // foreach( ProcessModule module in Process.GetCurrentProcess().Modules )
\r
216 // Debug.WriteLine( module.ModuleName );
\r
219 if(cacheDir.Exists)
\r
221 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )
\r
222 DeleteCacheDir( dirInfo );
\r
224 foreach( FileInfo fileInfo in cacheDir.GetFiles() )
\r
226 fileInfo.Attributes = FileAttributes.Normal;
\r
231 catch( Exception ex )
\r
233 Debug.WriteLine( string.Format(
\r
234 "Error deleting {0}, {1}", fileInfo.Name, ex.Message ) );
\r
238 cacheDir.Attributes = FileAttributes.Normal;
\r
244 catch( Exception ex )
\r
246 Debug.WriteLine( string.Format(
\r
247 "Error deleting {0}, {1}", cacheDir.Name, ex.Message ) );
\r
252 private bool IsTestDomain(AppDomain domain)
\r
254 return domain.FriendlyName.StartsWith( "domain-" );
\r
257 public static string GetPrivateBinPath( string basePath, IList assemblies )
\r
259 StringBuilder sb = new StringBuilder(200);
\r
260 ArrayList dirList = new ArrayList();
\r
262 foreach( string assembly in assemblies )
\r
264 string dir = PathUtils.RelativePath( basePath, Path.GetDirectoryName( assembly ) );
\r
265 if ( dir != null && dir != "." && !dirList.Contains( dir ) )
\r
267 dirList.Add( dir );
\r
268 if ( sb.Length > 0 )
\r
269 sb.Append( Path.PathSeparator );
\r
274 return sb.Length == 0 ? null : sb.ToString();
\r
277 public static void DeleteShadowCopyPath()
\r
279 if ( Directory.Exists( ShadowCopyPath ) )
\r
280 Directory.Delete( ShadowCopyPath, true );
\r
284 #region IService Members
\r
286 public void UnloadService()
\r
288 // TODO: Add DomainManager.UnloadService implementation
\r
291 public void InitializeService()
\r
293 // TODO: Add DomainManager.InitializeService implementation
\r