Cassini/WebServer.WebDev、NUnit 和 AppDomainUnloadedException

发布于 2024-07-14 00:21:35 字数 3327 浏览 4 评论 0原文

我正在使用 Cassini/WebServer.WebDev 使用 NUnit 运行 WebService 的一些自动化测试。

我没有做任何花哨的事情,只是

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
    }
  }
}

,但是当我使用 nunit-console.exe 运行它时,我得到以下输出:

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.

如果我在调试器下运行 nunit-console,我会在调试控制台中得到以下输出:

[...]
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).

有人有吗有什么想法可能会导致这种情况吗?

I am using Cassini/WebServer.WebDev to run some automated tests of a WebService using NUnit.

I am not doing anything fancy, just

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
    }
  }
}

, but when I run it using nunit-console.exe, I get the following output:

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.

If I run nunit-console under the debugger, I get the following output in the debug console:

[...]
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).

Do anyone have any ideas what could be causing this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

尤怨 2024-07-21 00:21:35

我有同样的问题,但没有使用卡西尼号。 相反,我拥有自己的基于 System.Net.HttpListener 的 Web 服务器托管,通过 System.Web.HttpRuntime 运行在通过 System.Web.HttpRuntime 创建的不同应用程序域中,并通过 ASP.Net 支持。代码>System.Web.Hosting.ApplicationHost.CreateApplicationHost()。 这本质上就是 Cassini 的工作方式,只不过 Cassini 在套接字层工作并实现了 System.Net.HttpListener 本身提供的许多功能。

不管怎样,为了解决我的问题,我需要在让 NUnit 卸载我的应用程序域之前调用 System.Web.HttpRuntime.Close()。 我通过在主机代理类中公开一个新的 Close() 方法来做到这一点,该方法由我的 [SetupFixture] 类的 [TearDown] 方法调用,并且该方法调用 System.Web.HttpRuntime .Close()

我通过 .Net Reflector 查看了 Cassini 实现,尽管它使用 System.Web.HttpRuntime.ProcessRequest(),但它似乎没有调用 System.Web.HttpRuntime.Close( ) 任何地方。

我不太确定如何继续使用预构建的 Cassini 实现 (Microsoft.VisualStudio.WebHost.Server),因为您需要获取 System.Web.HttpRuntime.Close () 调用发生在 Cassini 创建的用于托管 ASP.Net 的应用程序域内。

作为参考,这里是我使用嵌入式网络托管进行的工作单元测试的一些部分。

我的 WebServerHost 类是一个非常小的类,它允许将请求编组到由 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));
    }
}

WebServerContext 类只是一个 System.Net.HttpListenerContext 实例的包装器,该实例派生自 System.MarshalByRefObject,以允许来自新 ASP.Net 托管域的调用回调到我的域名。

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;
}

WebServerRequest 类只是抽象 System.Web.HttpWorkerRequest 类的实现,它通过 WebServerContext< 从 ASP.Net 托管域回调到我的域/代码> 类。

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;
}

WebServer 类是用于启动和停止 Web 服务器的控制器。 启动后,将使用我的 WebServerHost 类创建 ASP.Net 托管域作为代理以允许交互。 System.Net.HttpListener 实例也会启动,并启动一个单独的线程来接受连接。 建立连接后,线程池中会启动一个工作线程来处理请求,同样是通过我的 WebServerHost 类。 最后,当Web服务器停止时,监听器也停止,控制器等待接受连接的线程退出,然后关闭监听器。 最后,HTTP 运行时也通过调用 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));
    }
}

最后,这个 Initialization 类标记为NUnit [SetupFixture] 属性,用于在单元测试开始时启动 Web 服务器,并在单元测试完成时关闭它。

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();
    }
}

我知道这并不能完全回答问题,但我希望这些信息对您有用。

I had the same problem, but was not using Cassini. Instead, I had my own web server hosting based on System.Net.HttpListener with ASP.Net support through System.Web.HttpRuntime running in a different application domain created via System.Web.Hosting.ApplicationHost.CreateApplicationHost(). This is essentially the way Cassini works, except that Cassini works at the socket layer and implements a lot of the functionality provided by System.Net.HttpListener itself.

Anyway, to solve my problem, I needed to call System.Web.HttpRuntime.Close() before letting NUnit unload my application domain. I did this by exposing a new Close() method in my host proxy class that is invoked by the [TearDown] method of my [SetupFixture] class and that method calls System.Web.HttpRuntime.Close().

I looked at the Cassini implementation through .Net Reflector and, although it uses System.Web.HttpRuntime.ProcessRequest(), it doesn't seem to call System.Web.HttpRuntime.Close() anywhere.

I'm not exactly sure how you can keep using the pre-built Cassini implementation (Microsoft.VisualStudio.WebHost.Server), as you need to get the System.Web.HttpRuntime.Close() call to occur within the application domain created by Cassini to host ASP.Net.

For reference, here are some pieces of my working unit test with embedded web hosting.

My WebServerHost class is a very small class that allows marshaling requests into the application domain created by 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));
    }
}

The WebServerContext class is simply a wrapper around a System.Net.HttpListenerContext instance that derives from System.MarshalByRefObject to allow calls from the new ASP.Net hosting domain to call back into my domain.

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;
}

The WebServerRequest class is just an implementation of the abstract System.Web.HttpWorkerRequest class that calls back into my domain from the ASP.Net hosting domain via the WebServerContext class.

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;
}

The WebServer class is a controller for starting and stopping the web server. When started, the ASP.Net hosting domain is created with my WebServerHost class as a proxy to allow interaction. A System.Net.HttpListener instance is also started and a separate thread is started to accept connections. When connections are made, a worker thread is started in the thread pool to handle the request, again via my WebServerHost class. Finally, when the web server is stopped, the listener is stopped, the controller waits for the thread accepting connections to exit, and then the listener is closed. Finally, the HTTP runtime is also closed via a call into the WebServerHost.Close() method.

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));
    }
}

Finally, this Initialization class, marked with the NUnit [SetupFixture] attribute, is used to start the web server when the unit tests are started, and shut it down when they are completed.

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();
    }
}

I know that this is not exactly answering the question, but I hope you find the information useful.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文