diff --git a/README.md b/README.md index 5553f0d0b..a2c3b50b9 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Options: --include Filter expressions to include specific modules and types. --exclude-by-file Glob patterns specifying source files to exclude. --merge-with Path to existing coverage result to merge. + --exclude-non-called-files Exclude non called/non instrumented files from coverage. ``` #### Code Coverage diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index bec3e1a44..bdfbc8993 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -35,6 +35,7 @@ static int Main(string[] args) 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); CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); + CommandOption excludeNonCalledFiles = app.Option("--exclude-non-called-files", "Exclude non called/non instrumented files from coverage report", CommandOptionType.NoValue); app.OnExecute(() => { @@ -44,7 +45,7 @@ static int Main(string[] args) if (!target.HasValue()) throw new CommandParsingException(app, "Target must be specified."); - Coverage coverage = new Coverage(module.Value, excludeFilters.Values.ToArray(), includeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), mergeWith.Value()); + Coverage coverage = new Coverage(module.Value, excludeFilters.Values.ToArray(), includeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), mergeWith.Value(), excludeNonCalledFiles.HasValue()); coverage.PrepareModules(); Process process = new Process(); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 00d00e1e3..2869b942b 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -19,6 +19,7 @@ public class Coverage private string[] _includeFilters; private string[] _excludedSourceFiles; private string _mergeWith; + private bool _excludeNonCalledFiles; private List _results; public string Identifier @@ -26,13 +27,14 @@ public string Identifier get { return _identifier; } } - public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] excludedSourceFiles, string mergeWith) + public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] excludedSourceFiles, string mergeWith, bool excludeNonCalledFiles) { _module = module; _excludeFilters = excludeFilters; _includeFilters = includeFilters; _excludedSourceFiles = excludedSourceFiles; _mergeWith = mergeWith; + _excludeNonCalledFiles = excludeNonCalledFiles; _identifier = Guid.NewGuid().ToString(); _results = new List(); @@ -63,7 +65,7 @@ public void PrepareModules() public CoverageResult GetCoverageResult() { - CalculateCoverage(); + CalculateCoverage(_excludeNonCalledFiles); Modules modules = new Modules(); foreach (var result in _results) @@ -160,13 +162,20 @@ public CoverageResult GetCoverageResult() return coverageResult; } - private void CalculateCoverage() + private void CalculateCoverage(bool excludeNonCalledFiles) { + var resultsToRemove = new List(); foreach (var result in _results) { if (!File.Exists(result.HitsFilePath)) { - // File not instrumented, or nothing in it called. Warn about this? + // File not instrumented, or nothing in it called. + // Mark to be removed so no non used modules are included in coverage file? + if (excludeNonCalledFiles) + { + resultsToRemove.Add(result); + } + continue; } @@ -205,33 +214,38 @@ private void CalculateCoverage() } } - // for MoveNext() compiler autogenerated method we need to patch false positive (IAsyncStateMachine for instance) - // we'll remove all MoveNext() not covered branch - foreach (var document in result.Documents) - { - List> branchesToRemove = new List>(); - foreach (var branch in document.Value.Branches) - { - //if one branch is covered we search the other one only if it's not covered - if (CecilSymbolHelper.IsMoveNext(branch.Value.Method) && branch.Value.Hits > 0) - { - foreach (var moveNextBranch in document.Value.Branches) - { - if (moveNextBranch.Value.Method == branch.Value.Method && moveNextBranch.Value != branch.Value && moveNextBranch.Value.Hits == 0) - { - branchesToRemove.Add(moveNextBranch); - } - } - } - } - foreach (var branchToRemove in branchesToRemove) - { - document.Value.Branches.Remove(branchToRemove.Key); - } + // for MoveNext() compiler autogenerated method we need to patch false positive (IAsyncStateMachine for instance) + // we'll remove all MoveNext() not covered branch + foreach (var document in result.Documents) + { + List> branchesToRemove = new List>(); + foreach (var branch in document.Value.Branches) + { + //if one branch is covered we search the other one only if it's not covered + if (CecilSymbolHelper.IsMoveNext(branch.Value.Method) && branch.Value.Hits > 0) + { + foreach (var moveNextBranch in document.Value.Branches) + { + if (moveNextBranch.Value.Method == branch.Value.Method && moveNextBranch.Value != branch.Value && moveNextBranch.Value.Hits == 0) + { + branchesToRemove.Add(moveNextBranch); + } + } + } + } + foreach (var branchToRemove in branchesToRemove) + { + document.Value.Branches.Remove(branchToRemove.Key); + } } InstrumentationHelper.DeleteHitsFile(result.HitsFilePath); } + + foreach (var resultToRemove in resultsToRemove) + { + _results.Remove(resultToRemove); + } } } } diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index e53c87666..771d2ab83 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -13,6 +13,7 @@ public class InstrumentationTask : Task private string _include; private string _excludeByFile; private string _mergeWith; + private bool _excludeNonCalledFiles; internal static Coverage Coverage { @@ -50,6 +51,12 @@ public string MergeWith set { _mergeWith = value; } } + public bool ExcludeNonCalledFiles + { + get { return _excludeNonCalledFiles; } + set { _excludeNonCalledFiles = value; } + } + public override bool Execute() { try @@ -57,8 +64,7 @@ public override bool Execute() var excludedSourceFiles = _excludeByFile?.Split(','); var excludeFilters = _exclude?.Split(','); var includeFilters = _include?.Split(','); - - _coverage = new Coverage(_path, excludeFilters, includeFilters, excludedSourceFiles, _mergeWith); + _coverage = new Coverage(_path, excludeFilters, includeFilters, excludedSourceFiles, _mergeWith, _excludeNonCalledFiles); _coverage.PrepareModules(); } catch (Exception ex) diff --git a/src/coverlet.msbuild/coverlet.msbuild.props b/src/coverlet.msbuild/coverlet.msbuild.props index d776044e9..d2bc31375 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.props +++ b/src/coverlet.msbuild/coverlet.msbuild.props @@ -9,5 +9,6 @@ 0 line,branch,method + false diff --git a/src/coverlet.msbuild/coverlet.msbuild.targets b/src/coverlet.msbuild/coverlet.msbuild.targets index b8f40a549..3d0649af8 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.targets +++ b/src/coverlet.msbuild/coverlet.msbuild.targets @@ -10,6 +10,7 @@ Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" MergeWith="$(MergeWith)" + ExcludeNonCalledFiles="$(ExcludeNonCalledFiles)" Path="$(TargetPath)" /> @@ -20,6 +21,7 @@ Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" MergeWith="$(MergeWith)" + ExcludeNonCalledFiles="$(ExcludeNonCalledFiles)" Path="$(TargetPath)" /> diff --git a/test/coverlet.core.tests/CoverageTests.cs b/test/coverlet.core.tests/CoverageTests.cs index fc9d26f60..7cfe07b45 100644 --- a/test/coverlet.core.tests/CoverageTests.cs +++ b/test/coverlet.core.tests/CoverageTests.cs @@ -1,18 +1,15 @@ using System; using System.IO; - using Xunit; -using Moq; - -using Coverlet.Core; -using System.Collections.Generic; namespace Coverlet.Core.Tests { public class CoverageTests { - [Fact] - public void TestCoverage() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TestCoverage(bool excludeNonCalledFilesValue) { string module = GetType().Assembly.Location; string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); @@ -22,17 +19,24 @@ public void TestCoverage() File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); - // TODO: Find a way to mimick hits + // TODO: Find a way to mimic hits - // Since Coverage only instruments dependancies, we need a fake module here + // Since Coverage only instruments dependencies, we need a fake module here var testModule = Path.Combine(directory.FullName, "test.module.dll"); - var coverage = new Coverage(testModule, Array.Empty(), Array.Empty(), Array.Empty(), string.Empty); + var coverage = new Coverage(testModule, Array.Empty(), Array.Empty(), Array.Empty(), string.Empty, excludeNonCalledFilesValue); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); - Assert.NotEmpty(result.Modules); + if (excludeNonCalledFilesValue) + { + Assert.Empty(result.Modules); + } + else + { + Assert.NotEmpty(result.Modules); + } directory.Delete(true); }