copied mono-api-diff.cs from mono-2-2 branch so new patch can be applied and history...
[mono.git] / mcs / nunit24 / ClientUtilities / util / Services / DomainManager.cs
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
6 \r
7 using System;\r
8 using System.IO;\r
9 using System.Collections;\r
10 using System.Text;\r
11 using System.Configuration;\r
12 using System.Diagnostics;\r
13 using System.Security.Policy;\r
14 using NUnit.Core;\r
15 \r
16 namespace NUnit.Util\r
17 {\r
18         /// <summary>\r
19         /// The DomainManager class handles the creation and unloading\r
20         /// of domains as needed and keeps track of all existing domains.\r
21         /// </summary>\r
22         public class DomainManager : IService\r
23         {\r
24                 #region Properties\r
25                 private static string shadowCopyPath;\r
26                 public static string ShadowCopyPath\r
27                 {\r
28                         get\r
29                         {\r
30                                 if ( shadowCopyPath == null )\r
31                                 {\r
32                                         shadowCopyPath = ConfigurationSettings.AppSettings["shadowfiles.path"];\r
33                                         if ( shadowCopyPath == "" || shadowCopyPath== null )\r
34                                                 shadowCopyPath = Path.Combine( Path.GetTempPath(), @"nunit20\ShadowCopyCache" );\r
35                                         else\r
36                                                 shadowCopyPath = Environment.ExpandEnvironmentVariables(shadowCopyPath);\r
37 \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
43                                         }\r
44                                 }\r
45 \r
46                                 return shadowCopyPath;\r
47                         }\r
48                 }\r
49                 #endregion\r
50 \r
51                 #region Create and Unload Domains\r
52                 /// <summary>\r
53                 /// Construct an application domain for running a test package\r
54                 /// </summary>\r
55                 /// <param name="package">The TestPackage to be run</param>\r
56                 public AppDomain CreateDomain( TestPackage package )\r
57                 {\r
58                         FileInfo testFile = new FileInfo( package.FullName );\r
59 \r
60                         AppDomainSetup setup = new AppDomainSetup();\r
61 \r
62                         // We always use the same application name\r
63                         setup.ApplicationName = "Tests";\r
64 \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
69 \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
77 \r
78                         string binPath = package.PrivateBinPath;\r
79                         if ( package.AutoBinPath )\r
80                                 binPath = GetPrivateBinPath( appBase, package.Assemblies );\r
81                         setup.PrivateBinPath = binPath;\r
82 \r
83                         if ( package.GetSetting( "ShadowCopyFiles", true ) )\r
84                         {\r
85                                 setup.ShadowCopyFiles = "true";\r
86                                 setup.ShadowCopyDirectories = appBase;\r
87                                 setup.CachePath = GetCachePath();\r
88                         }\r
89 \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
94 \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
99 \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
103 \r
104 // No reference to extensions, so we do it a different way\r
105             string moduleName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;\r
106             string nunitDirPath = Path.GetDirectoryName(moduleName);\r
107 //            string coreExtensions = Path.Combine(nunitDirPath, "nunit.core.extensions.dll");\r
108 //                      assemblyResolver.AddFile( coreExtensions );\r
109             //assemblyResolver.AddFiles( nunitDirPath, "*.dll" );\r
110 \r
111             string addinsDirPath = Path.Combine(nunitDirPath, "addins");\r
112             assemblyResolver.AddDirectory( addinsDirPath );\r
113 \r
114                         // HACK: Only pass down our AddinRegistry one level so that tests of NUnit\r
115                         // itself start without any addins defined.\r
116                         if ( !IsTestDomain( AppDomain.CurrentDomain ) )\r
117                                 runnerDomain.SetData("AddinRegistry", Services.AddinRegistry);\r
118 \r
119                         return runnerDomain;\r
120                 }\r
121 \r
122                 public void Unload( AppDomain domain )\r
123                 {\r
124                         bool shadowCopy = domain.ShadowCopyFiles;\r
125                         string cachePath = domain.SetupInformation.CachePath;\r
126                         string domainName = domain.FriendlyName;\r
127 \r
128             try\r
129             {\r
130                 AppDomain.Unload(domain);\r
131             }\r
132             catch (Exception ex)\r
133             {\r
134                 // We assume that the tests did something bad and just leave\r
135                 // the orphaned AppDomain "out there". \r
136                 // TODO: Something useful.\r
137                 Trace.WriteLine("Unable to unload AppDomain {0}", domainName);\r
138                 Trace.WriteLine(ex.ToString());\r
139             }\r
140             finally\r
141             {\r
142                 if (shadowCopy)\r
143                     DeleteCacheDir(new DirectoryInfo(cachePath));\r
144             }\r
145                 }\r
146                 #endregion\r
147 \r
148                 #region Helper Methods\r
149                 /// <summary>\r
150                 /// Get the location for caching and delete any old cache info\r
151                 /// </summary>\r
152                 private string GetCachePath()\r
153                 {\r
154             int processId = Process.GetCurrentProcess().Id;\r
155             long ticks = DateTime.Now.Ticks;\r
156                         string cachePath = Path.Combine( ShadowCopyPath, processId.ToString() + "_" + ticks.ToString() ); \r
157                                 \r
158                         try \r
159                         {\r
160                                 DirectoryInfo dir = new DirectoryInfo(cachePath);               \r
161                                 if(dir.Exists) dir.Delete(true);\r
162                         }\r
163                         catch( Exception ex)\r
164                         {\r
165                                 throw new ApplicationException( \r
166                                         string.Format( "Invalid cache path: {0}",cachePath ),\r
167                                         ex );\r
168                         }\r
169 \r
170                         return cachePath;\r
171                 }\r
172 \r
173                 /// <summary>\r
174                 /// Helper method to delete the cache dir. This method deals \r
175                 /// with a bug that occurs when files are marked read-only\r
176                 /// and deletes each file separately in order to give better \r
177                 /// exception information when problems occur.\r
178                 /// \r
179                 /// TODO: This entire method is problematic. Should we be doing it?\r
180                 /// </summary>\r
181                 /// <param name="cacheDir"></param>\r
182                 private void DeleteCacheDir( DirectoryInfo cacheDir )\r
183                 {\r
184                         //                      Debug.WriteLine( "Modules:");\r
185                         //                      foreach( ProcessModule module in Process.GetCurrentProcess().Modules )\r
186                         //                              Debug.WriteLine( module.ModuleName );\r
187                         \r
188 \r
189                         if(cacheDir.Exists)\r
190                         {\r
191                                 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )\r
192                                         DeleteCacheDir( dirInfo );\r
193 \r
194                                 foreach( FileInfo fileInfo in cacheDir.GetFiles() )\r
195                                 {\r
196                                         fileInfo.Attributes = FileAttributes.Normal;\r
197                                         try \r
198                                         {\r
199                                                 fileInfo.Delete();\r
200                                         }\r
201                                         catch( Exception ex )\r
202                                         {\r
203                                                 Debug.WriteLine( string.Format( \r
204                                                         "Error deleting {0}, {1}", fileInfo.Name, ex.Message ) );\r
205                                         }\r
206                                 }\r
207 \r
208                                 cacheDir.Attributes = FileAttributes.Normal;\r
209 \r
210                                 try\r
211                                 {\r
212                                         cacheDir.Delete();\r
213                                 }\r
214                                 catch( Exception ex )\r
215                                 {\r
216                                         Debug.WriteLine( string.Format( \r
217                                                 "Error deleting {0}, {1}", cacheDir.Name, ex.Message ) );\r
218                                 }\r
219                         }\r
220                 }\r
221 \r
222                 private bool IsTestDomain(AppDomain domain)\r
223                 {\r
224                         return domain.FriendlyName.StartsWith( "domain-" );\r
225                 }\r
226 \r
227                 public static string GetPrivateBinPath( string basePath, IList assemblies )\r
228                 {\r
229                         StringBuilder sb = new StringBuilder(200);\r
230                         ArrayList dirList = new ArrayList();\r
231 \r
232                         foreach( string assembly in assemblies )\r
233                         {\r
234                                 string dir = PathUtils.RelativePath( basePath, Path.GetDirectoryName( assembly ) );\r
235                                 if ( dir != null && dir != "." && !dirList.Contains( dir ) )\r
236                                 {\r
237                                         dirList.Add( dir );\r
238                                         if ( sb.Length > 0 )\r
239                                                 sb.Append( Path.PathSeparator );\r
240                                         sb.Append( dir );\r
241                                 }\r
242                         }\r
243 \r
244                         return sb.Length == 0 ? null : sb.ToString();\r
245                 }\r
246 \r
247                 public static void DeleteShadowCopyPath()\r
248                 {\r
249                         if ( Directory.Exists( ShadowCopyPath ) )\r
250                                 Directory.Delete( ShadowCopyPath, true );\r
251                 }\r
252                 #endregion\r
253 \r
254                 #region IService Members\r
255 \r
256                 public void UnloadService()\r
257                 {\r
258                         // TODO:  Add DomainManager.UnloadService implementation\r
259                 }\r
260 \r
261                 public void InitializeService()\r
262                 {\r
263                         // TODO:  Add DomainManager.InitializeService implementation\r
264                 }\r
265 \r
266                 #endregion\r
267         }\r
268 }\r