diff --git a/src/NUnitTDNet.Adapter/ConsoleTestRunner.cs b/src/NUnitTDNet.Adapter/ConsoleTestRunner.cs index 3be856c..427f8a6 100644 --- a/src/NUnitTDNet.Adapter/ConsoleTestRunner.cs +++ b/src/NUnitTDNet.Adapter/ConsoleTestRunner.cs @@ -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) @@ -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"; diff --git a/src/NUnitTDNet.Adapter/EngineTestRunner.cs b/src/NUnitTDNet.Adapter/EngineTestRunner.cs index c1fe4b5..58dd2f6 100644 --- a/src/NUnitTDNet.Adapter/EngineTestRunner.cs +++ b/src/NUnitTDNet.Adapter/EngineTestRunner.cs @@ -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()) { @@ -57,13 +54,12 @@ TDF.TestRunState run(TDF.ITestListener testListener, Assembly testAssembly, stri var filterService = engine.Services.GetService(); 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) { diff --git a/src/NUnitTDNet.Adapter/Utilities.cs b/src/NUnitTDNet.Adapter/Utilities.cs index 15f3195..a012c06 100644 --- a/src/NUnitTDNet.Adapter/Utilities.cs +++ b/src/NUnitTDNet.Adapter/Utilities.cs @@ -6,32 +6,112 @@ 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(); var targetType = (Type)member; var types = includeNestedTypes(targetType); - var testPathList = new List(); + 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(); 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 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(); @@ -39,13 +119,47 @@ static Type[] includeNestedTypes(Type type) return types.ToArray(); } - static void includeNestedTypes(List types, Type type) + static void includeNestedTypes(List 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(); + foreach(var targetType in targetTypes) + { + includeConcreteTypes(typeList, assembly, targetType); + } + + return typeList.ToArray(); + } + + static void includeConcreteTypes(List 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); + } } }