2009-01-07 Atsushi Enomoto <atsushi@ximian.com>
[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 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
111 \r
112             string addinsDirPath = Path.Combine(nunitDirPath, "addins");\r
113             assemblyResolver.AddDirectory( addinsDirPath );\r
114 }\r
115 \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
120 \r
121                         return runnerDomain;\r
122                 }\r
123 \r
124                 public void Unload( AppDomain domain )\r
125                 {\r
126                         bool shadowCopy = domain.ShadowCopyFiles;\r
127                         string cachePath = domain.SetupInformation.CachePath;\r
128                         string domainName = domain.FriendlyName;\r
129 \r
130             try\r
131             {\r
132                 AppDomain.Unload(domain);\r
133             }\r
134             catch (Exception ex)\r
135             {\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
141             }\r
142             finally\r
143             {\r
144                 if (shadowCopy)\r
145                     DeleteCacheDir(new DirectoryInfo(cachePath));\r
146             }\r
147                 }\r
148                 #endregion\r
149 \r
150                 #region Helper Methods\r
151                 /// <summary>\r
152                 /// Get the location for caching and delete any old cache info\r
153                 /// </summary>\r
154                 private string GetCachePath()\r
155                 {\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
159                                 \r
160                         try \r
161                         {\r
162                                 DirectoryInfo dir = new DirectoryInfo(cachePath);               \r
163                                 if(dir.Exists) dir.Delete(true);\r
164                         }\r
165                         catch( Exception ex)\r
166                         {\r
167                                 throw new ApplicationException( \r
168                                         string.Format( "Invalid cache path: {0}",cachePath ),\r
169                                         ex );\r
170                         }\r
171 \r
172                         return cachePath;\r
173                 }\r
174 \r
175                 /// <summary>\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
180                 /// \r
181                 /// TODO: This entire method is problematic. Should we be doing it?\r
182                 /// </summary>\r
183                 /// <param name="cacheDir"></param>\r
184                 private void DeleteCacheDir( DirectoryInfo cacheDir )\r
185                 {\r
186                         //                      Debug.WriteLine( "Modules:");\r
187                         //                      foreach( ProcessModule module in Process.GetCurrentProcess().Modules )\r
188                         //                              Debug.WriteLine( module.ModuleName );\r
189                         \r
190 \r
191                         if(cacheDir.Exists)\r
192                         {\r
193                                 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )\r
194                                         DeleteCacheDir( dirInfo );\r
195 \r
196                                 foreach( FileInfo fileInfo in cacheDir.GetFiles() )\r
197                                 {\r
198                                         fileInfo.Attributes = FileAttributes.Normal;\r
199                                         try \r
200                                         {\r
201                                                 fileInfo.Delete();\r
202                                         }\r
203                                         catch( Exception ex )\r
204                                         {\r
205                                                 Debug.WriteLine( string.Format( \r
206                                                         "Error deleting {0}, {1}", fileInfo.Name, ex.Message ) );\r
207                                         }\r
208                                 }\r
209 \r
210                                 cacheDir.Attributes = FileAttributes.Normal;\r
211 \r
212                                 try\r
213                                 {\r
214                                         cacheDir.Delete();\r
215                                 }\r
216                                 catch( Exception ex )\r
217                                 {\r
218                                         Debug.WriteLine( string.Format( \r
219                                                 "Error deleting {0}, {1}", cacheDir.Name, ex.Message ) );\r
220                                 }\r
221                         }\r
222                 }\r
223 \r
224                 private bool IsTestDomain(AppDomain domain)\r
225                 {\r
226                         return domain.FriendlyName.StartsWith( "domain-" );\r
227                 }\r
228 \r
229                 public static string GetPrivateBinPath( string basePath, IList assemblies )\r
230                 {\r
231                         StringBuilder sb = new StringBuilder(200);\r
232                         ArrayList dirList = new ArrayList();\r
233 \r
234                         foreach( string assembly in assemblies )\r
235                         {\r
236                                 string dir = PathUtils.RelativePath( basePath, Path.GetDirectoryName( assembly ) );\r
237                                 if ( dir != null && dir != "." && !dirList.Contains( dir ) )\r
238                                 {\r
239                                         dirList.Add( dir );\r
240                                         if ( sb.Length > 0 )\r
241                                                 sb.Append( Path.PathSeparator );\r
242                                         sb.Append( dir );\r
243                                 }\r
244                         }\r
245 \r
246                         return sb.Length == 0 ? null : sb.ToString();\r
247                 }\r
248 \r
249                 public static void DeleteShadowCopyPath()\r
250                 {\r
251                         if ( Directory.Exists( ShadowCopyPath ) )\r
252                                 Directory.Delete( ShadowCopyPath, true );\r
253                 }\r
254                 #endregion\r
255 \r
256                 #region IService Members\r
257 \r
258                 public void UnloadService()\r
259                 {\r
260                         // TODO:  Add DomainManager.UnloadService implementation\r
261                 }\r
262 \r
263                 public void InitializeService()\r
264                 {\r
265                         // TODO:  Add DomainManager.InitializeService implementation\r
266                 }\r
267 \r
268                 #endregion\r
269         }\r
270 }\r