From 045ee5f49b2e607a97ffb133381819c0d17e3d21 Mon Sep 17 00:00:00 2001 From: Paulo Janotti Date: Fri, 27 Jul 2018 16:23:08 -0700 Subject: [PATCH] Add include option --- src/coverlet.console/Program.cs | 7 +- src/coverlet.core/Coverage.cs | 23 +++--- .../Helpers/InstrumentationHelper.cs | 70 ++++++++++++++++--- .../Instrumentation/Instrumenter.cs | 21 +++--- .../InstrumentationTask.cs | 14 +++- .../PerformanceTest.cs | 2 +- test/coverlet.core.tests/CoverageTests.cs | 2 +- .../Helpers/InstrumentationHelperTests.cs | 44 ++++++++++-- .../Instrumentation/InstrumenterTests.cs | 2 +- 9 files changed, 141 insertions(+), 44 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 0941d0c4e..867bd131d 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -31,8 +31,9 @@ static int Main(string[] args) CommandOption formats = app.Option("-f|--format", "Format of the generated coverage report.", CommandOptionType.MultipleValue); CommandOption threshold = app.Option("--threshold", "Exits with error if the coverage % is below value.", CommandOptionType.SingleValue); CommandOption thresholdTypes = app.Option("--threshold-type", "Coverage type to apply the threshold to.", CommandOptionType.MultipleValue); - CommandOption filters = app.Option("--exclude", "Filter expressions to exclude specific modules and types.", CommandOptionType.MultipleValue); - CommandOption excludes = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue); + CommandOption excludeFilters = app.Option("--exclude", "Filter expressions to exclude specific modules and types.", CommandOptionType.MultipleValue); + CommandOption includeFilters = app.Option("--include", "Filter expressions to include only specific modules and types.", CommandOptionType.MultipleValue); + CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue); app.OnExecute(() => { @@ -42,7 +43,7 @@ static int Main(string[] args) if (!target.HasValue()) throw new CommandParsingException(app, "Target must be specified."); - Coverage coverage = new Coverage(module.Value, filters.Values.ToArray(), excludes.Values.ToArray()); + Coverage coverage = new Coverage(module.Value, excludeFilters.Values.ToArray(), includeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray()); coverage.PrepareModules(); Process process = new Process(); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index fe9688a24..f98894e18 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Compression; using System.Linq; using Coverlet.Core.Helpers; @@ -13,8 +12,9 @@ public class Coverage { private string _module; private string _identifier; - private string[] _filters; - private string[] _excludes; + private string[] _excludeFilters; + private string[] _includeFilters; + private string[] _excludedSourceFiles; private List _results; public string Identifier @@ -22,11 +22,12 @@ public string Identifier get { return _identifier; } } - public Coverage(string module, string[] filters, string[] excludes) + public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] excludedSourceFiles) { _module = module; - _filters = filters; - _excludes = excludes; + _excludeFilters = excludeFilters; + _includeFilters = includeFilters; + _excludedSourceFiles = excludedSourceFiles; _identifier = Guid.NewGuid().ToString(); _results = new List(); } @@ -34,15 +35,17 @@ public Coverage(string module, string[] filters, string[] excludes) public void PrepareModules() { string[] modules = InstrumentationHelper.GetCoverableModules(_module); - string[] excludes = InstrumentationHelper.GetExcludedFiles(_excludes); - _filters = _filters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray(); + string[] excludes = InstrumentationHelper.GetExcludedFiles(_excludedSourceFiles); + _excludeFilters = _excludeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray(); + _includeFilters = _includeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray(); foreach (var module in modules) { - if (InstrumentationHelper.IsModuleExcluded(module, _filters)) + if (InstrumentationHelper.IsModuleExcluded(module, _excludeFilters) + || !InstrumentationHelper.IsModuleIncluded(module, _includeFilters)) continue; - var instrumenter = new Instrumenter(module, _identifier, _filters, excludes); + var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes); if (instrumenter.CanInstrument()) { InstrumentationHelper.BackupOriginalModule(module, _identifier); diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index 1919c1363..3462fda9e 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -111,23 +112,23 @@ public static bool IsValidFilterExpression(string filter) return true; } - public static bool IsModuleExcluded(string module, string[] filters) + public static bool IsModuleExcluded(string module, string[] excludeFilters) { - if (filters == null) + if (excludeFilters == null || excludeFilters.Length == 0) return false; module = Path.GetFileNameWithoutExtension(module); if (module == null) return false; - foreach (var filter in filters) + foreach (var filter in excludeFilters) { - string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1); string typePattern = filter.Substring(filter.IndexOf(']') + 1); if (typePattern != "*") continue; + string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1); modulePattern = WildcardToRegex(modulePattern); var regex = new Regex(modulePattern); @@ -139,30 +140,57 @@ public static bool IsModuleExcluded(string module, string[] filters) return false; } - public static bool IsTypeExcluded(string module, string type, string[] filters) + public static bool IsModuleIncluded(string module, string[] includeFilters) { - if (filters == null) - return false; + if (includeFilters == null || includeFilters.Length == 0) + return true; module = Path.GetFileNameWithoutExtension(module); if (module == null) return false; - foreach (var filter in filters) + foreach (var filter in includeFilters) { - string typePattern = filter.Substring(filter.IndexOf(']') + 1); string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1); - typePattern = WildcardToRegex(typePattern); + if (modulePattern == "*") + return true; + modulePattern = WildcardToRegex(modulePattern); - if (new Regex(typePattern).IsMatch(type) && new Regex(modulePattern).IsMatch(module)) + var regex = new Regex(modulePattern); + + if (regex.IsMatch(module)) return true; } return false; } + public static bool IsTypeExcluded(string module, string type, string[] excludeFilters) + { + if (excludeFilters == null || excludeFilters.Length == 0) + return false; + + module = Path.GetFileNameWithoutExtension(module); + if (module == null) + return false; + + return IsTypeFilterMatch(module, type, excludeFilters); + } + + public static bool IsTypeIncluded(string module, string type, string[] includeFilters) + { + if (includeFilters == null || includeFilters.Length == 0) + return true; + + module = Path.GetFileNameWithoutExtension(module); + if (module == null) + return true; + + return IsTypeFilterMatch(module, type, includeFilters); + } + public static bool IsLocalMethod(string method) => new Regex(WildcardToRegex("<*>*__*|*")).IsMatch(method); @@ -206,6 +234,26 @@ public static string[] GetExcludedFiles(string[] excludes) return files.Distinct().ToArray(); } + private static bool IsTypeFilterMatch(string module, string type, string[] filters) + { + Debug.Assert(module != null); + Debug.Assert(filters != null); + + foreach (var filter in filters) + { + string typePattern = filter.Substring(filter.IndexOf(']') + 1); + string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1); + + typePattern = WildcardToRegex(typePattern); + modulePattern = WildcardToRegex(modulePattern); + + if (new Regex(typePattern).IsMatch(type) && new Regex(modulePattern).IsMatch(module)) + return true; + } + + return false; + } + private static string GetBackupPath(string module, string identifier) { return Path.Combine( diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index e5d0d758c..19eec24aa 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -19,16 +19,18 @@ internal class Instrumenter { private readonly string _module; private readonly string _identifier; - private readonly string[] _filters; + private readonly string[] _excludeFilters; + private readonly string[] _includeFilters; private readonly string[] _excludedFiles; private readonly static Lazy _markExecutedMethodLoader = new Lazy(GetMarkExecutedMethod); private InstrumenterResult _result; - public Instrumenter(string module, string identifier, string[] filters, string[] excludedFiles) + public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles) { _module = module; _identifier = identifier; - _filters = filters; + _excludeFilters = excludeFilters; + _includeFilters = includeFilters; _excludedFiles = excludedFiles ?? Array.Empty(); } @@ -69,7 +71,8 @@ private void InstrumentModule() { var actualType = type.DeclaringType ?? type; if (!actualType.CustomAttributes.Any(IsExcludeAttribute) - && !InstrumentationHelper.IsTypeExcluded(_module, actualType.FullName, _filters)) + && !InstrumentationHelper.IsTypeExcluded(_module, actualType.FullName, _excludeFilters) + && InstrumentationHelper.IsTypeIncluded(_module, actualType.FullName, _includeFilters)) InstrumentType(type); } @@ -92,10 +95,10 @@ private void InstrumentType(TypeDefinition type) } var ctors = type.GetConstructors(); - foreach (var ctor in ctors) - { + foreach (var ctor in ctors) + { if (!ctor.CustomAttributes.Any(IsExcludeAttribute)) - InstrumentMethod(ctor); + InstrumentMethod(ctor); } } @@ -173,7 +176,7 @@ private void InstrumentIL(MethodDefinition method) private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, SequencePoint sequencePoint) { if (!_result.Documents.TryGetValue(sequencePoint.Document.Url, out var document)) - { + { document = new Document { Path = sequencePoint.Document.Url }; _result.Documents.Add(document.Path, document); } @@ -200,7 +203,7 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, BranchPoint branchPoint) { if (!_result.Documents.TryGetValue(branchPoint.Document, out var document)) - { + { document = new Document { Path = branchPoint.Document }; _result.Documents.Add(document.Path, document); } diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index 0ddbc2a26..60514c7c7 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -10,6 +10,7 @@ public class InstrumentationTask : Task private static Coverage _coverage; private string _path; private string _exclude; + private string _include; private string _excludeByFile; internal static Coverage Coverage @@ -30,6 +31,12 @@ public string Exclude set { _exclude = value; } } + public string Include + { + get { return _include; } + set { _include = value; } + } + public string ExcludeByFile { get { return _excludeByFile; } @@ -40,10 +47,11 @@ public override bool Execute() { try { - var excludes = _excludeByFile?.Split(','); - var filters = _exclude?.Split(','); + var excludedSourceFiles = _excludeByFile?.Split(','); + var excludeFilters = _exclude?.Split(','); + var includeFilters = _include?.Split(','); - _coverage = new Coverage(_path, filters, excludes); + _coverage = new Coverage(_path, excludeFilters, includeFilters, excludedSourceFiles); _coverage.PrepareModules(); } catch (Exception ex) diff --git a/test/coverlet.core.performancetest/PerformanceTest.cs b/test/coverlet.core.performancetest/PerformanceTest.cs index 0cb95b1e1..14c2e28cf 100644 --- a/test/coverlet.core.performancetest/PerformanceTest.cs +++ b/test/coverlet.core.performancetest/PerformanceTest.cs @@ -9,7 +9,7 @@ namespace coverlet.core.performancetest /// Test the performance of coverlet by running a unit test that calls a reasonably big and complex test class. /// Enable the test, compile, then run the test in the command line: /// - /// dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover test/coverlet.core.performa ncetest/ + /// dotnet test -p:CollectCoverage=true -p:CoverletOutputFormat=opencover test/coverlet.core.performancetest/ /// /// public class PerformanceTest diff --git a/test/coverlet.core.tests/CoverageTests.cs b/test/coverlet.core.tests/CoverageTests.cs index 52578a50e..45d8fdbd6 100644 --- a/test/coverlet.core.tests/CoverageTests.cs +++ b/test/coverlet.core.tests/CoverageTests.cs @@ -27,7 +27,7 @@ public void TestCoverage() // Since Coverage only instruments dependancies, we need a fake module here var testModule = Path.Combine(directory.FullName, "test.module.dll"); - var coverage = new Coverage(testModule, Array.Empty(), Array.Empty()); + var coverage = new Coverage(testModule, Array.Empty(), Array.Empty(), Array.Empty()); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs index 52ade8079..481d1915a 100644 --- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs @@ -149,6 +149,14 @@ public void TestIsModuleExcludedWithoutFilter() Assert.False(result); } + [Fact] + public void TestIsModuleIncludedWithoutFilter() + { + var result = InstrumentationHelper.IsModuleIncluded("Module.dll", new string[0]); + + Assert.True(result); + } + [Theory] [InlineData("[Module]mismatch")] [InlineData("[Mismatch]*")] @@ -159,23 +167,35 @@ public void TestIsModuleExcludedWithSingleMismatchFilter(string filter) Assert.False(result); } + [Fact] + public void TestIsModuleIncludedWithSingleMismatchFilter() + { + var result = InstrumentationHelper.IsModuleIncluded("Module.dll", new[] { "[Mismatch]*" }); + + Assert.False(result); + } + [Theory] [MemberData(nameof(ValidModuleFilterData))] - public void TestIsModuleExcludedWithFilter(string filter) + public void TestIsModuleExcludedAndIncludedWithFilter(string filter) { var result = InstrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter }); + Assert.True(result); + result = InstrumentationHelper.IsModuleIncluded("Module.dll", new[] { filter }); Assert.True(result); } [Theory] [MemberData(nameof(ValidModuleFilterData))] - public void TestIsModuleExcludedWithMatchingAndMismatchingFilter(string filter) + public void TestIsModuleExcludedAndIncludedWithMatchingAndMismatchingFilter(string filter) { var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" }; var result = InstrumentationHelper.IsModuleExcluded("Module.dll", filters); + Assert.True(result); + result = InstrumentationHelper.IsModuleIncluded("Module.dll", filters); Assert.True(result); } @@ -187,34 +207,48 @@ public void TestIsTypeExcludedWithoutFilter() Assert.False(result); } + [Fact] + public void TestIsTypeIncludedWithoutFilter() + { + var result = InstrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", new string[0]); + + Assert.True(result); + } + [Theory] [InlineData("[Module]mismatch")] [InlineData("[Mismatch]*")] [InlineData("[Mismatch]a.b.Dto")] - public void TestIsTypeExcludedWithSingleMismatchFilter(string filter) + public void TestIsTypeExcludedAndIncludedWithSingleMismatchFilter(string filter) { var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter }); + Assert.False(result); + result = InstrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", new[] { filter }); Assert.False(result); } [Theory] [MemberData(nameof(ValidModuleAndNamespaceFilterData))] - public void TestIsTypeExcludedWithFilter(string filter) + public void TestIsTypeExcludedAndIncludedWithFilter(string filter) { var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter }); + Assert.True(result); + result = InstrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", new[] { filter }); Assert.True(result); } [Theory] [MemberData(nameof(ValidModuleAndNamespaceFilterData))] - public void TestIsTypeExcludedWithMatchingAndMismatchingFilter(string filter) + public void TestIsTypeExcludedAndIncludedWithMatchingAndMismatchingFilter(string filter) { var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" }; var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", filters); + Assert.True(result); + result = InstrumentationHelper.IsTypeIncluded("Module.dll", "a.b.Dto", filters); Assert.True(result); } diff --git a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs index 196d0e362..abfdb208f 100644 --- a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs +++ b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs @@ -51,7 +51,7 @@ private InstrumenterTest CreateInstrumentor() File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); module = Path.Combine(directory.FullName, Path.GetFileName(module)); - Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty(), Array.Empty()); + Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty(), Array.Empty(), Array.Empty()); return new InstrumenterTest { Instrumenter = instrumenter,