using System; using System.Reflection; using System.IO; using System.Web; using System.Web.Hosting; using System.Web.UI; using System.Threading; namespace MonoTests.SystemWeb.Framework { /// /// The most important class from user perspective. See , /// , , for /// more information. /// /// /// /// /// [Serializable] public partial class WebTest { /// /// Thrown when trying to copy a resource after appdomain was created. Please call /// WebTest.Unload before copying resource. /// public class DomainUpException : Exception { } object _userData; /// /// Any user-defined data. Must be serializable to pass between appdomains. /// /// /// [Test] /// public void SampleTest () /// { /// WebTest t = new WebTest (new HandlerInvoker (MyCallback)); /// t.Run (); /// Assert.AreEqual ("Was here", t.UserData.ToString()); /// } /// /// static public void MyCallback () /// { /// WebTest.CurrentTest.UserData = "Was here"; /// } /// public object UserData { get { return _userData; } set { _userData = value; } } Response _response; /// /// The result of the last . See , /// . /// /// /// /// public Response Response { get { return _response; } set { _response = value; } } BaseInvoker _invoker; /// /// Set the invoker, which is executed in the web context by /// method. Most commonly used . See also: , /// /// /// /// /// /// public BaseInvoker Invoker { get { return _invoker; } set { _invoker = value; } } BaseRequest _request; /// /// Contains all the data necessary to create an in /// the application appdomain. See also , /// , . /// /// /// /// /// public BaseRequest Request { get { return _request; } set { _request = value; } } static MyHost host; internal static MyHost Host { get { EnsureHosting (); return host; } } /// /// Run the request using and /// values. Keep the result of the request in property. /// /// The body of the HTTP response (). /// /// /// /// public string Run () { #if !DOTNET SystemWebTestShim.BuildManager.SuppressDebugModeMessages (); #endif if (Request.Url == null) Request.Url = Invoker.GetDefaultUrl (); _unloadHandler.StartingRequest(); try { WebTest newTestInstance = Host.Run (this); CopyFrom (newTestInstance); } finally { _unloadHandler.FinishedRequest(); } return _response.Body; } private void CopyFrom (WebTest newTestInstance) { this._invoker = newTestInstance._invoker; this._request = newTestInstance._request; this._response = newTestInstance._response; this._userData = newTestInstance._userData; } /// /// The instance of the currently running test. Defined only in the web appdomain. /// In different threads this property may have different values. /// public static WebTest CurrentTest { get { return MyHost.GetCurrentTest (); } } /// /// This method must be called when custom or aspx code behind is used, /// to allow the framework to invoke all user supplied delegates. /// /// Parameter defined by the subclass. For example, /// expects to receive a instance here. /// /// /// public void Invoke (object param) { try { Invoker.DoInvoke (param); } catch (Exception ex) { RegisterException (ex); throw; } } public void SendHeaders () { Host.SendHeaders (this); } /// /// This method is intended for use from when /// the invocation causes an exception. In such cases, the exception must be registered /// with this method, and then swallowed. Before returning, /// will rethrow this exception. This is done to hide the exception from , /// which normally swallows the exception and returns 500 ERROR http result. /// /// The exception to be registered and rethrown. /// /// /// public static void RegisterException (Exception ex) { Host.RegisterException (ex); } /// /// Unload the web appdomain and delete the temporary application root /// directory. /// public static void CleanApp () { if (host != null) { lock (_appUnloadedSync) { EventHandler handler = new EventHandler(PulseAppUnloadedSync); WebTest.AppUnloaded += handler; WebTest t = new WebTest (PageInvoker.CreateOnLoad (new PageDelegate (UnloadAppDomain_OnLoad))); t.Run (); Monitor.Wait(_appUnloadedSync); WebTest.AppUnloaded -= handler; } } if (baseDir != null) { Directory.Delete (baseDir, true); baseDir = null; binDir = null; } } private static object _appUnloadedSync = new object(); private static void PulseAppUnloadedSync(object source, EventArgs args) { lock (_appUnloadedSync) Monitor.PulseAll(_appUnloadedSync); } public static void UnloadAppDomain_OnLoad (Page p) { HttpRuntime.UnloadAppDomain(); } public static void Unload () {} /// /// Default constructor. Initializes with a new /// and with an empty /// . /// /// /// /// /// public WebTest () { Invoker = new BaseInvoker (); Request = new BaseRequest (); } /// /// Same as , and set to /// the specified Url. /// /// The URL used for the next /// /// public WebTest (string url) : this () { Request.Url = url; } /// /// Create a new instance, initializing with the given /// value, and the with . /// /// The invoker used for this test. /// /// /// public WebTest (BaseInvoker invoker) : this () { Invoker = invoker; } /// /// Create a new instance, initializing with the given /// value, and the with . /// /// The request used for this test. /// /// /// public WebTest (BaseRequest request) : this () { Request = request; } /// /// Copy a resource embedded in the assembly into the web application /// /// A type in the assembly that contains the embedded resource. /// The name of the resource. /// The URL where the resource will be available /// Thrown when resource with name resourceName is not found. /// CopyResource (GetType (), "Default.skin", "App_Themes/Black/Default.skin"); public static void CopyResource (Type type, string resourceName, string targetUrl) { if (type == null) throw new ArgumentNullException ("type"); using (Stream source = type.Assembly.GetManifestResourceStream (resourceName)) { if (source == null) throw new ArgumentException ("resource not found: " + resourceName, "resourceName"); byte[] array = new byte[source.Length]; source.Read (array, 0, array.Length); CopyBinary (array, targetUrl); } } public static void CopyPrefixedResources (Type type, string namePrefix, string targetDir) { if (type == null) throw new ArgumentNullException ("type"); string[] manifestResources = type.Assembly.GetManifestResourceNames (); if (manifestResources == null || manifestResources.Length == 0) return; foreach (string resource in manifestResources) { if (resource == null || resource.Length == 0) continue; if (!resource.StartsWith (namePrefix)) continue; // The Replace part is for VisualStudio which compiles .resx files despite them being marked as // embedded resources, which breaks the tests. CopyResource (type, resource, Path.Combine (targetDir, resource.Substring (namePrefix.Length).Replace (".remove_extension", String.Empty))); } } /// /// Copy a chunk of data as a file into the web application. /// /// The array that contains the data to be written. /// The URL where the data will be available. /// The target filename where the data was stored. /// CopyBinary (System.Text.Encoding.UTF8.GetBytes ("Hello"), "App_Data/Greeting.txt"); public static string CopyBinary (byte[] sourceArray, string targetUrl) { EnsureWorkingDirectories (); EnsureDirectoryExists (Path.Combine (baseDir, Path.GetDirectoryName (targetUrl))); string targetFile = Path.Combine (baseDir, targetUrl); if (File.Exists(targetFile)) { using (FileStream existing = File.OpenRead(targetFile)) { bool equal = false; if (sourceArray.Length == existing.Length) { byte[] existingArray = new byte[sourceArray.Length]; existing.Read (existingArray, 0, existingArray.Length); equal = true; for (int i = 0; i < sourceArray.Length; i ++) { if (sourceArray[i] != existingArray[i]) { equal = false; break; } } } if (equal) { existing.Close (); File.SetLastWriteTime (targetFile, DateTime.Now); return targetFile; } } CheckDomainIsDown (); } using (FileStream target = new FileStream (targetFile, FileMode.Create)) { target.Write (sourceArray, 0, sourceArray.Length); } return targetFile; } static WebTestResourcesSetupAttribute.SetupHandler CheckResourcesSetupHandler () { // It is assumed WebTest is included in the same assembly which contains the // tests themselves object[] attributes = typeof (WebTest).Assembly.GetCustomAttributes (typeof (WebTestResourcesSetupAttribute), true); if (attributes == null || attributes.Length == 0) return null; WebTestResourcesSetupAttribute attr = attributes [0] as WebTestResourcesSetupAttribute; if (attr == null) return null; return attr.Handler; } public static void EnsureHosting () { if (host != null) return; host = AppDomain.CurrentDomain.GetData (HOST_INSTANCE_NAME) as MyHost; if (host == null) SetupHosting (); } public static void SetupHosting () { SetupHosting (null); } public static void SetupHosting (WebTestResourcesSetupAttribute.SetupHandler resHandler) { if (host == null) host = AppDomain.CurrentDomain.GetData (HOST_INSTANCE_NAME) as MyHost; if (host != null) CleanApp (); if (resHandler == null) resHandler = CheckResourcesSetupHandler (); if (resHandler == null) CopyResources (); else resHandler (); foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies ()) LoadAssemblyRecursive (ass); foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies ()) CopyAssembly (ass, binDir); host = (MyHost) ApplicationHost.CreateApplicationHost (typeof (MyHost), VIRTUAL_BASE_DIR, baseDir); AppDomain.CurrentDomain.SetData (HOST_INSTANCE_NAME, host); host.AppDomain.SetData (HOST_INSTANCE_NAME, host); host.AppDomain.DomainUnload += new EventHandler (_unloadHandler.OnUnload); } private static UnloadHandler _unloadHandler = new UnloadHandler(); public class UnloadHandler : MarshalByRefObject { AutoResetEvent _unloaded = new AutoResetEvent(false); int _numRequestsPending = 0; object _syncUnloading = new object(); object _syncNumRequestsPending = new object(); internal void StartingRequest() { // If the app domain is about to unload, wait lock (_syncUnloading) lock (_syncNumRequestsPending) _numRequestsPending++; } internal void FinishedRequest() { // Let any unloading continue once there are not requests pending lock (_syncNumRequestsPending) { _numRequestsPending--; if (_numRequestsPending == 0) Monitor.PulseAll(_syncNumRequestsPending); } } public void OnUnload (object o, EventArgs args) { // Block new requests from starting lock (_syncUnloading) { // Wait for pending requests to finish lock (_syncNumRequestsPending) { while (_numRequestsPending > 0) Monitor.Wait(_syncNumRequestsPending); } // Clear the host so that it will be created again on the next request AppDomain.CurrentDomain.SetData (HOST_INSTANCE_NAME, null); WebTest.host = null; EventHandler handler = WebTest.AppUnloaded; if (handler != null) handler(this, null); } } } public static event EventHandler AppUnloaded; public static string TestBaseDir { get { return baseDir; } } const string VIRTUAL_BASE_DIR = "/NunitWeb"; private static string baseDir; private static string binDir; const string HOST_INSTANCE_NAME = "MonoTests/SysWeb/Framework/Host"; static void LoadAssemblyRecursive (Assembly ass) { if (ass.GlobalAssemblyCache) return; foreach (AssemblyName ran in ass.GetReferencedAssemblies ()) { bool found = false; foreach (Assembly domain_ass in AppDomain.CurrentDomain.GetAssemblies ()) { if (domain_ass.FullName == ran.FullName) { found = true; break; } } if (found) continue; Assembly ra = Assembly.Load (ran, null); LoadAssemblyRecursive (ra); } } private static void CopyAssembly (Assembly ass, string dir) { if (ass.GlobalAssemblyCache || ass.FullName.StartsWith ("mscorlib")) return; string oldfn = ass.Location; if (oldfn.EndsWith (".exe")) return; string newfn = Path.Combine (dir, Path.GetFileName (oldfn)); if (File.Exists (newfn)) return; EnsureDirectoryExists (dir); File.Copy (oldfn, newfn); if (File.Exists (oldfn + ".mdb")) File.Copy (oldfn + ".mdb", newfn + ".mdb"); if (File.Exists (oldfn + ".pdb")) File.Copy (oldfn + ".pdb", newfn + ".pdb"); } private static void EnsureDirectoryExists (string directory) { if (directory == string.Empty) return; if (Directory.Exists (directory)) return; EnsureDirectoryExists (Path.GetDirectoryName (directory)); Directory.CreateDirectory (directory); } private static void CheckDomainIsDown () { if (host != null) throw new DomainUpException (); } private static void EnsureWorkingDirectories () { if (baseDir != null) return; CreateWorkingDirectories (); } private static void CreateWorkingDirectories () { string tmpFile = Path.GetTempFileName (); File.Delete (tmpFile); baseDir = tmpFile; Directory.CreateDirectory (tmpFile); binDir = Path.Combine (baseDir, "bin"); Directory.CreateDirectory (binDir); } public static void CopyResources () { Type myself = typeof (WebTest); CopyResource (myself, "My.ashx", "My.ashx"); CopyResource (myself, "Global.asax", "Global.asax"); CopyResource (myself, "MyPage.aspx", "MyPage.aspx"); CopyResource (myself, "MyPage.aspx.cs", "MyPage.aspx.cs"); CopyResource (myself, "MyPageWithMaster.aspx", "MyPageWithMaster.aspx"); CopyResource (myself, "My.master", "My.master"); CopyResourcesLocal (); } static partial void CopyResourcesLocal (); } }