2009-02-18 8 views
9

Estoy usando Cassini/WebServer.WebDev para ejecutar algunas pruebas automáticas de un servicio web usando NUnit.Cassini/WebServer.WebDev, NUnit y AppDomainUnloadedException

no estoy haciendo nada especial, simplemente

public class WebService{ 
    Microsoft.VisualStudio.WebHost.Server _server; 

    public void Start(){ 
    _server = new Microsoft.VisualStudio.WebHost.Server(_port, "/", _physicalPath); 
    } 

    public void Dispose() 
    { 
    if (_server != null) 
    { 
     _server.Stop(); 
     _server = null; 
    } 
    } 
} 
[TestFixture] 
public void TestFixture{ 
    [Test] 
    public void Test(){ 
    using(WebService webService = new WebService()){ 
     webService.Start(); 
     // actual test invoking the webservice 
    } 
    } 
} 

, pero cuando lo ejecuto usando nunit-console.exe, me sale el siguiente resultado:

NUnit version 2.5.0.9015 (Beta-2) 
Copyright (C) 2002-2008 Charlie Poole.\r\nCopyright (C) 2002-2004 James W. Newki 
rk, Michael C. Two, Alexei A. Vorontsov.\r\nCopyright (C) 2000-2002 Philip Craig 
.\r\nAll Rights Reserved. 

Runtime Environment - 
    OS Version: Microsoft Windows NT 6.0.6001 Service Pack 1 
    CLR Version: 2.0.50727.1434 (Net 2.0.50727.1434) 

ProcessModel: Default DomainUsage: Default 
Execution Runtime: net-2.0.50727.1434 
..... 
Tests run: 5, Errors: 0, Failures: 0, Inconclusive: 0 Time: 28,4538451 seconds 
    Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0 


Unhandled exceptions: 
1) TestCase1 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
2) TestCase2 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
3) TestCase3 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
4) TestCase4 : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 

Si me quedo nunit- consola en el depurador, me sale el siguiente resultado en la consola de depuración:

[...] 
The thread 0x1974 has exited with code 0 (0x0). 
############################################################################ 
##############     S U C C E S S    ################# 
############################################################################ 
Executed tests  : 5 
Ignored tests  : 0 
Failed tests   : 0 
Unhandled exceptions : 4 
Total time   : 25,7092944 seconds 
############################################################################ 
The thread 0x1bd4 has exited with code 0 (0x0). 
The thread 0x10f8 has exited with code 0 (0x0). 
The thread '<No Name>' (0x1a80) has exited with code 0 (0x0). 
A first chance exception of type 'System.AppDomainUnloadedException' occurred in System.Web.dll 
##### Unhandled Exception while running 
System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
    at System.Web.Hosting.ApplicationManager.HostingEnvironmentShutdownComplete(String appId, IApplicationHost appHost) 
    at System.Web.Hosting.HostingEnvironment.OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) 
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll 
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll 
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in System.Web.dll 
The thread 0x111c has exited with code 0 (0x0). 
The program '[0x1A64] nunit-console.exe: Managed' has exited with code -100 (0xffffff9c). 

hacer alguien tiene alguna idea de lo que podría b e causando esto?

+0

¿Alguna vez encontró una solución a este problema? Estoy teniendo un problema similar. –

+0

No es una/buena/solución, pero terminé configurando failonerror = "false" en NUnit-task y postprocesando el xml-output con herramientas separadas, que ignoraron el AppDomainUnloadedException no administrado. –

+0

He subido una solución de ejemplo que demuestra el problema al error NUnit apropiado en https://bugs.launchpad.net/nunitv2/+bug/423611. sería genial si pudieras probarlo y verificar su validez (y publicar tus hallazgos en launchpad) –

Respuesta

7

Tuve el mismo problema, pero no estaba usando Cassini. En cambio, tenía mi propio servidor web hosting basado en System.Net.HttpListener con soporte ASP.Net a través de System.Web.HttpRuntime ejecutándose en un dominio de aplicación diferente creado a través de System.Web.Hosting.ApplicationHost.CreateApplicationHost(). Esta es esencialmente la forma en que funciona Cassini, excepto que Cassini trabaja en la capa de socket e implementa mucha de la funcionalidad provista por el propio System.Net.HttpListener.

De todos modos, para resolver mi problema, necesitaba llamar al System.Web.HttpRuntime.Close() antes de dejar que NUnit descargara el dominio de mi aplicación. Hice esto al exponer un nuevo método Close() en la clase proxy de mi servidor que es invocado por el método [TearDown] de mi clase [SetupFixture] y ese método llama al System.Web.HttpRuntime.Close().

Miré la implementación de Cassini a través de .Net Reflector y, aunque usa System.Web.HttpRuntime.ProcessRequest(), no parece llamar al System.Web.HttpRuntime.Close() en ninguna parte.

No estoy exactamente seguro de cómo puede seguir usando la implementación preinstalada de Cassini (Microsoft.VisualStudio.WebHost.Server), ya que necesita obtener la llamada System.Web.HttpRuntime.Close() dentro del dominio de la aplicación creado por Cassini para alojar ASP.Net.

Como referencia, aquí hay algunas piezas de mi prueba de unidad de trabajo con alojamiento web incorporado.

Mi clase WebServerHost es una clase muy pequeña que permite ordenar las solicitudes en el dominio de la aplicación creado por System.Web.Hosting.ApplicationHost.CreateApplicationHost().

using System; 
using System.IO; 
using System.Web; 
using System.Web.Hosting; 

public class WebServerHost : 
    MarshalByRefObject 
{ 
    public void 
    Close() 
    { 
     HttpRuntime.Close(); 
    } 

    public void 
    ProcessRequest(WebServerContext context) 
    { 
     HttpRuntime.ProcessRequest(new WebServerRequest(context)); 
    } 
} 

La clase WebServerContext es simplemente una envoltura alrededor de una instancia System.Net.HttpListenerContext que se deriva de System.MarshalByRefObject para permitir llamadas desde el nuevo dominio de alojamiento ASP.Net para llamar de nuevo en mi dominio.

using System; 
using System.Net; 

public class WebServerContext : 
    MarshalByRefObject 
{ 
    public 
    WebServerContext(HttpListenerContext context) 
    { 
     this.context = context; 
    } 

    // public methods and properties that forward to HttpListenerContext omitted 

    private HttpListenerContext 
    context; 
} 

La clase WebServerRequest es sólo una implementación de la clase abstracta System.Web.HttpWorkerRequest que llama de nuevo en mi dominio del dominio de alojamiento ASP.Net través de la clase WebServerContext.

using System; 
using System.IO; 
using System.Web; 

class WebServerRequest : 
    HttpWorkerRequest 
{ 
    public 
    WebServerRequest(WebServerContext context) 
    { 
     this.context = context; 
    } 

    // implementation of HttpWorkerRequest methods omitted; they all just call 
    // methods and properties on context 

    private WebServerContext 
    context; 
} 

La clase WebServer es un controlador para iniciar y detener el servidor web. Cuando se inicia, el dominio de alojamiento ASP.Net se crea con mi clase WebServerHost como proxy para permitir la interacción. También se inicia una instancia System.Net.HttpListener y se inicia un subproceso independiente para aceptar conexiones.Cuando se realizan las conexiones, se inicia un subproceso de trabajo en el grupo de subprocesos para gestionar la solicitud, de nuevo a través de mi clase WebServerHost. Finalmente, cuando se detiene el servidor web, el oyente se detiene, el controlador espera a que salga la conexión que acepta el hilo y luego se cierra el oyente. Finalmente, el tiempo de ejecución de HTTP también se cierra mediante una llamada al método WebServerHost.Close().

using System; 
using System.IO; 
using System.Net; 
using System.Reflection; 
using System.Threading; 
using System.Web.Hosting; 

class WebServer 
{ 
    public static void 
    Start() 
    { 
     lock (typeof(WebServer)) 
     { 
      // do not start more than once 
      if (listener != null) 
       return; 

      // create web server host in new AppDomain 
      host = 
       (WebServerHost)ApplicationHost.CreateApplicationHost 
       (
        typeof(WebServerHost), 
        "/", 
        Path.GetTempPath() 
       ); 

      // start up the HTTP listener 
      listener = new HttpListener(); 
      listener.Prefixes.Add("http://*:8182/"); 
      listener.Start(); 

      acceptConnectionsThread = new Thread(acceptConnections); 
      acceptConnectionsThread.Start(); 
     } 
    } 

    public static void 
    Stop() 
    { 
     lock (typeof(WebServer)) 
     { 
      if (listener == null) 
       return; 

      // stop listening; will cause HttpListenerException in thread blocked on GetContext() 
      listener.Stop(); 

      // wait connection acceptance thread to exit 
      acceptConnectionsThread.Join(); 
      acceptConnectionsThread = null; 

      // close listener 
      listener.Close(); 
      listener = null; 

      // close host 
      host.Close(); 
      host = null; 
     } 
    } 

    private static WebServerHost 
    host = null; 

    private static HttpListener 
    listener = null; 

    private static Thread 
    acceptConnectionsThread; 

    private static void 
    acceptConnections(object state) 
    { 
     while (listener.IsListening) 
     { 
      try 
      { 
       HttpListenerContext context = listener.GetContext(); 
       ThreadPool.QueueUserWorkItem(handleConnection, context); 
      } 
      catch (HttpListenerException e) 
      { 
       // this exception is ignored; it will be thrown when web server is stopped and at that time 
       // listening will be set to false which will end the loop and the thread 
      } 
     } 
    } 

    private static void 
    handleConnection(object state) 
    { 
     host.ProcessRequest(new WebServerContext((HttpListenerContext)state)); 
    } 
} 

Por último, esta clase Initialization, marcado con el atributo NUnit [SetupFixture], se utiliza para iniciar el servidor web cuando se inician las pruebas unitarias, y apagarlo cuando se hayan completado.

using System; 
using NUnit.Framework; 

[SetUpFixture] 
public class Initialization 
{ 
    [SetUp] 
    public void 
    Setup() 
    { 
     // start the local web server 
     WebServer.Start(); 
    } 

    [TearDown] 
    public void 
    TearDown() 
    { 
     // stop the local web server 
     WebServer.Stop(); 
    } 
} 

Sé que esto no está respondiendo exactamente la pregunta, pero espero que encuentre la información útil.

+0

Me pregunto, ¿cómo sería una prueba en particular? ¿Su cuerpo estaría envuelto en algún tipo de llamada de ejecución para delegar la ejecución real en el AppDomain creado? –