Merge pull request #949 from ermshiperete/bug-novell-463149
[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.Threading;\r
12 using System.Configuration;\r
13 using System.Diagnostics;\r
14 using System.Security.Policy;\r
15 using NUnit.Core;\r
16 \r
17 namespace NUnit.Util\r
18 {\r
19         /// <summary>\r
20         /// The DomainManager class handles the creation and unloading\r
21         /// of domains as needed and keeps track of all existing domains.\r
22         /// </summary>\r
23         public class DomainManager : IService\r
24         {\r
25                 #region Properties\r
26                 private static string shadowCopyPath;\r
27                 public static string ShadowCopyPath\r
28                 {\r
29                         get\r
30                         {\r
31                                 if ( shadowCopyPath == null )\r
32                                 {\r
33                                         shadowCopyPath = ConfigurationSettings.AppSettings["shadowfiles.path"];\r
34                                         if ( shadowCopyPath == "" || shadowCopyPath== null )\r
35                                                 shadowCopyPath = Path.Combine( Path.GetTempPath(), @"nunit20\ShadowCopyCache" );\r
36                                         else\r
37                                                 shadowCopyPath = Environment.ExpandEnvironmentVariables(shadowCopyPath);\r
38 \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
44                                         }\r
45                                 }\r
46 \r
47                                 return shadowCopyPath;\r
48                         }\r
49                 }\r
50                 #endregion\r
51 \r
52                 #region Create and Unload Domains\r
53                 /// <summary>\r
54                 /// Construct an application domain for running a test package\r
55                 /// </summary>\r
56                 /// <param name="package">The TestPackage to be run</param>\r
57                 public AppDomain CreateDomain( TestPackage package )\r
58                 {\r
59                         FileInfo testFile = new FileInfo( package.FullName );\r
60 \r
61                         AppDomainSetup setup = new AppDomainSetup();\r
62 \r
63                         // We always use the same application name\r
64                         setup.ApplicationName = "Tests";\r
65 \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
70 \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
78 \r
79                         string binPath = package.PrivateBinPath;\r
80                         if ( package.AutoBinPath )\r
81                                 binPath = GetPrivateBinPath( appBase, package.Assemblies );\r
82                         setup.PrivateBinPath = binPath;\r
83 \r
84                         if ( package.GetSetting( "ShadowCopyFiles", true ) )\r
85                         {\r
86                                 setup.ShadowCopyFiles = "true";\r
87                                 setup.ShadowCopyDirectories = appBase;\r
88                                 setup.CachePath = GetCachePath();\r
89                         }\r
90 \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
95 \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
100 \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
104 \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                         // 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
119 \r
120                         return runnerDomain;\r
121                 }\r
122 \r
123                 public void Unload( AppDomain domain )\r
124                 {\r
125                         new DomainUnloader(domain).Unload();\r
126                 }\r
127                 #endregion\r
128 \r
129                 #region Nested DomainUnloader Class\r
130                 class DomainUnloader\r
131                 {\r
132                         private Thread thread;\r
133                         private AppDomain domain;\r
134 \r
135                         public DomainUnloader(AppDomain domain)\r
136                         {\r
137                                 this.domain = domain;\r
138                         }\r
139 \r
140                         public void Unload()\r
141                         {\r
142                                 thread = new Thread(new ThreadStart(UnloadOnThread));\r
143                                 thread.Start();\r
144                                 if (!thread.Join(20000))\r
145                                 {\r
146                                         Trace.WriteLine("Unable to unload AppDomain {0}", domain.FriendlyName);\r
147                                         Trace.WriteLine("Unload thread timed out");\r
148                                 }\r
149                         }\r
150 \r
151                         private void UnloadOnThread()\r
152                         {\r
153                                 bool shadowCopy = domain.ShadowCopyFiles;\r
154                                 string cachePath = domain.SetupInformation.CachePath;\r
155                                 string domainName = domain.FriendlyName;\r
156 \r
157                 try\r
158                     {\r
159                         AppDomain.Unload(domain);\r
160                 }\r
161                     catch (Exception ex)\r
162                 {\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
168                     }\r
169                 finally\r
170                     {\r
171                     if (shadowCopy)\r
172                             DeleteCacheDir(new DirectoryInfo(cachePath));\r
173                 }\r
174                         }\r
175                 }\r
176                 #endregion\r
177 \r
178                 #region Helper Methods\r
179                 /// <summary>\r
180                 /// Get the location for caching and delete any old cache info\r
181                 /// </summary>\r
182                 private string GetCachePath()\r
183                 {\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
187                                 \r
188                         try \r
189                         {\r
190                                 DirectoryInfo dir = new DirectoryInfo(cachePath);               \r
191                                 if(dir.Exists) dir.Delete(true);\r
192                         }\r
193                         catch( Exception ex)\r
194                         {\r
195                                 throw new ApplicationException( \r
196                                         string.Format( "Invalid cache path: {0}",cachePath ),\r
197                                         ex );\r
198                         }\r
199 \r
200                         return cachePath;\r
201                 }\r
202 \r
203                 /// <summary>\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
208                 /// \r
209                 /// TODO: This entire method is problematic. Should we be doing it?\r
210                 /// </summary>\r
211                 /// <param name="cacheDir"></param>\r
212                 private static void DeleteCacheDir( DirectoryInfo cacheDir )\r
213                 {\r
214                         //                      Debug.WriteLine( "Modules:");\r
215                         //                      foreach( ProcessModule module in Process.GetCurrentProcess().Modules )\r
216                         //                              Debug.WriteLine( module.ModuleName );\r
217                         \r
218 \r
219                         if(cacheDir.Exists)\r
220                         {\r
221                                 foreach( DirectoryInfo dirInfo in cacheDir.GetDirectories() )\r
222                                         DeleteCacheDir( dirInfo );\r
223 \r
224                                 foreach( FileInfo fileInfo in cacheDir.GetFiles() )\r
225                                 {\r
226                                         fileInfo.Attributes = FileAttributes.Normal;\r
227                                         try \r
228                                         {\r
229                                                 fileInfo.Delete();\r
230                                         }\r
231                                         catch( Exception ex )\r
232                                         {\r
233                                                 Debug.WriteLine( string.Format( \r
234                                                         "Error deleting {0}, {1}", fileInfo.Name, ex.Message ) );\r
235                                         }\r
236                                 }\r
237 \r
238                                 cacheDir.Attributes = FileAttributes.Normal;\r
239 \r
240                                 try\r
241                                 {\r
242                                         cacheDir.Delete();\r
243                                 }\r
244                                 catch( Exception ex )\r
245                                 {\r
246                                         Debug.WriteLine( string.Format( \r
247                                                 "Error deleting {0}, {1}", cacheDir.Name, ex.Message ) );\r
248                                 }\r
249                         }\r
250                 }\r
251 \r
252                 private bool IsTestDomain(AppDomain domain)\r
253                 {\r
254                         return domain.FriendlyName.StartsWith( "domain-" );\r
255                 }\r
256 \r
257                 public static string GetPrivateBinPath( string basePath, IList assemblies )\r
258                 {\r
259                         StringBuilder sb = new StringBuilder(200);\r
260                         ArrayList dirList = new ArrayList();\r
261 \r
262                         foreach( string assembly in assemblies )\r
263                         {\r
264                                 string dir = PathUtils.RelativePath( basePath, Path.GetDirectoryName( assembly ) );\r
265                                 if ( dir != null && dir != "." && !dirList.Contains( dir ) )\r
266                                 {\r
267                                         dirList.Add( dir );\r
268                                         if ( sb.Length > 0 )\r
269                                                 sb.Append( Path.PathSeparator );\r
270                                         sb.Append( dir );\r
271                                 }\r
272                         }\r
273 \r
274                         return sb.Length == 0 ? null : sb.ToString();\r
275                 }\r
276 \r
277                 public static void DeleteShadowCopyPath()\r
278                 {\r
279                         if ( Directory.Exists( ShadowCopyPath ) )\r
280                                 Directory.Delete( ShadowCopyPath, true );\r
281                 }\r
282                 #endregion\r
283 \r
284                 #region IService Members\r
285 \r
286                 public void UnloadService()\r
287                 {\r
288                         // TODO:  Add DomainManager.UnloadService implementation\r
289                 }\r
290 \r
291                 public void InitializeService()\r
292                 {\r
293                         // TODO:  Add DomainManager.InitializeService implementation\r
294                 }\r
295 \r
296                 #endregion\r
297         }\r
298 }\r