#region Copyright (c) 2002-2003, James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole, Philip A. Craig
/************************************************************************************
'
' Copyright © 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
' Copyright © 2000-2003 Philip A. Craig
'
' This software is provided 'as-is', without any express or implied warranty. In no
' event will the authors be held liable for any damages arising from the use of this
' software.
'
' Permission is granted to anyone to use this software for any purpose, including
' commercial applications, and to alter it and redistribute it freely, subject to the
' following restrictions:
'
' 1. The origin of this software must not be misrepresented; you must not claim that
' you wrote the original software. If you use this software in a product, an
' acknowledgment (see the following) in the product documentation is required.
'
' Portions Copyright © 2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
' or Copyright © 2000-2003 Philip A. Craig
'
' 2. Altered source versions must be plainly marked as such, and must not be
' misrepresented as being the original software.
'
' 3. This notice may not be removed or altered from any source distribution.
'
'***********************************************************************************/
#endregion
using System;
namespace NUnit.Core
{
using System.Runtime.Remoting;
using System.Security.Policy;
using System.Reflection;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
public class TestDomain
{
#region Instance Variables
///
/// The appdomain used to load tests
///
private AppDomain domain;
///
/// The path to our cache
///
private string cachePath;
///
/// The remote runner loaded in the test appdomain
///
private RemoteTestRunner testRunner;
#endregion
#region Properties
public bool IsTestLoaded
{
get { return testRunner != null; }
}
public Test Test
{
get { return IsTestLoaded ? testRunner.Test : null; }
}
public string TestName
{
get { return testRunner.TestName; }
set { testRunner.TestName = value; }
}
#endregion
#region Public Members
#region Load a single assembly
public Test LoadAssembly( string assemblyFileName )
{
return LoadAssembly( assemblyFileName, null );
}
public Test LoadAssembly(string assemblyFileName, string testFixture)
{
FileInfo testFile = new FileInfo( assemblyFileName );
ThrowIfAlreadyLoaded();
try
{
string assemblyPath = Path.GetFullPath( assemblyFileName );
string domainName = string.Format( "domain-{0}", Path.GetFileName( assemblyFileName ) );
domain = MakeAppDomain( domainName, testFile.DirectoryName, testFile.FullName + ".config", testFile.DirectoryName );
testRunner = MakeRemoteTestRunner( domain );
if(testRunner != null)
{
testRunner.TestFileName = assemblyPath;
if ( testFixture != null )
testRunner.TestName = testFixture;
domain.DoCallBack( new CrossAppDomainDelegate( testRunner.BuildSuite ) );
return testRunner.Test;
}
else
{
Unload();
return null;
}
}
catch
{
Unload();
throw;
}
}
#endregion
#region Load multiple assemblies
public Test LoadAssemblies( string testFileName, IList assemblies )
{
return LoadAssemblies( testFileName, assemblies, null );
}
public Test LoadAssemblies( string testFileName, IList assemblies, string testFixture )
{
FileInfo testFile = new FileInfo( testFileName );
return LoadAssemblies( testFileName, testFile.DirectoryName, testFile.FullName + ".config", GetBinPath(assemblies), assemblies, testFixture );
}
public Test LoadAssemblies( string testFileName, string appBase, string configFile, string binPath, IList assemblies, string testFixture )
{
ThrowIfAlreadyLoaded();
try
{
string domainName = string.Format( "domain-{0}", Path.GetFileName( testFileName ) );
domain = MakeAppDomain( testFileName, appBase, configFile, binPath );
testRunner = MakeRemoteTestRunner( domain );
if(testRunner != null)
{
testRunner.TestFileName = testFileName;
testRunner.Assemblies = assemblies;
if ( testFixture != null )
testRunner.TestName = testFixture;
domain.DoCallBack( new CrossAppDomainDelegate( testRunner.BuildSuite ) );
return testRunner.Test;
}
else
{
Unload();
return null;
}
}
catch
{
Unload();
throw;
}
}
#endregion
public TestResult Run(NUnit.Core.EventListener listener, TextWriter outStream, TextWriter errorStream )
{
return testRunner.Run(listener, outStream, errorStream);
}
public void Unload()
{
testRunner = null;
if(domain != null)
{
try
{
AppDomain.Unload(domain);
DirectoryInfo cacheDir = new DirectoryInfo(cachePath);
if(cacheDir.Exists) cacheDir.Delete(true);
}
catch( CannotUnloadAppDomainException )
{
// TODO: Do something useful. For now we just
// leave the orphaned AppDomain "out there"
// rather than aborting the application.
}
finally
{
domain = null;
}
}
}
#endregion
#region Helper Methods
private void ThrowIfAlreadyLoaded()
{
if ( domain != null || testRunner != null )
throw new InvalidOperationException( "TestDomain already loaded" );
}
///
/// This method creates appDomains for the framework.
///
/// Name of the domain
/// ApplicationBase for the domain
/// ConfigurationFile for the domain
/// PrivateBinPath for the domain
///
private AppDomain MakeAppDomain( string domainName, string appBase, string configFile, string binPath )
{
Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
Evidence evidence = new Evidence(baseEvidence);
AppDomainSetup setup = new AppDomainSetup();
// We always use the same application name
setup.ApplicationName = "Tests";
// We always want to do shadow copying. Note that we do NOT
// set ShadowCopyDirectories because we rely on the default
// setting of ApplicationBase plus PrivateBinPath
setup.ShadowCopyFiles = "true";
setup.ShadowCopyDirectories = appBase;
setup.ApplicationBase = appBase;
setup.ConfigurationFile = configFile;
setup.PrivateBinPath = binPath;
AppDomain runnerDomain = AppDomain.CreateDomain(domainName, evidence, setup);
ConfigureCachePath(runnerDomain);
return runnerDomain;
}
///
/// Set the location for caching and delete any old cache info
///
/// Our domain
private void ConfigureCachePath(AppDomain domain)
{
cachePath = String.Format(@"{0}\{1}",
ConfigurationSettings.AppSettings["shadowfiles.path"], DateTime.Now.Ticks);
cachePath = Environment.ExpandEnvironmentVariables(cachePath);
DirectoryInfo dir = new DirectoryInfo(cachePath);
if(dir.Exists) dir.Delete(true);
domain.SetCachePath(cachePath);
return;
}
private static RemoteTestRunner MakeRemoteTestRunner( AppDomain runnerDomain )
{
object obj = runnerDomain.CreateInstanceAndUnwrap(
typeof(RemoteTestRunner).Assembly.FullName,
typeof(RemoteTestRunner).FullName,
false, BindingFlags.Default,null,null,null,null,null);
return (RemoteTestRunner) obj;
}
public static string GetBinPath( IList assemblies )
{
ArrayList dirs = new ArrayList();
string binPath = null;
foreach( string path in assemblies )
{
string dir = Path.GetDirectoryName( Path.GetFullPath( path ) );
if ( !dirs.Contains( dir ) )
{
dirs.Add( dir );
if ( binPath == null )
binPath = dir;
else
binPath = binPath + ";" + dir;
}
}
return binPath;
}
#endregion
}
}