// NAnt - A .NET build tool
// Copyright (C) 2001-2003 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Tomas Restrepo (tomasr@mvps.org)
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;
using NUnit.Core;
namespace NAnt.NUnit2.Tasks {
///
/// Custom TestDomain, similar to the one included with NUnit, in order
/// to workaround some limitations in it.
///
internal class NUnit2TestDomain {
#region Public Instance Constructors
///
/// Initializes a new instance of the
/// class.
///
public NUnit2TestDomain() {
}
#endregion Public Instance Constructors
#region Public Instance Methods
///
/// Runs a single testcase.
///
/// The test assembly.
/// The application configuration file for the test domain.
///
/// The result of the test.
///
public TestRunner CreateRunner(FileInfo assemblyFile, FileInfo configFile) {
// create test domain
_domain = CreateDomain(assemblyFile.Directory, assemblyFile,
configFile);
// assemble directories which can be probed for missing unresolved
// assembly references
string[] probePaths = null;
if (AppDomain.CurrentDomain.SetupInformation.PrivateBinPath != null) {
string [] privateBinPaths = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath.Split(Path.PathSeparator);
probePaths = new string [privateBinPaths.Length + 1];
for (int i = 0; i < privateBinPaths.Length; i++) {
probePaths[i] = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
privateBinPaths[i]);
}
} else {
probePaths = new string[1];
}
// add base directory of current AppDomain as probe path
probePaths [probePaths.Length - 1] = AppDomain.CurrentDomain.BaseDirectory;
// create an instance of our custom Assembly Resolver in the target domain.
_domain.CreateInstanceFrom(Assembly.GetExecutingAssembly().CodeBase,
typeof(AssemblyResolveHandler).FullName,
false,
BindingFlags.Public | BindingFlags.Instance,
null,
new object[] {probePaths},
CultureInfo.InvariantCulture,
null,
AppDomain.CurrentDomain.Evidence);
// create testrunner
return CreateTestRunner(_domain);
}
public void Unload() {
if (_domain != null) {
try {
AppDomain.Unload(_domain);
} catch (CannotUnloadAppDomainException) {
// ignore exceptions during unload, this matches the
// behaviour of the NUnit TestDomain
} finally {
_domain = null;
}
}
}
#endregion Public Instance Methods
#region Private Instance Methods
private AppDomain CreateDomain(DirectoryInfo basedir, FileInfo assemblyFile, FileInfo configFile) {
// spawn new domain in specified directory
AppDomainSetup domSetup = new AppDomainSetup();
domSetup.ApplicationBase = basedir.FullName;
// use explicitly specified configuration file, or fall back to
// configuration file for given assembly file
string configurationFile = null;
if (configFile != null) {
configurationFile = configFile.FullName;
} else {
configurationFile = assemblyFile.FullName + ".config";
}
// only set configuration file if it actually exists
if (File.Exists(configurationFile)) {
domSetup.ConfigurationFile = configurationFile;
}
domSetup.ApplicationName = "NAnt NUnit Remote Domain";
return AppDomain.CreateDomain(
domSetup.ApplicationName,
AppDomain.CurrentDomain.Evidence,
domSetup
);
}
private RemoteTestRunner CreateTestRunner(AppDomain domain) {
ObjectHandle oh;
Type rtrType = typeof(RemoteTestRunner);
oh = domain.CreateInstance(
rtrType.Assembly.FullName,
rtrType.FullName,
false,
BindingFlags.Public | BindingFlags.Instance,
null,
null,
CultureInfo.InvariantCulture,
null,
AppDomain.CurrentDomain.Evidence);
return (RemoteTestRunner) oh.Unwrap();
}
#endregion Private Instance Methods
#region Private Instance Fields
private AppDomain _domain;
#endregion Private Instance Fields
///
/// Helper class called when an assembly resolve event is raised.
///
[Serializable()]
private class AssemblyResolveHandler {
#region Public Instance Constructors
///
/// Initializes an instanse of the
/// class.
///
public AssemblyResolveHandler(string[] probePaths) {
_assemblyCache = new Hashtable();
_probePaths = probePaths;
// attach handlers for the current domain.
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(ResolveAssembly);
AppDomain.CurrentDomain.AssemblyLoad +=
new AssemblyLoadEventHandler(AssemblyLoad);
}
#endregion Public Instance Constructors
#region Public Instance Methods
///
/// Called back when the CLR cannot resolve a given assembly.
///
/// The source of the event.
/// A that contains the event data.
///
/// The nunit.framework we know to be in NAnts bin directory, if
/// that is the assembly that needs to be resolved; otherwise,
/// .
///
public Assembly ResolveAssembly(Object sender, ResolveEventArgs args) {
bool isFullName = args.Name.IndexOf("Version=") != -1;
// find assembly in cache
if (isFullName) {
if (_assemblyCache.Contains(args.Name)) {
// return assembly from cache
return (Assembly) _assemblyCache[args.Name];
}
} else {
foreach (Assembly assembly in _assemblyCache.Values) {
if (assembly.GetName(false).Name == args.Name) {
// return assembly from cache
return assembly;
}
}
}
// find assembly in probe paths
foreach (string path in _probePaths) {
if (!Directory.Exists(path)) {
continue;
}
string[] assemblies = Directory.GetFiles(path, "*.dll");
foreach (string assemblyFile in assemblies) {
try {
AssemblyName assemblyName = AssemblyName.GetAssemblyName(assemblyFile);
if (isFullName) {
if (assemblyName.FullName == args.Name) {
return Assembly.LoadFrom(assemblyFile);
}
} else {
if (assemblyName.Name == args.Name) {
return Assembly.LoadFrom(assemblyFile);
}
}
} catch {}
}
}
// assembly reference could not be resolved
return null;
}
///
/// Occurs when an assembly is loaded. The loaded assembly is added
/// to the assembly cache.
///
/// The source of the event.
/// An that contains the event data.
private void AssemblyLoad(object sender, AssemblyLoadEventArgs args) {
// store assembly in cache
_assemblyCache[args.LoadedAssembly.FullName] = args.LoadedAssembly;
}
#endregion Public Instance Methods
#region Private Instance Fields
///
/// Holds the list of directories that will be scanned for missing
/// assembly references.
///
private readonly string[] _probePaths;
///
/// Holds the loaded assemblies.
///
private readonly Hashtable _assemblyCache;
#endregion Private Instance Fields
}
}
}