2009-02-18 3 views
9

Я использую Cassini/WebServer.WebDev для запуска некоторых автоматических тестов WebService с использованием NUnit.Cassini/WebServer.WebDev, NUnit и AppDomainUnloadedException

Я не делал ничего сверхъестественного, просто

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- консоль под отладчиком, я получаю следующий вывод в консоли отладки:

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

Есть ли у кого-нибудь идеи, что может b e это?

+0

Вы когда-нибудь находили решение этой проблемы? У меня такая же проблема. –

+0

Не было/хорошее/решение, но я закончил установку failonerror = "false" в NUnit-задаче и постобработку xml-вывода с помощью отдельных инструментов, которые игнорировали необработанное исключение AppDomainUnloadedException. –

+0

Я загрузил примерное решение, демонстрирующее проблему с соответствующей ошибкой NUnit на странице https://bugs.launchpad.net/nunitv2/+bug/423611. было бы здорово, если бы вы могли попробовать и перекрестно проверить его достоверность (и опубликовать свои результаты на панели запуска) –

ответ

7

У меня была такая же проблема, но не использовался Кассини. Вместо этого у меня был собственный хостинг веб-серверов на основе System.Net.HttpListener с поддержкой ASP.Net через System.Web.HttpRuntime, работающий в другом домене приложения, созданного с помощью System.Web.Hosting.ApplicationHost.CreateApplicationHost(). Это, по сути, способ работы Cassini, за исключением того, что Cassini работает на уровне сокетов и реализует множество функций, предоставляемых самим System.Net.HttpListener.

В любом случае, чтобы решить мою проблему, мне нужно было позвонить System.Web.HttpRuntime.Close(), прежде чем позволить NUnit разгрузить домен моего приложения. Я сделал это, обнажив новый метод Close() в моем прокси-классе хоста, который вызывается методом [TearDown] моего класса [SetupFixture], и ​​этот метод вызывает System.Web.HttpRuntime.Close().

Я посмотрел на реализацию Кассини через .Net Reflector и, хотя он использует System.Web.HttpRuntime.ProcessRequest(), он, кажется, не звонит System.Web.HttpRuntime.Close() в любом месте.

Я не совсем уверен, как вы можете продолжать использовать встроенную реализацию Cassini (Microsoft.VisualStudio.WebHost.Server), так как вам нужно получить вызов System.Web.HttpRuntime.Close() в домене приложения, созданного Cassini для размещения ASP.Net.

Для справки, вот некоторые части теста моего рабочего блока со встроенным веб-хостингом.

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 класса, который вызывает обратно в мой домен от хостинга домена ASP.Net через 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; 
} 

WebServer класс представляет собой контроллер для запуска и остановки веб-сервера. При запуске домен хоста ASP.Net создается с моим классом WebServerHost в качестве прокси для взаимодействия. Также запускается экземпляр System.Net.HttpListener, и для приема соединений запускается отдельный поток.Когда соединения выполняются, рабочий поток запускается в пуле потоков для обработки запроса, опять же через мой класс WebServerHost. Наконец, когда веб-сервер остановлен, слушатель остановлен, контроллер ожидает, что поток, принимающий соединения, будет удален, а затем слушатель будет закрыт. Наконец, время выполнения 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 класс, отмеченные [SetupFixture] атрибут NUnit, используется для запуска веб-сервера при запуске юнит-тестов, и закрыть его, когда они будут завершены.

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

Я знаю, что это точно не отвечает на этот вопрос, но я надеюсь, что вы найдете полезную информацию.

+0

Интересно, какой будет конкретный тест? Будет ли его тело обернуто каким-то вызовом Execute, чтобы делегировать фактическое выполнение созданному AppDomain? –

Смежные вопросы