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.Configuration;
\r
12 using System.Diagnostics;
\r
13 using System.Security.Policy;
\r
16 namespace NUnit.Util
\r
19 /// The DomainManager class handles the creation and unloading
\r
20 /// of domains as needed and keeps track of all existing domains.
\r
22 public class DomainManager : IService
\r
25 private static string shadowCopyPath;
\r
26 public static string ShadowCopyPath
\r
30 if ( shadowCopyPath == null )
\r
32 shadowCopyPath = ConfigurationSettings.AppSettings["shadowfiles.path"];
\r
33 if ( shadowCopyPath == "" || shadowCopyPath== null )
\r
34 shadowCopyPath = Path.Combine( Path.GetTempPath(), @"nunit20\ShadowCopyCache" );
\r
36 shadowCopyPath = Environment.ExpandEnvironmentVariables(shadowCopyPath);
\r
38 // FIXME: we know that in the config file we have %temp%...
\r
39 if( shadowCopyPath.IndexOf ( "%temp%\\" ) != -1) {
\r
40 shadowCopyPath = shadowCopyPath.Replace( "%temp%\\", Path.GetTempPath() );
\r
41 if ( Path.DirectorySeparatorChar == '/' )
\r
42 shadowCopyPath = shadowCopyPath.Replace ( '\\', '/' );
\r
46 return shadowCopyPath;
\r
51 #region Create and Unload Domains
\r
53 /// Construct an application domain for running a test package
\r
55 /// <param name="package">The TestPackage to be run</param>
\r
56 public AppDomain CreateDomain( TestPackage package )
\r
58 FileInfo testFile = new FileInfo( package.FullName );
\r
60 AppDomainSetup setup = new AppDomainSetup();
\r
62 // We always use the same application name
\r
63 setup.ApplicationName = "Tests";
\r
65 string appBase = package.BasePath;
\r
66 if ( appBase == null || appBase == string.Empty )
\r
67 appBase = testFile.DirectoryName;
\r
68 setup.ApplicationBase = appBase;
\r
70 string configFile = package.ConfigurationFile;
\r
71 if ( configFile == null || configFile == string.Empty )
\r
72 configFile = NUnitProject.IsProjectFile(testFile.Name)
\r
73 ? Path.GetFileNameWithoutExtension( testFile.Name ) + ".config"
\r
74 : testFile.Name + ".config";
\r
75 // Note: Mono needs full path to config file...
\r
76 setup.ConfigurationFile = Path.Combine( appBase, configFile );
\r
78 string binPath = package.PrivateBinPath;
\r
79 if ( package.AutoBinPath )
\r
80 binPath = GetPrivateBinPath( appBase, package.Assemblies );
\r
81 setup.PrivateBinPath = binPath;
\r
83 if ( package.GetSetting( "ShadowCopyFiles", true ) )
\r
85 setup.ShadowCopyFiles = "true";
\r
86 setup.ShadowCopyDirectories = appBase;
\r
87 setup.CachePath = GetCachePath();
\r
90 string domainName = "domain-" + package.Name;
\r
91 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
\r
92 Evidence evidence = new Evidence(baseEvidence);
\r
93 AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
\r
95 // Inject assembly resolver into remote domain to help locate our assemblies
\r
96 AssemblyResolver assemblyResolver = (AssemblyResolver)runnerDomain.CreateInstanceFromAndUnwrap(
\r
97 typeof(AssemblyResolver).Assembly.CodeBase,
\r
98 typeof(AssemblyResolver).FullName);
\r
100 // Tell resolver to use our core assemblies in the test domain
\r
101 assemblyResolver.AddFile( typeof( NUnit.Core.RemoteTestRunner ).Assembly.Location );
\r
102 assemblyResolver.AddFile( typeof( NUnit.Core.ITest ).Assembly.Location );
\r
104 if ((int) Environment.OSVersion.Platform != 6) { // Mono workaround for nunit on OSX breakage.
\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
116 // HACK: Only pass down our AddinRegistry one level so that tests of NUnit
\r
117 // itself start without any addins defined.
\r
118 if ( !IsTestDomain( AppDomain.CurrentDomain ) )
\r
119 runnerDomain.SetData("AddinRegistry", Services.AddinRegistry);
\r
121 return runnerDomain;
\r
124 public void Unload( AppDomain domain )
\r
126 bool shadowCopy = domain.ShadowCopyFiles;
\r
127 string cachePath = domain.SetupInformation.CachePath;
\r
128 string domainName = domain.FriendlyName;
\r
132 AppDomain.Unload(domain);
\r
134 catch (Exception ex)
\r
136 // We assume that the tests did something bad and just leave
\r
137 // the orphaned AppDomain "out there".
\r
138 // TODO: Something useful.
\r
139 Trace.WriteLine("Unable to unload AppDomain {0}", domainName);
\r
140 Trace.WriteLine(ex.ToString());
\r
145 DeleteCacheDir(new DirectoryInfo(cachePath));
\r
150 #region Helper Methods
\r
152 /// Get the location for caching and delete any old cache info
\r
154 private string GetCachePath()
\r
156 int processId = Process.GetCurrentProcess().Id;
\r
157 long ticks = DateTime.Now.Ticks;
\r
158 string cachePath = Path.Combine( ShadowCopyPath, processId.ToString() + "_" + ticks.ToString() );
\r
162 DirectoryInfo dir = new DirectoryInfo(cachePath);
\r
163 if(dir.Exists) dir.Delete(true);
\r
165 catch( Exception ex)
\r
167 throw new ApplicationException(
\r
168 string.Format( "Invalid cache path: {0}",cachePath ),
\r
176 /// Helper method to delete the cache dir. This method deals
\r
177 /// with a bug that occurs when files are marked read-only
\r
178 /// and deletes each file separately in order to give better
\r
179 /// exception information when problems occur.
\r
181 /// TODO: This entire method is problematic. Should we be doing it?
\r
183 /// <param name="cacheDir"></param>
\r
184 private void DeleteCacheDir( DirectoryInfo cacheDir )
\r
186 // Debug.WriteLine( "Modules:");
\r
187 // foreach( ProcessModule module in Process.GetCurrentProcess().Modules )
\r
188 // Debug.WriteLine( module.ModuleName );
\r
191 if(cacheDir.Exists)
\r
193 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )
\r
194 DeleteCacheDir( dirInfo );
\r
196 foreach( FileInfo fileInfo in cacheDir.GetFiles() )
\r
198 fileInfo.Attributes = FileAttributes.Normal;
\r
203 catch( Exception ex )
\r
205 Debug.WriteLine( string.Format(
\r
206 "Error deleting {0}, {1}", fileInfo.Name, ex.Message ) );
\r
210 cacheDir.Attributes = FileAttributes.Normal;
\r
216 catch( Exception ex )
\r
218 Debug.WriteLine( string.Format(
\r
219 "Error deleting {0}, {1}", cacheDir.Name, ex.Message ) );
\r
224 private bool IsTestDomain(AppDomain domain)
\r
226 return domain.FriendlyName.StartsWith( "domain-" );
\r
229 public static string GetPrivateBinPath( string basePath, IList assemblies )
\r
231 StringBuilder sb = new StringBuilder(200);
\r
232 ArrayList dirList = new ArrayList();
\r
234 foreach( string assembly in assemblies )
\r
236 string dir = PathUtils.RelativePath( basePath, Path.GetDirectoryName( assembly ) );
\r
237 if ( dir != null && dir != "." && !dirList.Contains( dir ) )
\r
239 dirList.Add( dir );
\r
240 if ( sb.Length > 0 )
\r
241 sb.Append( Path.PathSeparator );
\r
246 return sb.Length == 0 ? null : sb.ToString();
\r
249 public static void DeleteShadowCopyPath()
\r
251 if ( Directory.Exists( ShadowCopyPath ) )
\r
252 Directory.Delete( ShadowCopyPath, true );
\r
256 #region IService Members
\r
258 public void UnloadService()
\r
260 // TODO: Add DomainManager.UnloadService implementation
\r
263 public void InitializeService()
\r
265 // TODO: Add DomainManager.InitializeService implementation
\r