#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
namespace NUnit.Core
{
using System;
using System.IO;
using System.Reflection;
using System.Collections;
///
/// Summary description for TestSuiteBuilder.
///
public class TestSuiteBuilder
{
#region Private Fields
///
/// Hashtable of all test suites we have created to represent namespaces.
/// Used to locate namespace parent suites for fixtures.
///
Hashtable namespaceSuites = new Hashtable();
///
/// The root of the test suite being created by this builder. This
/// may be a simple TestSuite, an AssemblyTestSuite or a RootTestSuite
/// encompassing multiple assemblies.
///
TestSuite rootSuite;
///
/// The version of the nunit framework referenced by the loaded assembly.
///
Version frameworkVersion = null;
#endregion
#region Properties
public Version FrameworkVersion
{
get { return frameworkVersion; }
}
#endregion
#region Public Methods
public Assembly Load(string assemblyName)
{
// Change currentDirectory in case assembly references unmanaged dlls
string currentDirectory = Environment.CurrentDirectory;
string assemblyDirectory = Path.GetDirectoryName( assemblyName );
bool swap = assemblyDirectory != null && assemblyDirectory != string.Empty;
try
{
if ( swap )
Environment.CurrentDirectory = assemblyDirectory;
Assembly assembly = AppDomain.CurrentDomain.Load(Path.GetFileNameWithoutExtension(assemblyName));
foreach( AssemblyName refAssembly in assembly.GetReferencedAssemblies() )
{
if ( refAssembly.Name == "nunit.framework" )
this.frameworkVersion = refAssembly.Version;
}
return assembly;
}
finally
{
if ( swap )
Environment.CurrentDirectory = currentDirectory;
}
}
public TestSuite Build(string projectName, IList assemblies)
{
RootTestSuite rootSuite = new RootTestSuite( projectName );
int assemblyKey = 0;
foreach(string assembly in assemblies)
{
TestSuite suite = Build( assembly, assemblyKey++ );
rootSuite.Add( suite );
}
return rootSuite;
}
public TestSuite Build( string assemblyName )
{
return Build( assemblyName, 0 );
}
public TestSuite Build(string assemblyName, string testName )
{
TestSuite suite = null;
Assembly assembly = Load(assemblyName);
if(assembly != null)
{
Type testType = assembly.GetType(testName);
if(testType != null)
return MakeSuite( testType );
// Assume that testName is a namespace
string prefix = testName + '.';
Type[] testTypes = assembly.GetExportedTypes();
int testFixtureCount = 0;
foreach(Type type in testTypes)
{
if(IsTestFixture(type) && type.Namespace != null)
//if(IsTestFixture(type) || IsTestSuiteProperty(type))
{
if( type.Namespace == testName || type.Namespace.StartsWith(prefix) )
{
suite = BuildFromNameSpace(testName, 0);
try
{
object fixture = BuildTestFixture( type );
suite.Add(fixture);
testFixtureCount++;
}
catch(InvalidTestFixtureException exception)
{
InvalidFixture fixture = new InvalidFixture(testType, exception.Message);
suite.Add(fixture);
}
}
}
}
return testFixtureCount == 0 ? null : rootSuite;
}
return suite;
}
public TestSuite Build( IList assemblies, string testName )
{
TestSuite suite = null;
foreach(string assemblyName in assemblies)
{
suite = Build( assemblyName, testName );
if ( suite != null ) break;
}
return suite;
}
public object BuildTestFixture( Type fixtureType )
{
ConstructorInfo ctor = fixtureType.GetConstructor(Type.EmptyTypes);
if(ctor == null) throw new InvalidTestFixtureException(fixtureType.FullName + " does not have a valid constructor");
object testFixture;
try
{
testFixture = ctor.Invoke(Type.EmptyTypes);
}
catch( Exception ex )
{
throw new InvalidTestFixtureException( ctor.Name + " threw a exception", ex );
}
if(testFixture == null) throw new InvalidTestFixtureException(ctor.Name + " cannot be invoked");
if(HasMultipleSetUpMethods(testFixture))
{
throw new InvalidTestFixtureException(ctor.Name + " has multiple SetUp methods");
}
if(HasMultipleTearDownMethods(testFixture))
{
throw new InvalidTestFixtureException(ctor.Name + " has multiple TearDown methods");
}
if(HasMultipleFixtureSetUpMethods(testFixture))
{
throw new InvalidTestFixtureException(ctor.Name + " has multiple TestFixtureSetUp methods");
}
if(HasMultipleFixtureTearDownMethods(testFixture))
{
throw new InvalidTestFixtureException(ctor.Name + " has multiple TestFixtureTearDown methods");
}
CheckSetUpTearDownSignature(GetMethodWithGivenAttribute(fixtureType,typeof(NUnit.Framework.SetUpAttribute)));
CheckSetUpTearDownSignature(GetMethodWithGivenAttribute(fixtureType,typeof(NUnit.Framework.TearDownAttribute)));
CheckSetUpTearDownSignature(GetMethodWithGivenAttribute(fixtureType,typeof(NUnit.Framework.TestFixtureSetUpAttribute)));
CheckSetUpTearDownSignature(GetMethodWithGivenAttribute(fixtureType,typeof(NUnit.Framework.TestFixtureTearDownAttribute)));
return testFixture;
}
public TestSuite MakeSuite( Type testType )
{
TestSuite suite = null;
if(testType != null)
{
if(IsTestFixture(testType))
{
suite = MakeSuiteFromTestFixtureType(testType);
}
else if(IsTestSuiteProperty(testType))
{
suite = MakeSuiteFromProperty(testType);
}
}
return suite;
}
public TestSuite MakeSuiteFromTestFixtureType(Type fixtureType)
{
TestSuite suite = new TestSuite(fixtureType.Name);
try
{
object testFixture = BuildTestFixture(fixtureType);
suite.Add(testFixture);
}
catch(InvalidTestFixtureException exception)
{
InvalidFixture fixture = new InvalidFixture(fixtureType,exception.Message);
suite.ShouldRun = false;
suite.IgnoreReason = exception.Message;
suite.Add(fixture);
}
return suite.Tests[0] as TestSuite;
}
#endregion
#region Nested TypeFilter Class
private class TypeFilter
{
private string rootNamespace;
TypeFilter( string rootNamespace )
{
this.rootNamespace = rootNamespace;
}
public bool Include( Type type )
{
if ( type.Namespace == rootNamespace )
return true;
return type.Namespace.StartsWith( rootNamespace + '.' );
}
}
#endregion
#region Helper Methods
private TestSuite BuildFromNameSpace( string nameSpace, int assemblyKey )
{
if( nameSpace == null || nameSpace == "" ) return rootSuite;
TestSuite suite = (TestSuite)namespaceSuites[nameSpace];
if(suite!=null) return suite;
int index = nameSpace.LastIndexOf(".");
string prefix = string.Format( "[{0}]", assemblyKey );
if( index == -1 )
{
suite = new TestSuite( nameSpace, assemblyKey );
if ( rootSuite == null )
rootSuite = suite;
else
rootSuite.Add(suite);
namespaceSuites[nameSpace]=suite;
}
else
{
string parentNameSpace = nameSpace.Substring( 0,index );
TestSuite parent = BuildFromNameSpace( parentNameSpace, assemblyKey );
string suiteName = nameSpace.Substring( index+1 );
suite = new TestSuite( parentNameSpace, suiteName, assemblyKey );
parent.Add( suite );
namespaceSuites[nameSpace] = suite;
}
return suite;
}
private TestSuite Build( string assemblyName, int assemblyKey )
{
TestSuiteBuilder builder = new TestSuiteBuilder();
Assembly assembly = Load( assemblyName );
builder.rootSuite = new AssemblyTestSuite( assemblyName, assemblyKey );
int testFixtureCount = 0;
Type[] testTypes = assembly.GetExportedTypes();
foreach(Type testType in testTypes)
{
////////////////////////////////////////////////////////////////////////
// Use the second if statement to allow including Suites in the
// tree of tests. This causes a problem when the same test is added
// in multiple suites so we need to either fix it or prevent it.
//
// See also the block of code to uncomment in TestSuite.cs
////////////////////////////////////////////////////////////////////////
if(IsTestFixture(testType))
//if(IsTestFixture(testType) || IsTestSuiteProperty(testType))
{
testFixtureCount++;
string namespaces = testType.Namespace;
TestSuite suite = builder.BuildFromNameSpace( namespaces, assemblyKey );
try
{
object fixture = BuildTestFixture( testType );
suite.Add(fixture);
}
catch(InvalidTestFixtureException exception)
{
InvalidFixture fixture = new InvalidFixture(testType, exception.Message);
suite.Add(fixture);
}
}
}
if(testFixtureCount == 0)
{
//throw new NoTestFixturesException(assemblyName + " has no TestFixtures");
builder.rootSuite.ShouldRun = false;
builder.rootSuite.IgnoreReason = "Has no TestFixtures";
}
return builder.rootSuite;
}
private int CountMethodWithGivenAttribute(object fixture, Type type)
{
int count = 0;
foreach(MethodInfo method in fixture.GetType().GetMethods(BindingFlags.Public|BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.DeclaredOnly))
{
if(method.IsDefined(type,false))
count++;
}
return count;
}
private MethodInfo GetMethodWithGivenAttribute(Type fixtureType, Type attrType)
{
foreach ( MethodInfo method in fixtureType.GetMethods(BindingFlags.Public|BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Static ))
{
if( method.IsDefined( attrType, true ) )
return method;
}
return null;
}
private bool HasMultipleSetUpMethods(object fixture)
{
return CountMethodWithGivenAttribute(fixture,typeof(NUnit.Framework.SetUpAttribute)) > 1;
}
private bool HasMultipleTearDownMethods(object fixture)
{
return CountMethodWithGivenAttribute(fixture,typeof(NUnit.Framework.TearDownAttribute)) > 1;
}
private bool HasMultipleFixtureSetUpMethods(object fixture)
{
return CountMethodWithGivenAttribute(fixture,typeof(NUnit.Framework.TestFixtureSetUpAttribute)) > 1;
}
private bool HasMultipleFixtureTearDownMethods(object fixture)
{
return CountMethodWithGivenAttribute(fixture,typeof(NUnit.Framework.TestFixtureTearDownAttribute)) > 1;
}
private bool IsTestFixture(Type type)
{
if(type.IsAbstract) return false;
return type.IsDefined(typeof(NUnit.Framework.TestFixtureAttribute), true);
}
private bool IsTestSuiteProperty(Type testClass)
{
return (GetSuiteProperty(testClass) != null);
}
///
/// Uses reflection to obtain the suite property for the Type
///
///
/// The Suite property of the Type, or null if the property
/// does not exist
private TestSuite MakeSuiteFromProperty(Type testClass)
{
TestSuite suite = null;
PropertyInfo suiteProperty = null;
try
{
suiteProperty=GetSuiteProperty(testClass);
suite = (TestSuite)suiteProperty.GetValue(null, new Object[0]);
}
catch(InvalidSuiteException)
{
return null;
}
return suite;
}
private PropertyInfo GetSuiteProperty(Type testClass)
{
if(testClass != null)
{
PropertyInfo[] properties = testClass.GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly);
foreach(PropertyInfo property in properties)
{
object[] attrributes = property.GetCustomAttributes(typeof(NUnit.Framework.SuiteAttribute),false);
if(attrributes.Length>0)
{
try {
CheckSuiteProperty(property);
}catch(InvalidSuiteException){
return null;
}
return property;
}
}
}
return null;
}
private void CheckSuiteProperty(PropertyInfo property)
{
MethodInfo method = property.GetGetMethod(true);
if(method.ReturnType!=typeof(NUnit.Core.TestSuite))
throw new InvalidSuiteException("Invalid suite property method signature");
if(method.GetParameters().Length>0)
throw new InvalidSuiteException("Invalid suite property method signature");
}
private void CheckSetUpTearDownSignature(MethodInfo method)
{
if ( method != null )
{
if ( !method.IsPublic && !method.IsFamily || method.ReturnType != typeof(void) || method.GetParameters().Length > 0 )
throw new InvalidTestFixtureException("Invalid SetUp or TearDown method signature");
}
}
}
#endregion
}