Skip to content

Commit

Permalink
Add support for executing tests in nested classes when parent class i…
Browse files Browse the repository at this point in the history
…s targeted #4

Add support for executing tests in concrete sub-classes when abstract class is targeted #5
Add support for targeting generic test fixtures #6
  • Loading branch information
jcansdale committed Jul 18, 2016
1 parent 0ba45bd commit 9f6e523
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 37 deletions.
22 changes: 12 additions & 10 deletions src/NUnitTDNet.Adapter/ConsoleTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,22 @@ public TestRunState RunAssembly(ITestListener testListener, Assembly assembly)

public TestRunState RunMember(ITestListener testListener, Assembly assembly, MemberInfo member)
{
var testPaths = Utilities.GetTestPaths(member);
return executeConsoleRunner(testListener, assembly, testPaths);
var where = Utilities.GetWhereForTarget(assembly, member);
if(string.IsNullOrEmpty(where))
{
return TestRunState.NoTests;
}

return executeConsoleRunner(testListener, assembly, where);
}

public TestRunState RunNamespace(ITestListener testListener, Assembly assembly, string ns)
{
var testPaths = new string[] { ns };
return executeConsoleRunner(testListener, assembly, testPaths);
var where = Utilities.GetWhereForTarget(assembly, ns);
return executeConsoleRunner(testListener, assembly, where);
}

TestRunState executeConsoleRunner(ITestListener testListener, Assembly testAssembly, string[] testPaths)
TestRunState executeConsoleRunner(ITestListener testListener, Assembly testAssembly, string where)
{
var exeFile = findConsoleRunner();
if(exeFile == null)
Expand All @@ -40,12 +45,9 @@ TestRunState executeConsoleRunner(ITestListener testListener, Assembly testAssem

string assemblyFile = new Uri(testAssembly.EscapedCodeBase).LocalPath;
string arguments = quote(assemblyFile);
if(testPaths != null)
if(!string.IsNullOrEmpty(where))
{
foreach(var testPath in testPaths)
{
arguments += " --test=" + quote(testPath);
}
arguments += " --where=" + quote(where);
}

arguments += " --process:InProcess";
Expand Down
30 changes: 13 additions & 17 deletions src/NUnitTDNet.Adapter/EngineTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,27 @@ public class EngineTestRunner : TDF.ITestRunner
{
public TDF.TestRunState RunAssembly(TDF.ITestListener testListener, Assembly assembly)
{
var testPath = new Uri(assembly.EscapedCodeBase).LocalPath;
var testPaths = new string[] { testPath };
return run(testListener, assembly, testPaths);
return run(testListener, assembly, null);
}

public TDF.TestRunState RunMember(TDF.ITestListener testListener, Assembly assembly, MemberInfo member)
{
var testPaths = Utilities.GetTestPaths(member);
return run(testListener, assembly, testPaths);
var where = Utilities.GetWhereForTarget(assembly, member);
if(string.IsNullOrEmpty(where))
{
return TDF.TestRunState.NoTests;
}

return run(testListener, assembly, where);
}

public TDF.TestRunState RunNamespace(TDF.ITestListener testListener, Assembly assembly, string ns)
{
var testPath = ns;
if(string.IsNullOrEmpty(ns))
{
testPath = new Uri(assembly.EscapedCodeBase).LocalPath;
}

var testPaths = new string[] { testPath };
return run(testListener, assembly, testPaths);
var where = Utilities.GetWhereForTarget(assembly, ns);
return run(testListener, assembly, where);
}

TDF.TestRunState run(TDF.ITestListener testListener, Assembly testAssembly, string[] testPaths)
TDF.TestRunState run(TDF.ITestListener testListener, Assembly testAssembly, string where)
{
using (var engine = new TestEngineClass())
{
Expand All @@ -57,13 +54,12 @@ TDF.TestRunState run(TDF.ITestListener testListener, Assembly testAssembly, stri

var filterService = engine.Services.GetService<ITestFilterService>();
ITestFilterBuilder builder = filterService.GetTestFilterBuilder();
foreach(var testPath in testPaths)
if (!string.IsNullOrEmpty(where))
{
builder.AddTest(testPath);
builder.SelectWhere(where);
}

var filter = builder.GetFilter();

var totalTests = runner.CountTestCases(filter);
if (totalTests == 0)
{
Expand Down
134 changes: 124 additions & 10 deletions src/NUnitTDNet.Adapter/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,160 @@

class Utilities
{
public static string[] GetTestPaths(MemberInfo member)
public static string GetWhereForTarget(Assembly assembly, string ns)
{
if (string.IsNullOrEmpty(ns))
{
return null;
}

return toWhereClause(ns);
}

public static string GetWhereForTarget(Assembly assembly, MemberInfo member)
{
if (member is Type)
{
var whereClauseList = new List<string>();
var targetType = (Type)member;
var types = includeNestedTypes(targetType);
var testPathList = new List<string>();
types = includeConcreteTypes(assembly, types);
foreach (var type in types)
{
testPathList.Add(type.FullName);
var whereClause = toWhereClause(type);
whereClauseList.Add(whereClause);
}

return testPathList.ToArray();
return orWhereClauses(whereClauseList);
}

if (member is MethodInfo)
{
var whereClauseList = new List<string>();
MethodInfo methodInfo = (MethodInfo)member;
var testPath = methodInfo.DeclaringType.FullName + "." + methodInfo.Name;
var testPaths = new string[] { testPath };
return testPaths;
var targetTypes = new Type[] { methodInfo.DeclaringType };
var types = includeConcreteTypes(assembly, targetTypes);
foreach(var type in types)
{
var whereClause = toWhereClause(type, methodInfo);
whereClauseList.Add(whereClause);
}

return orWhereClauses(whereClauseList);
}

throw new Exception("Member type not supported: " + member.GetType());
}

static string orWhereClauses(ICollection<string> whereClauses)
{
var where = string.Empty;
foreach (var whereClause in whereClauses)
{
if (where != string.Empty)
{
where += " || ";
}

where += whereClause;
}

return where;
}

static string toWhereClause(Type type)
{
return string.Format("(class == '{0}')", type.FullName);
}

static string toWhereClause(Type type, MethodInfo method)
{
if (type.IsGenericTypeDefinition)
{
// this doesn't work with explicit tests
return string.Format("class == '{0}' && method == '{1}'", type.FullName, method.Name);
}

var testPath = toTestPath(type, method);
return toWhereClause(testPath);
}

static string toTestPath(Type type, MethodInfo methodInfo)
{
return toTestPath(type) + "." + methodInfo.Name;
}

static string toTestPath(Type type)
{
var testPath = type.FullName;
if (!type.IsGenericTypeDefinition)
{
return testPath;
}

testPath = type.FullName.Split('`')[0];
testPath += "<";
foreach(var arg in type.GetGenericArguments())
{
if(!testPath.EndsWith("<"))
{
testPath += ",";
}

testPath += arg.Name;
}
testPath += ">";
return testPath;
}

static Type[] includeNestedTypes(Type type)
{
var types = new List<Type>();
includeNestedTypes(types, type);
return types.ToArray();
}

static void includeNestedTypes(List<Type> types, Type type)
static void includeNestedTypes(List<Type> typeList, Type type)
{
types.Add(type);
typeList.Add(type);
foreach (var nestedType in type.GetNestedTypes())
{
includeNestedTypes(types, nestedType);
includeNestedTypes(typeList, nestedType);
}
}

static Type[] includeConcreteTypes(Assembly assembly, Type[] targetTypes)
{
var typeList = new List<Type>();
foreach(var targetType in targetTypes)
{
includeConcreteTypes(typeList, assembly, targetType);
}

return typeList.ToArray();
}

static void includeConcreteTypes(List<Type> typeList, Assembly assembly, Type targetType)
{
if (targetType.IsAbstract && !targetType.IsSealed) // static classes are abstract and sealed
{
foreach (Type candidateType in assembly.GetExportedTypes())
{
if (targetType.IsAssignableFrom(candidateType) && !candidateType.IsAbstract)
{
typeList.Add(candidateType);
}
}

return;
}

typeList.Add(targetType);
}

static string toWhereClause(string testPath)
{
return string.Format("(test == '{0}')", testPath);
}
}
}

0 comments on commit 9f6e523

Please sign in to comment.