1 #if TARGET_JVM_FOR_WEBTEST
6 using System.Reflection;
9 using System.Web.Hosting;
11 using System.Threading;
13 namespace MonoTests.SystemWeb.Framework
16 /// The most important class from user perspective. See <see cref="Request"/>,
17 /// <see cref="Response"/>, <see cref="Invoker"/>, <see cref="Run"/> for
20 /// <seealso cref="Request"/>
21 /// <seealso cref="Response"/>
22 /// <seealso cref="Invoker"/>
23 /// <seealso cref="Run"/>
28 /// Thrown when trying to copy a resource after appdomain was created. Please call
29 /// WebTest.Unload before copying resource.
31 public class DomainUpException : Exception
37 /// Any user-defined data. Must be serializable to pass between appdomains.
41 /// public void SampleTest ()
43 /// WebTest t = new WebTest (new HandlerInvoker (MyCallback));
45 /// Assert.AreEqual ("Was here", t.UserData.ToString());
48 /// static public void MyCallback ()
50 /// WebTest.CurrentTest.UserData = "Was here";
54 public object UserData
56 get { return _userData; }
57 set { _userData = value; }
62 /// The result of the last <see cref="Run"/>. See <see cref="MonoTests.SystemWeb.Framework.Response"/>,
63 /// <see cref="FormRequest"/>.
65 /// <seealso cref="Run"/>
66 /// <seealso cref="MonoTests.SystemWeb.Framework.Response"/>
67 /// <seealso cref="FormRequest"/>
68 public Response Response
70 get { return _response; }
71 set { _response = value; }
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"/>
80 /// <seealso cref="Invoke"/>
81 /// <seealso cref="PageInvoker"/>
82 /// <seealso cref="BaseInvoker"/>
83 /// <seealso cref="HandlerInvoker"/>
84 public BaseInvoker Invoker
86 get { return _invoker; }
87 set { _invoker = value; }
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"/>.
96 /// <seealso cref="System.Web.HttpWorkerRequest"/>
97 /// <seealso cref="BaseRequest"/>
98 /// <seealso cref="PostableRequest"/>
99 /// <seealso cref="FormRequest"/>
100 public BaseRequest Request
102 get { return _request; }
103 set { _request = value; }
107 internal static MyHost Host
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.
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"/>
126 SystemWebTestShim.BuildManager.SuppressDebugModeMessages ();
128 if (Request.Url == null)
129 Request.Url = Invoker.GetDefaultUrl ();
130 _unloadHandler.StartingRequest();
132 WebTest newTestInstance = Host.Run (this);
133 CopyFrom (newTestInstance);
135 _unloadHandler.FinishedRequest();
137 return _response.Body;
140 private void CopyFrom (WebTest newTestInstance)
142 this._invoker = newTestInstance._invoker;
143 this._request = newTestInstance._request;
144 this._response = newTestInstance._response;
145 this._userData = newTestInstance._userData;
149 /// The instance of the currently running test. Defined only in the web appdomain.
150 /// In different threads this property may have different values.
152 public static WebTest CurrentTest
154 get { return MyHost.GetCurrentTest (); }
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.
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)
169 Invoker.DoInvoke (param);
171 catch (Exception ex) {
172 RegisterException (ex);
177 public void SendHeaders ()
179 Host.SendHeaders (this);
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.
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)
195 Host.RegisterException (ex);
199 /// Unload the web appdomain and delete the temporary application root
202 public static void CleanApp ()
206 lock (_appUnloadedSync) {
207 EventHandler handler = new EventHandler(PulseAppUnloadedSync);
208 WebTest.AppUnloaded += handler;
209 WebTest t = new WebTest (PageInvoker.CreateOnLoad (new PageDelegate (UnloadAppDomain_OnLoad)));
211 Monitor.Wait(_appUnloadedSync);
212 WebTest.AppUnloaded -= handler;
215 if (baseDir != null) {
216 Directory.Delete (baseDir, true);
223 private static object _appUnloadedSync = new object();
225 private static void PulseAppUnloadedSync(object source, EventArgs args)
227 lock (_appUnloadedSync)
228 Monitor.PulseAll(_appUnloadedSync);
231 public static void UnloadAppDomain_OnLoad (Page p)
233 HttpRuntime.UnloadAppDomain();
236 public static void Unload () {}
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"/>.
243 /// <seealso cref="Invoker"/>
244 /// <seealso cref="BaseInvoker"/>
245 /// <seealso cref="Request"/>
246 /// <seealso cref="BaseRequest"/>
249 Invoker = new BaseInvoker ();
250 Request = new BaseRequest ();
254 /// Same as <see cref="WebTest()"/>, and set <see cref="MonoTests.SystemWeb.Framework.BaseRequest.Url"/> to
255 /// the specified Url.
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)
267 /// Create a new instance, initializing <see cref="Invoker"/> with the given
268 /// value, and the <see cref="Request"/> with <see cref="BaseRequest"/>.
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)
281 /// Create a new instance, initializing <see cref="Request"/> with the given
282 /// value, and the <see cref="Invoker"/> with <see cref="BaseInvoker"/>.
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)
296 /// Copy a resource embedded in the assembly into the web application
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)
306 throw new ArgumentNullException ("type");
308 using (Stream source = type.Assembly.GetManifestResourceStream (resourceName)) {
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);
318 public static void CopyPrefixedResources (Type type, string namePrefix, string targetDir)
321 throw new ArgumentNullException ("type");
323 string[] manifestResources = type.Assembly.GetManifestResourceNames ();
324 if (manifestResources == null || manifestResources.Length == 0)
327 foreach (string resource in manifestResources) {
328 if (resource == null || resource.Length == 0)
331 if (!resource.StartsWith (namePrefix))
334 CopyResource (type, resource, Path.Combine (targetDir, resource.Substring (namePrefix.Length)));
339 /// Copy a chunk of data as a file into the web application.
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)
350 EnsureWorkingDirectories ();
351 EnsureDirectoryExists (Path.Combine (baseDir, Path.GetDirectoryName (targetUrl)));
352 string targetFile = Path.Combine (baseDir, targetUrl);
354 if (File.Exists(targetFile)) {
355 using (FileStream existing = File.OpenRead(targetFile)) {
357 if (sourceArray.Length == existing.Length) {
358 byte[] existingArray = new byte[sourceArray.Length];
359 existing.Read (existingArray, 0, existingArray.Length);
362 for (int i = 0; i < sourceArray.Length; i ++) {
363 if (sourceArray[i] != existingArray[i]) {
372 File.SetLastWriteTime (targetFile, DateTime.Now);
378 CheckDomainIsDown ();
381 using (FileStream target = new FileStream (targetFile, FileMode.Create)) {
382 target.Write (sourceArray, 0, sourceArray.Length);
389 static WebTestResourcesSetupAttribute.SetupHandler CheckResourcesSetupHandler ()
391 // It is assumed WebTest is included in the same assembly which contains the
393 object[] attributes = typeof (WebTest).Assembly.GetCustomAttributes (typeof (WebTestResourcesSetupAttribute), true);
394 if (attributes == null || attributes.Length == 0)
397 WebTestResourcesSetupAttribute attr = attributes [0] as WebTestResourcesSetupAttribute;
404 public static void EnsureHosting ()
409 host = new MyHost ();
412 host = AppDomain.CurrentDomain.GetData (HOST_INSTANCE_NAME) as MyHost;
418 public static void SetupHosting ()
423 public static void SetupHosting (WebTestResourcesSetupAttribute.SetupHandler resHandler)
427 host = AppDomain.CurrentDomain.GetData (HOST_INSTANCE_NAME) as MyHost;
432 host = new MyHost ();
435 if (resHandler == null)
436 resHandler = CheckResourcesSetupHandler ();
437 if (resHandler == null)
442 foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies ())
443 LoadAssemblyRecursive (ass);
445 foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies ())
446 CopyAssembly (ass, binDir);
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);
455 private static UnloadHandler _unloadHandler = new UnloadHandler();
457 public class UnloadHandler : MarshalByRefObject
459 AutoResetEvent _unloaded = new AutoResetEvent(false);
461 int _numRequestsPending = 0;
462 object _syncUnloading = new object();
463 object _syncNumRequestsPending = new object();
465 internal void StartingRequest()
467 // If the app domain is about to unload, wait
468 lock (_syncUnloading)
469 lock (_syncNumRequestsPending)
470 _numRequestsPending++;
473 internal void FinishedRequest()
475 // Let any unloading continue once there are not requests pending
476 lock (_syncNumRequestsPending) {
477 _numRequestsPending--;
478 if (_numRequestsPending == 0)
479 Monitor.PulseAll(_syncNumRequestsPending);
483 public void OnUnload (object o, EventArgs args)
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);
493 // Clear the host so that it will be created again on the next request
494 AppDomain.CurrentDomain.SetData (HOST_INSTANCE_NAME, null);
497 EventHandler handler = WebTest.AppUnloaded;
505 public static event EventHandler AppUnloaded;
507 public static string TestBaseDir {
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";
523 static void LoadAssemblyRecursive (Assembly ass)
525 if (ass.GlobalAssemblyCache)
527 foreach (AssemblyName ran in ass.GetReferencedAssemblies ()) {
529 foreach (Assembly domain_ass in AppDomain.CurrentDomain.GetAssemblies ()) {
530 if (domain_ass.FullName == ran.FullName) {
537 Assembly ra = Assembly.Load (ran, null);
538 LoadAssemblyRecursive (ra);
542 private static void CopyAssembly (Assembly ass, string dir)
544 if (ass.GlobalAssemblyCache)
546 string oldfn = ass.Location;
547 if (oldfn.EndsWith (".exe"))
549 string newfn = Path.Combine (dir, Path.GetFileName (oldfn));
550 if (File.Exists (newfn))
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");
560 private static void EnsureDirectoryExists (string directory)
562 if (directory == string.Empty)
564 if (Directory.Exists (directory))
566 EnsureDirectoryExists (Path.GetDirectoryName (directory));
567 Directory.CreateDirectory (directory);
570 private static void CheckDomainIsDown ()
573 throw new DomainUpException ();
576 private static void EnsureWorkingDirectories ()
580 CreateWorkingDirectories ();
583 private static void CreateWorkingDirectories ()
585 string tmpFile = Path.GetTempFileName ();
586 File.Delete (tmpFile);
588 Directory.CreateDirectory (tmpFile);
589 binDir = Path.Combine (baseDir, "bin");
590 Directory.CreateDirectory (binDir);
593 public static void CopyResources ()
595 Type myself = typeof (WebTest);
597 CopyResource (myself, "My.ashx", "My.ashx");
598 CopyResource (myself, "Global.asax", "Global.asax");
600 #if INSIDE_SYSTEM_WEB
601 CopyPrefixedResources (myself, "App_GlobalResources/", "App_GlobalResources");
602 CopyPrefixedResources (myself, "App_Code/", "App_Code");
604 CopyResource (myself, "Web.mono.config", "Web.config");
606 CopyResource (myself, "Web.mono.config.1.1", "Web.config");
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");