New tests.
[mono.git] / mcs / class / System.Web / Test / mainsoft / NunitWeb / NunitWeb / WebTest.cs
1 #if TARGET_JVM_FOR_WEBTEST
2 #define TARGET_JVM
3 #endif
4
5 using System;
6 using System.Reflection;
7 using System.IO;
8 using System.Web;
9 using System.Web.Hosting;
10 using System.Web.UI;
11 using System.Threading;
12
13 namespace MonoTests.SystemWeb.Framework
14 {
15         /// <summary>
16         /// The most important class from user perspective. See <see cref="Request"/>,
17         /// <see cref="Response"/>, <see cref="Invoker"/>, <see cref="Run"/> for
18         /// more information.
19         /// </summary>
20         /// <seealso cref="Request"/>
21         /// <seealso cref="Response"/>
22         /// <seealso cref="Invoker"/>
23         /// <seealso cref="Run"/>
24         [Serializable]
25         public class WebTest
26         {
27                 /// <summary>
28                 /// Thrown when trying to copy a resource after appdomain was created. Please call
29                 /// WebTest.Unload before copying resource.
30                 /// </summary>
31                 public class DomainUpException : Exception
32                 {
33                 }
34
35                 object _userData;
36                 /// <summary>
37                 /// Any user-defined data. Must be serializable to pass between appdomains.
38                 /// </summary>
39                 /// <example>
40                 /// [Test]
41                 /// public void SampleTest ()
42                 /// {
43                 ///     WebTest t = new WebTest (new HandlerInvoker (MyCallback));
44                 ///     t.Run ();
45                 ///     Assert.AreEqual ("Was here", t.UserData.ToString());
46                 /// }
47                 /// 
48                 /// static public void MyCallback ()
49                 /// {
50                 ///     WebTest.CurrentTest.UserData = "Was here";
51                 /// }
52                 /// </example>
53
54                 public object UserData
55                 {
56                         get { return _userData; }
57                         set { _userData = value; }
58                 }
59
60                 Response _response;
61                 /// <summary>
62                 /// The result of the last <see cref="Run"/>. See <see cref="MonoTests.SystemWeb.Framework.Response"/>,
63                 /// <see cref="FormRequest"/>.
64                 /// </summary>
65                 /// <seealso cref="Run"/>
66                 /// <seealso cref="MonoTests.SystemWeb.Framework.Response"/>
67                 /// <seealso cref="FormRequest"/>
68                 public Response Response
69                 {
70                         get { return _response; }
71                         set { _response = value; }
72                 }
73
74                 BaseInvoker _invoker;
75                 /// <summary>
76                 /// Set the invoker, which is executed in the web context by <see cref="Invoke"/>
77                 /// method. Most commonly used <see cref="PageInvoker"/>. See also: <see cref="BaseInvoker"/>,
78                 /// <see cref="HandlerInvoker"/>
79                 /// </summary>
80                 /// <seealso cref="Invoke"/>
81                 /// <seealso cref="PageInvoker"/>
82                 /// <seealso cref="BaseInvoker"/>
83                 /// <seealso cref="HandlerInvoker"/>
84                 public BaseInvoker Invoker
85                 {
86                         get { return _invoker; }
87                         set { _invoker = value; }
88                 }
89
90                 BaseRequest _request;
91                 /// <summary>
92                 /// Contains all the data necessary to create an <see cref="System.Web.HttpWorkerRequest"/> in
93                 /// the application appdomain. See also <see cref="BaseRequest"/>,
94                 /// <see cref="PostableRequest"/>, <see cref="FormRequest"/>.
95                 /// </summary>
96                 /// <seealso cref="System.Web.HttpWorkerRequest"/>
97                 /// <seealso cref="BaseRequest"/>
98                 /// <seealso cref="PostableRequest"/>
99                 /// <seealso cref="FormRequest"/>
100                 public BaseRequest Request
101                 {
102                         get { return _request; }
103                         set { _request = value; }
104                 }
105
106                 static MyHost host;
107                 internal static MyHost Host
108                 {
109                         get {
110                                 EnsureHosting ();
111                                 return host;
112                         }
113                 }
114
115                 /// <summary>
116                 /// Run the request using <see cref="Request"/> and <see cref="Invoker"/>
117                 /// values. Keep the result of the request in <see cref="Response"/> property.
118                 /// </summary>
119                 /// <returns>The body of the HTTP response (<see cref="MonoTests.SystemWeb.Framework.Response.Body"/>).</returns>
120                 /// <seealso cref="Request"/>
121                 /// <seealso cref="Invoker"/>
122                 /// <seealso cref="Response"/>
123                 /// <seealso cref="MonoTests.SystemWeb.Framework.Response.Body"/>
124                 public string Run ()
125                 {
126                         SystemWebTestShim.BuildManager.SuppressDebugModeMessages ();
127
128                         if (Request.Url == null)
129                                 Request.Url = Invoker.GetDefaultUrl ();
130                         _unloadHandler.StartingRequest();
131                         try {
132                                 WebTest newTestInstance = Host.Run (this);
133                                 CopyFrom (newTestInstance);
134                         } finally {
135                                 _unloadHandler.FinishedRequest();
136                         }
137                         return _response.Body;
138                 }
139                 
140                 private void CopyFrom (WebTest newTestInstance)
141                 {
142                         this._invoker = newTestInstance._invoker;
143                         this._request = newTestInstance._request;
144                         this._response = newTestInstance._response;
145                         this._userData = newTestInstance._userData;
146                 }
147
148                 /// <summary>
149                 /// The instance of the currently running test. Defined only in the web appdomain.
150                 /// In different threads this property may have different values.
151                 /// </summary>
152                 public static WebTest CurrentTest
153                 {
154                         get { return MyHost.GetCurrentTest (); }
155                 }
156
157                 /// <summary>
158                 /// This method must be called when custom <see cref="System.Web.IHttpHandler.ProcessRequest"/> or aspx code behind is used,
159                 /// to allow the framework to invoke all user supplied delegates.
160                 /// </summary>
161                 /// <param name="param">Parameter defined by the <see cref="BaseInvoker"/> subclass. For example,
162                 /// <see cref="PageInvoker"/> expects to receive a <see cref="System.Web.UI.Page"/> instance here.</param>
163                 /// <seealso cref="System.Web.IHttpHandler.ProcessRequest"/>
164                 /// <seealso cref="BaseInvoker"/>
165                 /// <seealso cref="PageInvoker"/>
166                 public void Invoke (object param)
167                 {
168                         try {
169                                 Invoker.DoInvoke (param);
170                         }
171                         catch (Exception ex) {
172                                 RegisterException (ex);
173                                 throw;
174                         }
175                 }
176
177                 public void SendHeaders ()
178                 {
179                         Host.SendHeaders (this);
180                 }
181
182                 /// <summary>
183                 /// This method is intended for use from <see cref="MonoTests.SystemWeb.Framework.BaseInvoker.DoInvoke"/> when
184                 /// the invocation causes an exception. In such cases, the exception must be registered
185                 /// with this method, and then swallowed. Before returning, <see cref="WebTest.Run"/>
186                 /// will rethrow this exception. This is done to hide the exception from <see cref="System.Web.HttpRuntime"/>,
187                 /// which normally swallows the exception and returns 500 ERROR http result.
188                 /// </summary>
189                 /// <param name="ex">The exception to be registered and rethrown.</param>
190                 /// <seealso cref="MonoTests.SystemWeb.Framework.BaseInvoker.DoInvoke"/>
191                 /// <seealso cref="WebTest.Run"/>
192                 /// <seealso cref="System.Web.HttpRuntime"/>
193                 public static void RegisterException (Exception ex)
194                 {
195                         Host.RegisterException (ex);
196                 }
197
198                 /// <summary>
199                 /// Unload the web appdomain and delete the temporary application root
200                 /// directory.
201                 /// </summary>
202                 public static void CleanApp ()
203                 {
204 #if !TARGET_JVM
205                         if (host != null) {
206                                 lock (_appUnloadedSync) {
207                                         EventHandler handler = new EventHandler(PulseAppUnloadedSync);
208                                         WebTest.AppUnloaded += handler;
209                                         WebTest t = new WebTest (PageInvoker.CreateOnLoad (new PageDelegate (UnloadAppDomain_OnLoad)));
210                                         t.Run ();
211                                         Monitor.Wait(_appUnloadedSync);
212                                         WebTest.AppUnloaded -= handler;
213                                 }                       
214                         }
215                         if (baseDir != null) {
216                                 Directory.Delete (baseDir, true);
217                                 baseDir = null;
218                                 binDir = null;
219                         }
220 #endif
221                 }
222                 
223                 private static object _appUnloadedSync = new object();
224                 
225                 private static void PulseAppUnloadedSync(object source, EventArgs args)
226                 {
227                         lock (_appUnloadedSync)
228                                 Monitor.PulseAll(_appUnloadedSync);
229                 }
230
231                 public static void UnloadAppDomain_OnLoad (Page p) 
232                 {
233                         HttpRuntime.UnloadAppDomain();
234                 }
235
236                 public static void Unload () {}
237
238                 /// <summary>
239                 /// Default constructor. Initializes <see cref="Invoker"/> with a new
240                 /// <see cref="BaseInvoker"/> and <see cref="Request"/> with an empty
241                 /// <see cref="BaseRequest"/>.
242                 /// </summary>
243                 /// <seealso cref="Invoker"/>
244                 /// <seealso cref="BaseInvoker"/>
245                 /// <seealso cref="Request"/>
246                 /// <seealso cref="BaseRequest"/>
247                 public WebTest ()
248                 {
249                         Invoker = new BaseInvoker ();
250                         Request = new BaseRequest ();
251                 }
252
253                 /// <summary>
254                 /// Same as <see cref="WebTest()"/>, and set <see cref="MonoTests.SystemWeb.Framework.BaseRequest.Url"/> to
255                 /// the specified Url.
256                 /// </summary>
257                 /// <param name="url">The URL used for the next <see cref="Run"/></param>
258                 /// <seealso cref="MonoTests.SystemWeb.Framework.BaseRequest.Url"/>
259                 /// <seealso cref="Run"/>
260                 public WebTest (string url)
261                         : this ()
262                 {
263                         Request.Url = url;
264                 }
265
266                 /// <summary>
267                 /// Create a new instance, initializing <see cref="Invoker"/> with the given
268                 /// value, and the <see cref="Request"/> with <see cref="BaseRequest"/>.
269                 /// </summary>
270                 /// <param name="invoker">The invoker used for this test.</param>
271                 /// <seealso cref="Invoker"/>
272                 /// <seealso cref="Request"/>
273                 /// <seealso cref="BaseRequest"/>
274                 public WebTest (BaseInvoker invoker)
275                         : this ()
276                 {
277                         Invoker = invoker;
278                 }
279
280                 /// <summary>
281                 /// Create a new instance, initializing <see cref="Request"/> with the given
282                 /// value, and the <see cref="Invoker"/> with <see cref="BaseInvoker"/>.
283                 /// </summary>
284                 /// <param name="request">The request used for this test.</param>
285                 /// <seealso cref="Request"/>
286                 /// <seealso cref="Invoker"/>
287                 /// <seealso cref="BaseInvoker"/>
288                 public WebTest (BaseRequest request)
289                         : this ()
290                 {
291                         Request = request;
292                 }
293
294
295                 /// <summary>
296                 /// Copy a resource embedded in the assembly into the web application
297                 /// </summary>
298                 /// <param name="type">A type in the assembly that contains the embedded resource.</param>
299                 /// <param name="resourceName">The name of the resource.</param>
300                 /// <param name="targetUrl">The URL where the resource will be available</param>
301                 /// <exception cref="System.ArgumentException">Thrown when resource with name resourceName is not found.</exception>
302                 /// <example><code>CopyResource (GetType (), "Default.skin", "App_Themes/Black/Default.skin");</code></example>
303                 public static void CopyResource (Type type, string resourceName, string targetUrl)
304                 {
305                         if (type == null)
306                                 throw new ArgumentNullException ("type");
307 #if !TARGET_JVM
308                         using (Stream source = type.Assembly.GetManifestResourceStream (resourceName)) {
309                                 if (source == null)
310                                         throw new ArgumentException ("resource not found: " + resourceName, "resourceName");
311                                 byte[] array = new byte[source.Length];
312                                 source.Read (array, 0, array.Length);
313                                 CopyBinary (array, targetUrl);
314                         }
315 #endif
316                 }
317
318                 public static void CopyPrefixedResources (Type type, string namePrefix, string targetDir)
319                 {
320                         if (type == null)
321                                 throw new ArgumentNullException ("type");
322                         
323                         string[] manifestResources = type.Assembly.GetManifestResourceNames ();
324                         if (manifestResources == null || manifestResources.Length == 0)
325                                 return;
326
327                         foreach (string resource in manifestResources) {
328                                 if (resource == null || resource.Length == 0)
329                                         continue;
330                                 
331                                 if (!resource.StartsWith (namePrefix))
332                                         continue;
333                                 
334                                 CopyResource (type, resource, Path.Combine (targetDir, resource.Substring (namePrefix.Length)));
335                         }
336                 }
337                 
338                 /// <summary>
339                 /// Copy a chunk of data as a file into the web application.
340                 /// </summary>
341                 /// <param name="sourceArray">The array that contains the data to be written.</param>
342                 /// <param name="targetUrl">The URL where the data will be available.</param>
343                 /// <returns>The target filename where the data was stored.</returns>
344                 /// <example><code>CopyBinary (System.Text.Encoding.UTF8.GetBytes ("Hello"), "App_Data/Greeting.txt");</code></example>
345                 public static string CopyBinary (byte[] sourceArray, string targetUrl)
346                 {
347 #if TARGET_JVM
348                         return null;
349 #else
350                         EnsureWorkingDirectories ();
351                         EnsureDirectoryExists (Path.Combine (baseDir, Path.GetDirectoryName (targetUrl)));
352                         string targetFile = Path.Combine (baseDir, targetUrl);
353
354                         if (File.Exists(targetFile)) {
355                                 using (FileStream existing = File.OpenRead(targetFile)) {
356                                         bool equal = false;
357                                         if (sourceArray.Length == existing.Length) {
358                                                 byte[] existingArray = new byte[sourceArray.Length];
359                                                 existing.Read (existingArray, 0, existingArray.Length);
360                                                 
361                                                 equal = true;
362                                                 for (int i = 0; i < sourceArray.Length; i ++) {
363                                                         if (sourceArray[i] != existingArray[i]) {
364                                                                 equal = false;
365                                                                 break;
366                                                         }
367                                                 }
368                                         }
369                                         
370                                         if (equal) {
371                                                 existing.Close ();
372                                                 File.SetLastWriteTime (targetFile, DateTime.Now);
373                                                 return targetFile;
374                                         }
375                                         
376                                 }
377                                 
378                                 CheckDomainIsDown ();
379                         }
380
381                         using (FileStream target = new FileStream (targetFile, FileMode.Create)) {
382                                 target.Write (sourceArray, 0, sourceArray.Length);
383                         }
384
385                         return targetFile;
386 #endif
387                 }
388
389                 static WebTestResourcesSetupAttribute.SetupHandler CheckResourcesSetupHandler ()
390                 {
391                         // It is assumed WebTest is included in the same assembly which contains the
392                         // tests themselves
393                         object[] attributes = typeof (WebTest).Assembly.GetCustomAttributes (typeof (WebTestResourcesSetupAttribute), true);
394                         if (attributes == null || attributes.Length == 0)
395                                 return null;
396                         
397                         WebTestResourcesSetupAttribute attr = attributes [0] as WebTestResourcesSetupAttribute;
398                         if (attr == null)
399                                 return null;
400
401                         return attr.Handler;
402                 }
403                 
404                 public static void EnsureHosting ()
405                 {
406                         if (host != null)
407                                 return;
408 #if TARGET_JVM
409                         host = new MyHost ();
410                         return;
411 #else
412                         host = AppDomain.CurrentDomain.GetData (HOST_INSTANCE_NAME) as MyHost;
413                         if (host == null)
414                                 SetupHosting ();
415 #endif
416                 }
417                 
418                 public static void SetupHosting ()
419                 {
420                         SetupHosting (null);
421                 }
422                 
423                 public static void SetupHosting (WebTestResourcesSetupAttribute.SetupHandler resHandler)
424                 {
425 #if !TARGET_JVM
426                         if (host == null)
427                                 host = AppDomain.CurrentDomain.GetData (HOST_INSTANCE_NAME) as MyHost;
428 #endif
429                         if (host != null)
430                                 CleanApp ();
431 #if TARGET_JVM
432                         host = new MyHost ();
433                         return;
434 #else
435                         if (resHandler == null)
436                                 resHandler = CheckResourcesSetupHandler ();
437                         if (resHandler == null)
438                                 CopyResources ();
439                         else
440                                 resHandler ();
441                         
442                         foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies ())
443                                 LoadAssemblyRecursive (ass);
444
445                         foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies ())
446                                 CopyAssembly (ass, binDir);
447
448                         host = (MyHost) ApplicationHost.CreateApplicationHost (typeof (MyHost), VIRTUAL_BASE_DIR, baseDir);
449                         AppDomain.CurrentDomain.SetData (HOST_INSTANCE_NAME, host);
450                         host.AppDomain.SetData (HOST_INSTANCE_NAME, host);
451                         host.AppDomain.DomainUnload += new EventHandler (_unloadHandler.OnUnload);
452 #endif
453                 }
454
455                 private static UnloadHandler _unloadHandler = new UnloadHandler();
456                                 
457                 public class UnloadHandler : MarshalByRefObject
458                 {
459                         AutoResetEvent _unloaded = new AutoResetEvent(false);
460                         
461                         int _numRequestsPending = 0;
462                         object _syncUnloading = new object();
463                         object _syncNumRequestsPending = new object();
464                         
465                         internal void StartingRequest()
466                         {
467                                 // If the app domain is about to unload, wait
468                                 lock (_syncUnloading)
469                                         lock (_syncNumRequestsPending)
470                                                 _numRequestsPending++;
471                         }
472                         
473                         internal void FinishedRequest()
474                         {
475                                 // Let any unloading continue once there are not requests pending
476                                 lock (_syncNumRequestsPending) {
477                                         _numRequestsPending--;
478                                         if (_numRequestsPending == 0)
479                                                 Monitor.PulseAll(_syncNumRequestsPending);
480                                 }
481                         }
482                         
483                         public void OnUnload (object o, EventArgs args)
484                         {
485 #if !TARGET_JVM
486                 // Block new requests from starting
487                                 lock (_syncUnloading) {
488                                         // Wait for pending requests to finish
489                                         lock (_syncNumRequestsPending) {
490                                                 while (_numRequestsPending > 0)
491                                                         Monitor.Wait(_syncNumRequestsPending);
492                                         }
493                                         // Clear the host so that it will be created again on the next request
494                                         AppDomain.CurrentDomain.SetData (HOST_INSTANCE_NAME, null);
495                                         WebTest.host = null;
496                                         
497                                         EventHandler handler = WebTest.AppUnloaded;
498                                         if (handler != null)
499                                                 handler(this, null);
500                                 }
501 #endif
502             }
503                 }
504
505                 public static event EventHandler AppUnloaded;
506
507                 public static string TestBaseDir {
508                         get {
509 #if !TARGET_JVM
510                                 return baseDir;
511 #else
512                                 return String.Empty;
513 #endif
514                         }
515                 }
516                 
517 #if !TARGET_JVM
518                 const string VIRTUAL_BASE_DIR = "/NunitWeb";
519                 private static string baseDir;
520                 private static string binDir;
521                 const string HOST_INSTANCE_NAME = "MonoTests/SysWeb/Framework/Host";
522                 
523                 static void LoadAssemblyRecursive (Assembly ass)
524                 {
525                         if (ass.GlobalAssemblyCache)
526                                 return;
527                         foreach (AssemblyName ran in ass.GetReferencedAssemblies ()) {
528                                 bool found = false;
529                                 foreach (Assembly domain_ass in AppDomain.CurrentDomain.GetAssemblies ()) {
530                                         if (domain_ass.FullName == ran.FullName) {
531                                                 found = true;
532                                                 break;
533                                         }
534                                 }
535                                 if (found)
536                                         continue;
537                                 Assembly ra = Assembly.Load (ran, null);
538                                 LoadAssemblyRecursive (ra);
539                         }
540                 }
541
542                 private static void CopyAssembly (Assembly ass, string dir)
543                 {
544                         if (ass.GlobalAssemblyCache)
545                                 return;
546                         string oldfn = ass.Location;
547                         if (oldfn.EndsWith (".exe"))
548                                 return;
549                         string newfn = Path.Combine (dir, Path.GetFileName (oldfn));
550                         if (File.Exists (newfn))
551                                 return;
552                         EnsureDirectoryExists (dir);
553                         File.Copy (oldfn, newfn);
554                         if (File.Exists (oldfn + ".mdb"))
555                                 File.Copy (oldfn + ".mdb", newfn + ".mdb");
556                         if (File.Exists (oldfn + ".pdb"))
557                                 File.Copy (oldfn + ".pdb", newfn + ".pdb");
558                 }
559                 
560                 private static void EnsureDirectoryExists (string directory)
561                 {
562                         if (directory == string.Empty)
563                                 return;
564                         if (Directory.Exists (directory))
565                                 return;
566                         EnsureDirectoryExists (Path.GetDirectoryName (directory));
567                         Directory.CreateDirectory (directory);
568                 }
569
570                 private static void CheckDomainIsDown ()
571                 {
572                         if (host != null)
573                                 throw new DomainUpException ();
574                 }
575
576                 private static void EnsureWorkingDirectories ()
577                 {
578                         if (baseDir != null)
579                                 return;
580                         CreateWorkingDirectories ();
581                 }
582
583                 private static void CreateWorkingDirectories ()
584                 {
585                         string tmpFile = Path.GetTempFileName ();
586                         File.Delete (tmpFile);
587                         baseDir = tmpFile;
588                         Directory.CreateDirectory (tmpFile);
589                         binDir = Path.Combine (baseDir, "bin");
590                         Directory.CreateDirectory (binDir);
591                 }
592
593                 public static void CopyResources ()
594                 {
595                         Type myself = typeof (WebTest);
596                         
597                         CopyResource (myself, "My.ashx", "My.ashx");
598                         CopyResource (myself, "Global.asax", "Global.asax");
599 #if NET_2_0
600 #if INSIDE_SYSTEM_WEB
601                         CopyPrefixedResources (myself, "App_GlobalResources/", "App_GlobalResources");
602                         CopyPrefixedResources (myself, "App_Code/", "App_Code");
603 #endif
604                         CopyResource (myself, "Web.mono.config", "Web.config");
605 #else
606                         CopyResource (myself, "Web.mono.config.1.1", "Web.config");
607 #endif
608                         CopyResource (myself, "MyPage.aspx", "MyPage.aspx");
609                         CopyResource (myself, "MyPage.aspx.cs", "MyPage.aspx.cs");
610                         CopyResource (myself, "MyPageWithMaster.aspx", "MyPageWithMaster.aspx");
611                         CopyResource (myself, "My.master", "My.master");
612                 }
613 #endif
614         }
615 }