diff --git a/src/xunit.console.netcore/CommandLine.cs b/src/xunit.console.netcore/CommandLine.cs index 4c7df4fc4f..4b9b5a641a 100644 --- a/src/xunit.console.netcore/CommandLine.cs +++ b/src/xunit.console.netcore/CommandLine.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Xunit.ConsoleClient.Project; namespace Xunit.ConsoleClient { @@ -14,18 +15,56 @@ protected CommandLine(string[] args, Predicate fileExists = null) fileExists = fileName => File.Exists(fileName); for (var i = args.Length - 1; i >= 0; i--) + { + if (args[i][0] == '@') + { + // Parse response file + IList rspArguments = ParseResponseFile(args[i].Substring(1)); + for (int j = rspArguments.Count - 1; j >= 0; j--) + arguments.Push(rspArguments[j]); + continue; + } arguments.Push(args[i]); + } TeamCity = Environment.GetEnvironmentVariable("TEAMCITY_PROJECT_NAME") != null; AppVeyor = Environment.GetEnvironmentVariable("APPVEYOR_API_URL") != null; Project = Parse(fileExists); } + /// + /// Parse a response file passed as a command-line arguments. + /// No verification here to make this completely opaque + /// + /// Path to the response file + /// The data structure in + private IList ParseResponseFile(string responseFile) + { + + var argumentsList = new List(); + + if (!File.Exists(responseFile)) + throw new ArgumentException(String.Format("Response file {0} not found", responseFile)); + + // Add contents from the text file to the command line + foreach (string line in File.ReadAllLines(responseFile)) + { + string cleanLine = line.Trim(); + if (string.IsNullOrEmpty(cleanLine)) + continue; + var rspArguments = cleanLine.Split(); + foreach(string arg in rspArguments) + argumentsList.Add(arg); + } + + return argumentsList; + } + public bool AppVeyor { get; protected set; } public int? MaxParallelThreads { get; set; } - public XunitProject Project { get; protected set; } + public ExtendedXunitProject Project { get; protected set; } public bool? ParallelizeAssemblies { get; protected set; } @@ -40,9 +79,9 @@ protected CommandLine(string[] args, Predicate fileExists = null) public bool Wait { get; protected set; } - static XunitProject GetProjectFile(List> assemblies) + static ExtendedXunitProject GetProjectFile(List> assemblies) { - var result = new XunitProject(); + var result = new ExtendedXunitProject(); foreach (var assembly in assemblies) result.Add(new XunitProjectAssembly @@ -65,7 +104,7 @@ public static CommandLine Parse(params string[] args) return new CommandLine(args); } - protected XunitProject Parse(Predicate fileExists) + protected ExtendedXunitProject Parse(Predicate fileExists) { var assemblies = new List>(); @@ -223,6 +262,25 @@ protected XunitProject Parse(Predicate fileExists) project.Filters.IncludedMethods.Add(option.Value); } + else if (optionName == "-skipmethod") + { + if (option.Value == null) + throw new ArgumentException("missing argument for -skipmethod"); + project.Filters.ExcludedMethods.Add(option.Value); + + } + else if (optionName == "-skipclass") + { + if (option.Value == null) + throw new ArgumentException("missing argument for -skipclass"); + project.Filters.ExcludedClasses.Add(option.Value); + } + else if (optionName == "-skipnamespace") + { + if (option.Value == null) + throw new ArgumentException("missing argument for -skipnamespace"); + project.Filters.ExcludedNamespaces.Add(option.Value); + } else { if (option.Value == null) diff --git a/src/xunit.console.netcore/Filters/ExtendedXunitFilters.cs b/src/xunit.console.netcore/Filters/ExtendedXunitFilters.cs new file mode 100644 index 0000000000..b51b5fe741 --- /dev/null +++ b/src/xunit.console.netcore/Filters/ExtendedXunitFilters.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace Xunit.ConsoleClient.Filters +{ + /// + /// Wrapper class, which hides XunitFilters' Filter method and adds logic to exclude methods or namespaces + /// + public class ExtendedXunitFilters : XunitFilters + { + public HashSet ExcludedMethods { get; private set; } + public HashSet ExcludedClasses { get; private set; } + public HashSet ExcludedNamespaces { get; private set; } + + public ExtendedXunitFilters() : base() + { + ExcludedMethods = new HashSet(); + ExcludedClasses = new HashSet(); + ExcludedNamespaces = new HashSet(); + } + + /// + /// Determine whether a passed test case should run + /// + /// Test case to filter + /// Boolean - True runs the test case, False skips + public new bool Filter(ITestCase testCase) + { + // Exclusions supersede inclusions - i.e. if a method/class/namespace is both included and excluded it won't run + return FilterExcludedMethodsAndClasses(testCase) && base.Filter(testCase); + } + + bool FilterExcludedMethodsAndClasses(ITestCase testCase) + { + // If no explicit exclusions have been defined, return true + if (ExcludedMethods.Count == 0 && ExcludedClasses.Count == 0 && ExcludedNamespaces.Count == 0) + return true; + + if (ExcludedClasses.Count != 0 && ExcludedClasses.Contains(testCase.TestMethod.TestClass.Class.Name)) + return false; + + var methodName = $"{testCase.TestMethod.TestClass.Class.Name}.{testCase.TestMethod.Method.Name}"; + + if (ExcludedMethods.Count != 0 && ExcludedMethods.Contains(methodName)) + return false; + + if (ExcludedNamespaces.Count != 0 && ExcludedNamespaces.Any(a => testCase.TestMethod.TestClass.Class.Name.StartsWith($"{a}.", StringComparison.Ordinal))) + return false; + + return true; + } + + } +} diff --git a/src/xunit.console.netcore/Program.cs b/src/xunit.console.netcore/Program.cs index a016da4fc9..fd6969ae75 100644 --- a/src/xunit.console.netcore/Program.cs +++ b/src/xunit.console.netcore/Program.cs @@ -6,6 +6,8 @@ using System.Reflection; using System.Threading.Tasks; using System.Xml.Linq; +using Xunit.ConsoleClient.Filters; +using Xunit.ConsoleClient.Project; namespace Xunit.ConsoleClient { @@ -175,7 +177,7 @@ static void PrintUsage() ); } - static int RunProject(string defaultDirectory, XunitProject project, bool teamcity, bool appVeyor, bool showProgress, bool? parallelizeAssemblies, bool? parallelizeTestCollections, int? maxThreadCount) + static int RunProject(string defaultDirectory, ExtendedXunitProject project, bool teamcity, bool appVeyor, bool showProgress, bool? parallelizeAssemblies, bool? parallelizeTestCollections, int? maxThreadCount) { XElement assembliesElement = null; var xmlTransformers = TransformFactory.GetXmlTransformers(project); @@ -280,7 +282,7 @@ static XmlTestExecutionVisitor CreateVisitor(object consoleLock, string defaultD return new StandardOutputVisitor(consoleLock, defaultDirectory, assemblyElement, () => cancel, completionMessages, showProgress); } - static XElement ExecuteAssembly(object consoleLock, string defaultDirectory, XunitProjectAssembly assembly, bool needsXml, bool teamCity, bool appVeyor, bool showProgress, bool? parallelizeTestCollections, int? maxThreadCount, XunitFilters filters) + static XElement ExecuteAssembly(object consoleLock, string defaultDirectory, XunitProjectAssembly assembly, bool needsXml, bool teamCity, bool appVeyor, bool showProgress, bool? parallelizeTestCollections, int? maxThreadCount, ExtendedXunitFilters filters) { if (cancel) return null; diff --git a/src/xunit.console.netcore/Project/ExtendedXunitProject.cs b/src/xunit.console.netcore/Project/ExtendedXunitProject.cs new file mode 100644 index 0000000000..5da9319c85 --- /dev/null +++ b/src/xunit.console.netcore/Project/ExtendedXunitProject.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit.ConsoleClient.Filters; + +namespace Xunit.ConsoleClient.Project +{ + public class ExtendedXunitProject : XunitProject + { + public new ExtendedXunitFilters Filters { get; private set; } + public ExtendedXunitProject() : base() + { + Filters = new ExtendedXunitFilters(); + } + } +} diff --git a/src/xunit.console.netcore/Utility/TransformFactory.cs b/src/xunit.console.netcore/Utility/TransformFactory.cs index 3c8bb50806..f21b8e7639 100644 --- a/src/xunit.console.netcore/Utility/TransformFactory.cs +++ b/src/xunit.console.netcore/Utility/TransformFactory.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Xml; using System.Xml.Linq; +using Xunit.ConsoleClient.Project; #if !NETCORE using System.Configuration; @@ -50,7 +51,7 @@ public static List AvailableTransforms get { return instance.availableTransforms.Values.ToList(); } } - public static List> GetXmlTransformers(XunitProject project) + public static List> GetXmlTransformers(ExtendedXunitProject project) { return project.Output.Select(output => new Action(xml => instance.availableTransforms[output.Key].OutputHandler(xml, output.Value))).ToList(); } diff --git a/src/xunit.console.netcore/xunit.console.netcore.csproj b/src/xunit.console.netcore/xunit.console.netcore.csproj index 0263ee41ba..f375f3a389 100644 --- a/src/xunit.console.netcore/xunit.console.netcore.csproj +++ b/src/xunit.console.netcore/xunit.console.netcore.csproj @@ -41,7 +41,9 @@ Common\TestDiscoveryVisitor.cs + +