From 3b832a0e15f380e39efc057b6f4a1eb9f2c6c087 Mon Sep 17 00:00:00 2001 From: Jeremy Herbison <jeremy.herbison@gmail.com> Date: Fri, 12 Oct 2018 15:31:06 -0400 Subject: [PATCH 1/9] Added an "IncludeDirectories" param so that assemblies that are not project references can also be instrumented. --- src/coverlet.console/Program.cs | 3 +- src/coverlet.core/Coverage.cs | 53 +++++++++--------- src/coverlet.core/CoverageResult.cs | 20 +++---- .../Helpers/InstrumentationHelper.cs | 55 +++++++++++++++++-- .../InstrumentationTask.cs | 10 +++- src/coverlet.msbuild/coverlet.msbuild.props | 1 + src/coverlet.msbuild/coverlet.msbuild.targets | 2 + test/coverlet.core.tests/CoverageTests.cs | 2 +- .../Helpers/InstrumentationHelperTests.cs | 38 ++++++++++--- 9 files changed, 133 insertions(+), 51 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 89bc94ce9..5d7e837bd 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -32,6 +32,7 @@ static int Main(string[] args) CommandOption thresholdTypes = app.Option("--threshold-type", "Coverage type to apply the threshold to.", 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 includeDirectories = app.Option("--include-directories", "Include directories containing additional assemblies to be instrumented.", 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); @@ -43,7 +44,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(), includeDirectories.Values.ToArray(), excludedSourceFiles.Values.ToArray(), mergeWith.Value()); coverage.PrepareModules(); Process process = new Process(); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 00d00e1e3..b440a78f2 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -17,6 +17,7 @@ public class Coverage private string _identifier; private string[] _excludeFilters; private string[] _includeFilters; + private string[] _includeDirectories; private string[] _excludedSourceFiles; private string _mergeWith; private List<InstrumenterResult> _results; @@ -26,11 +27,13 @@ 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[] includeDirectories, string[] excludedSourceFiles, string mergeWith) { + Console.WriteLine(module); _module = module; _excludeFilters = excludeFilters; _includeFilters = includeFilters; + _includeDirectories = includeDirectories; _excludedSourceFiles = excludedSourceFiles; _mergeWith = mergeWith; @@ -40,7 +43,7 @@ public Coverage(string module, string[] excludeFilters, string[] includeFilters, public void PrepareModules() { - string[] modules = InstrumentationHelper.GetCoverableModules(_module); + string[] modules = InstrumentationHelper.GetCoverableModules(_module, _includeDirectories); string[] excludes = InstrumentationHelper.GetExcludedFiles(_excludedSourceFiles); _excludeFilters = _excludeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray(); _includeFilters = _includeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray(); @@ -205,29 +208,29 @@ 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<KeyValuePair<(int, int), Branch>> branchesToRemove = new List<KeyValuePair<(int, int), Branch>>(); - 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<KeyValuePair<(int, int), Branch>> branchesToRemove = new List<KeyValuePair<(int, int), Branch>>(); + 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); diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index ed17451a7..25ca1605c 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -53,43 +53,43 @@ internal void Merge(Modules modules) { foreach (var document in module.Value) { - if (!this.Modules[module.Key].ContainsKey(document.Key)) + if (!this.Modules[existingKey].ContainsKey(document.Key)) { - this.Modules[module.Key].Add(document.Key, document.Value); + this.Modules[existingKey].Add(document.Key, document.Value); } else { foreach (var @class in document.Value) { - if (!this.Modules[module.Key][document.Key].ContainsKey(@class.Key)) + if (!this.Modules[existingKey][document.Key].ContainsKey(@class.Key)) { - this.Modules[module.Key][document.Key].Add(@class.Key, @class.Value); + this.Modules[existingKey][document.Key].Add(@class.Key, @class.Value); } else { foreach (var method in @class.Value) { - if (!this.Modules[module.Key][document.Key][@class.Key].ContainsKey(method.Key)) + if (!this.Modules[existingKey][document.Key][@class.Key].ContainsKey(method.Key)) { - this.Modules[module.Key][document.Key][@class.Key].Add(method.Key, method.Value); + this.Modules[existingKey][document.Key][@class.Key].Add(method.Key, method.Value); } else { foreach (var line in method.Value.Lines) { - if (!this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) + if (!this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) { - this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); + this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); } else { - this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; + this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; } } foreach (var branch in method.Value.Branches) { - var branches = this.Modules[module.Key][document.Key][@class.Key][method.Key].Branches; + var branches = this.Modules[existingKey][document.Key][@class.Key][method.Key].Branches; var branchInfo = branches.FirstOrDefault(b => b.EndOffset == branch.EndOffset && b.Line == branch.Line && b.Offset == branch.Offset && b.Ordinal == branch.Ordinal && b.Path == branch.Path); if (branchInfo == null) branches.Add(branch); diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index ea1fc19e1..3a25968f6 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -1,10 +1,14 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.PortableExecutable; +using System.Security.Cryptography; +using System.Text; using System.Text.RegularExpressions; using Microsoft.Extensions.FileSystemGlobbing; @@ -14,11 +18,17 @@ namespace Coverlet.Core.Helpers { internal static class InstrumentationHelper { - public static string[] GetCoverableModules(string module) + public static string[] GetCoverableModules(string module, string[] includeDirectories) { - IEnumerable<string> modules = Directory.EnumerateFiles(Path.GetDirectoryName(module)).Where(f => f.EndsWith(".exe") || f.EndsWith(".dll")); - modules = modules.Where(m => IsAssembly(m) && Path.GetFileName(m) != Path.GetFileName(module)); - return modules.ToArray(); + var moduleDirectory = Path.GetDirectoryName(module); + var files = new List<string>(Directory.GetFiles(moduleDirectory)); + + if (includeDirectories != null) + foreach (var includeDirectory in ExpandIncludeDirectories(includeDirectories, moduleDirectory)) + files.AddRange(Directory.GetFiles(includeDirectory)); + + return files.Where(m => IsAssembly(m) && Path.GetFileName(m) != Path.GetFileName(module)) + .Distinct().ToArray(); } public static bool HasPdb(string module) @@ -40,10 +50,11 @@ public static bool HasPdb(string module) } } - public static void BackupOriginalModule(string module, string identifier) + public static string BackupOriginalModule(string module, string identifier) { var backupPath = GetBackupPath(module, identifier); File.Copy(module, backupPath); + return backupPath; } public static void RestoreOriginalModule(string module, string identifier) @@ -243,11 +254,33 @@ private static bool IsTypeFilterMatch(string module, string type, string[] filte return false; } + private static IEnumerable<string> ExpandIncludeDirectories(string[] includeDirectories, string moduleDirectory) + { + var result = new List<string>(includeDirectories.Length); + + foreach (var includeDirectory in includeDirectories.Where(d => d != null)) + { + var fullPath = (!Path.IsPathRooted(includeDirectory) + ? Path.GetFullPath(Path.Combine(moduleDirectory, includeDirectory)) + : includeDirectory).TrimEnd('*'); + + if (!Directory.Exists(fullPath)) + throw new DirectoryNotFoundException($"The included directory '{fullPath}' does not exist."); + + if (includeDirectory.EndsWith("*", StringComparison.Ordinal)) + result.AddRange(Directory.GetDirectories(fullPath)); + else + result.Add(fullPath); + } + + return result; + } + private static string GetBackupPath(string module, string identifier) { return Path.Combine( Path.GetTempPath(), - Path.GetFileNameWithoutExtension(module) + "_" + identifier + ".dll" + Path.GetFileNameWithoutExtension(module) + "_" + GetPathHash(Path.GetDirectoryName(module)) + "_" + identifier + ".dll" ); } @@ -274,6 +307,9 @@ private static bool IsAssembly(string filePath) { try { + if (!(filePath.EndsWith(".exe") || filePath.EndsWith(".dll"))) + return false; + AssemblyName.GetAssemblyName(filePath); return true; } @@ -282,6 +318,13 @@ private static bool IsAssembly(string filePath) return false; } } + + private static string GetPathHash(string path) + { + using (var md5 = MD5.Create()) + return BitConverter.ToString(md5.ComputeHash(Encoding.Unicode.GetBytes(path))) + .Replace("-", string.Empty); + } } } diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index e53c87666..400f7f1d6 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -11,6 +11,7 @@ public class InstrumentationTask : Task private string _path; private string _exclude; private string _include; + private string _includeDirectories; private string _excludeByFile; private string _mergeWith; @@ -38,6 +39,12 @@ public string Include set { _include = value; } } + public string IncludeDirectories + { + get { return _includeDirectories; } + set { _includeDirectories = value; } + } + public string ExcludeByFile { get { return _excludeByFile; } @@ -57,8 +64,9 @@ public override bool Execute() var excludedSourceFiles = _excludeByFile?.Split(','); var excludeFilters = _exclude?.Split(','); var includeFilters = _include?.Split(','); + var includeDirectories = _includeDirectories?.Split(','); - _coverage = new Coverage(_path, excludeFilters, includeFilters, excludedSourceFiles, _mergeWith); + _coverage = new Coverage(_path, excludeFilters, includeFilters, includeDirectories, excludedSourceFiles, _mergeWith); _coverage.PrepareModules(); } catch (Exception ex) diff --git a/src/coverlet.msbuild/coverlet.msbuild.props b/src/coverlet.msbuild/coverlet.msbuild.props index d776044e9..d4beab8e3 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.props +++ b/src/coverlet.msbuild/coverlet.msbuild.props @@ -9,5 +9,6 @@ <MergeWith Condition="$(MergeWith) == ''"></MergeWith> <Threshold Condition="$(Threshold) == ''">0</Threshold> <ThresholdType Condition="$(ThresholdType) == ''">line,branch,method</ThresholdType> + <IncludeDirectories Condition="$(IncludeDirectories) == ''"></IncludeDirectories> </PropertyGroup> </Project> diff --git a/src/coverlet.msbuild/coverlet.msbuild.targets b/src/coverlet.msbuild/coverlet.msbuild.targets index b8f40a549..ec8807f9f 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.targets +++ b/src/coverlet.msbuild/coverlet.msbuild.targets @@ -7,6 +7,7 @@ <Coverlet.MSbuild.Tasks.InstrumentationTask Condition="'$(VSTestNoBuild)' == 'true' and $(CollectCoverage) == 'true'" Include="$(Include)" + IncludeDirectories="$(IncludeDirectories)" Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" MergeWith="$(MergeWith)" @@ -17,6 +18,7 @@ <Coverlet.MSbuild.Tasks.InstrumentationTask Condition="'$(VSTestNoBuild)' != 'true' and $(CollectCoverage) == 'true'" Include="$(Include)" + IncludeDirectories="$(IncludeDirectories)" Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" MergeWith="$(MergeWith)" diff --git a/test/coverlet.core.tests/CoverageTests.cs b/test/coverlet.core.tests/CoverageTests.cs index fc9d26f60..2732e656e 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<string>(), Array.Empty<string>(), Array.Empty<string>(), string.Empty); + var coverage = new Coverage(testModule, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), string.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 44e1021c8..51ec77362 100644 --- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs @@ -12,7 +12,7 @@ public class InstrumentationHelperTests public void TestGetDependencies() { string module = typeof(InstrumentationHelperTests).Assembly.Location; - var modules = InstrumentationHelper.GetCoverableModules(module); + var modules = InstrumentationHelper.GetCoverableModules(module, null); Assert.False(Array.Exists(modules, m => m == module)); } @@ -28,12 +28,7 @@ public void TestBackupOriginalModule() string module = typeof(InstrumentationHelperTests).Assembly.Location; string identifier = Guid.NewGuid().ToString(); - InstrumentationHelper.BackupOriginalModule(module, identifier); - - var backupPath = Path.Combine( - Path.GetTempPath(), - Path.GetFileNameWithoutExtension(module) + "_" + identifier + ".dll" - ); + var backupPath = InstrumentationHelper.BackupOriginalModule(module, identifier); Assert.True(File.Exists(backupPath)); } @@ -230,6 +225,35 @@ public void TestIsTypeExcludedAndIncludedWithMatchingAndMismatchingFilter(string Assert.True(result); } + [Fact] + public void TestIncludeDirectoryInvalidThrowsDirectoryNotFoundException() + { + string module = typeof(InstrumentationHelperTests).Assembly.Location; + Assert.Throws<DirectoryNotFoundException>(() => + InstrumentationHelper.GetCoverableModules(module, new[] {"Foobar"})); + } + + [Fact] + public void TestIncludeDirectories() + { + string module = typeof(InstrumentationHelperTests).Assembly.Location; + + var currentDirModules = InstrumentationHelper.GetCoverableModules(module, + new[] {Environment.CurrentDirectory}); + + var parentDirWildcardModules = InstrumentationHelper.GetCoverableModules(module, + new[] {Path.Combine(Directory.GetParent(Environment.CurrentDirectory).FullName, "*")}); + + // There are at least as many modules found when searching the parent directory's subdirectories + Assert.True(parentDirWildcardModules.Length >= currentDirModules.Length); + + var relativePathModules = InstrumentationHelper.GetCoverableModules(module, + new[] {"."}); + + // Same number of modules found when using a relative path + Assert.Equal(currentDirModules.Length, relativePathModules.Length); + } + public static IEnumerable<object[]> ValidModuleFilterData => new List<object[]> { From 473cee95d2a48548823470308833f1d72bc3067c Mon Sep 17 00:00:00 2001 From: Jeremy Herbison <jherby2k@users.noreply.github.com> Date: Fri, 12 Oct 2018 16:27:26 -0400 Subject: [PATCH 2/9] Remove WriteLine I accidentally left behind. --- src/coverlet.core/Coverage.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index b440a78f2..24ad0a7f3 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -29,7 +29,6 @@ public string Identifier public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] includeDirectories, string[] excludedSourceFiles, string mergeWith) { - Console.WriteLine(module); _module = module; _excludeFilters = excludeFilters; _includeFilters = includeFilters; @@ -153,8 +152,10 @@ public CoverageResult GetCoverageResult() InstrumentationHelper.RestoreOriginalModule(result.ModulePath, _identifier); } - var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules }; - if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith) && File.Exists(_mergeWith)) + var coverageResult = new CoverageResult { Identifier = _identifier, Modules = new Modules() }; + coverageResult.Merge(modules); + + if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith)) { string json = File.ReadAllText(_mergeWith); coverageResult.Merge(JsonConvert.DeserializeObject<Modules>(json)); From 6ecac9db3635fb7ba5c12f2d384710f2171c30e6 Mon Sep 17 00:00:00 2001 From: Jeremy Herbison <jeremy.herbison@gmail.com> Date: Sun, 14 Oct 2018 15:55:21 -0400 Subject: [PATCH 3/9] Revert changes to CoverageResult, as merging the same file from multiple locations is already supported in the master branch. --- src/coverlet.core/CoverageResult.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index 25ca1605c..ed17451a7 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -53,43 +53,43 @@ internal void Merge(Modules modules) { foreach (var document in module.Value) { - if (!this.Modules[existingKey].ContainsKey(document.Key)) + if (!this.Modules[module.Key].ContainsKey(document.Key)) { - this.Modules[existingKey].Add(document.Key, document.Value); + this.Modules[module.Key].Add(document.Key, document.Value); } else { foreach (var @class in document.Value) { - if (!this.Modules[existingKey][document.Key].ContainsKey(@class.Key)) + if (!this.Modules[module.Key][document.Key].ContainsKey(@class.Key)) { - this.Modules[existingKey][document.Key].Add(@class.Key, @class.Value); + this.Modules[module.Key][document.Key].Add(@class.Key, @class.Value); } else { foreach (var method in @class.Value) { - if (!this.Modules[existingKey][document.Key][@class.Key].ContainsKey(method.Key)) + if (!this.Modules[module.Key][document.Key][@class.Key].ContainsKey(method.Key)) { - this.Modules[existingKey][document.Key][@class.Key].Add(method.Key, method.Value); + this.Modules[module.Key][document.Key][@class.Key].Add(method.Key, method.Value); } else { foreach (var line in method.Value.Lines) { - if (!this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) + if (!this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) { - this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); + this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); } else { - this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; + this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; } } foreach (var branch in method.Value.Branches) { - var branches = this.Modules[existingKey][document.Key][@class.Key][method.Key].Branches; + var branches = this.Modules[module.Key][document.Key][@class.Key][method.Key].Branches; var branchInfo = branches.FirstOrDefault(b => b.EndOffset == branch.EndOffset && b.Line == branch.Line && b.Offset == branch.Offset && b.Ordinal == branch.Ordinal && b.Path == branch.Path); if (branchInfo == null) branches.Add(branch); From 017d277e3a9b527ed2f4b3d4c052286de8435b7e Mon Sep 17 00:00:00 2001 From: Jeremy Herbison <jeremy.herbison@gmail.com> Date: Mon, 15 Oct 2018 22:39:50 -0400 Subject: [PATCH 4/9] Go back to full module paths as keys - it wasn't working in all scenarios. --- src/coverlet.core/Coverage.cs | 2 +- src/coverlet.core/CoverageResult.cs | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 24ad0a7f3..e54cd2a98 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -148,7 +148,7 @@ public CoverageResult GetCoverageResult() } } - modules.Add(Path.GetFileName(result.ModulePath), documents); + modules.Add(result.ModulePath, documents); InstrumentationHelper.RestoreOriginalModule(result.ModulePath, _identifier); } diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index ed17451a7..c78aa7dcd 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -45,7 +44,9 @@ internal void Merge(Modules modules) { foreach (var module in modules) { - if (!this.Modules.Keys.Contains(module.Key)) + var existingKey = Modules.Keys.SingleOrDefault(key => Path.GetFileName(key) == Path.GetFileName(module.Key)); + + if (existingKey == null) { this.Modules.Add(module.Key, module.Value); } @@ -53,43 +54,43 @@ internal void Merge(Modules modules) { foreach (var document in module.Value) { - if (!this.Modules[module.Key].ContainsKey(document.Key)) + if (!this.Modules[existingKey].ContainsKey(document.Key)) { - this.Modules[module.Key].Add(document.Key, document.Value); + this.Modules[existingKey].Add(document.Key, document.Value); } else { foreach (var @class in document.Value) { - if (!this.Modules[module.Key][document.Key].ContainsKey(@class.Key)) + if (!this.Modules[existingKey][document.Key].ContainsKey(@class.Key)) { - this.Modules[module.Key][document.Key].Add(@class.Key, @class.Value); + this.Modules[existingKey][document.Key].Add(@class.Key, @class.Value); } else { foreach (var method in @class.Value) { - if (!this.Modules[module.Key][document.Key][@class.Key].ContainsKey(method.Key)) + if (!this.Modules[existingKey][document.Key][@class.Key].ContainsKey(method.Key)) { - this.Modules[module.Key][document.Key][@class.Key].Add(method.Key, method.Value); + this.Modules[existingKey][document.Key][@class.Key].Add(method.Key, method.Value); } else { foreach (var line in method.Value.Lines) { - if (!this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) + if (!this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) { - this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); + this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); } else { - this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; + this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; } } foreach (var branch in method.Value.Branches) { - var branches = this.Modules[module.Key][document.Key][@class.Key][method.Key].Branches; + var branches = this.Modules[existingKey][document.Key][@class.Key][method.Key].Branches; var branchInfo = branches.FirstOrDefault(b => b.EndOffset == branch.EndOffset && b.Line == branch.Line && b.Offset == branch.Offset && b.Ordinal == branch.Ordinal && b.Path == branch.Path); if (branchInfo == null) branches.Add(branch); From 0bf5227444c4c2957b3bec383bdd139ca6d9e81f Mon Sep 17 00:00:00 2001 From: Jeremy Herbison <jeremy.herbison@gmail.com> Date: Mon, 15 Oct 2018 23:04:02 -0400 Subject: [PATCH 5/9] Skip included directories that don't exist quietly, rather than throwing. --- src/coverlet.core/Helpers/InstrumentationHelper.cs | 3 +-- .../Helpers/InstrumentationHelperTests.cs | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index 3a25968f6..5321247ea 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -264,8 +264,7 @@ private static IEnumerable<string> ExpandIncludeDirectories(string[] includeDire ? Path.GetFullPath(Path.Combine(moduleDirectory, includeDirectory)) : includeDirectory).TrimEnd('*'); - if (!Directory.Exists(fullPath)) - throw new DirectoryNotFoundException($"The included directory '{fullPath}' does not exist."); + if (!Directory.Exists(fullPath)) continue; if (includeDirectory.EndsWith("*", StringComparison.Ordinal)) result.AddRange(Directory.GetDirectories(fullPath)); diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs index 51ec77362..c7043cf86 100644 --- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs @@ -225,14 +225,6 @@ public void TestIsTypeExcludedAndIncludedWithMatchingAndMismatchingFilter(string Assert.True(result); } - [Fact] - public void TestIncludeDirectoryInvalidThrowsDirectoryNotFoundException() - { - string module = typeof(InstrumentationHelperTests).Assembly.Location; - Assert.Throws<DirectoryNotFoundException>(() => - InstrumentationHelper.GetCoverableModules(module, new[] {"Foobar"})); - } - [Fact] public void TestIncludeDirectories() { From e6a9524b2b4683f1ec9ad322ca5e3767c506ba84 Mon Sep 17 00:00:00 2001 From: Jeremy Herbison <jeremy.herbison@gmail.com> Date: Sun, 4 Nov 2018 00:12:21 -0400 Subject: [PATCH 6/9] Restore accidentally-removed #220. --- src/coverlet.core/Coverage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index e54cd2a98..067bebc78 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -155,7 +155,7 @@ public CoverageResult GetCoverageResult() var coverageResult = new CoverageResult { Identifier = _identifier, Modules = new Modules() }; coverageResult.Merge(modules); - if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith)) + if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith) && File.Exists(_mergeWith)) { string json = File.ReadAllText(_mergeWith); coverageResult.Merge(JsonConvert.DeserializeObject<Modules>(json)); From a7a27e7abe02d586b729bcb84a43d677d799d6af Mon Sep 17 00:00:00 2001 From: Viktor Hofer <viktor.hofer@microsoft.com> Date: Mon, 26 Nov 2018 19:00:08 +0100 Subject: [PATCH 7/9] Apply PR from viktorhofer --- README.md | 25 +++--- src/coverlet.core/Coverage.cs | 25 ++++-- src/coverlet.core/CoverageResult.cs | 25 +++--- .../Helpers/InstrumentationHelper.cs | 87 ++++++++++--------- .../Helpers/InstrumentationHelperTests.cs | 2 +- 5 files changed, 87 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index d79bb70d2..c25c0e694 100644 --- a/README.md +++ b/README.md @@ -56,18 +56,19 @@ Arguments: <ASSEMBLY> Path to the test assembly. Options: - -h|--help Show help information - -v|--version Show version information - -t|--target Path to the test runner application. - -a|--targetargs Arguments to be passed to the test runner. - -o|--output Output of the generated coverage report - -f|--format Format of the generated coverage report. - --threshold Exits with error if the coverage % is below value. - --threshold-type Coverage type to apply the threshold to. - --exclude Filter expressions to exclude specific modules and types. - --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. + -h|--help Show help information + -v|--version Show version information + -t|--target Path to the test runner application. + -a|--targetargs Arguments to be passed to the test runner. + -o|--output Output of the generated coverage report + -f|--format Format of the generated coverage report. + --threshold Exits with error if the coverage % is below value. + --threshold-type Coverage type to apply the threshold to. + --exclude Filter expressions to exclude specific modules and types. + --include Filter expressions to include specific modules and types. + --include-directories Include directories containing additional assemblies to be instrumented. + --exclude-by-file Glob patterns specifying source files to exclude. + --merge-with Path to existing coverage result to merge. ``` #### Code Coverage diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 067bebc78..c9a4a906c 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -32,7 +32,7 @@ public Coverage(string module, string[] excludeFilters, string[] includeFilters, _module = module; _excludeFilters = excludeFilters; _includeFilters = includeFilters; - _includeDirectories = includeDirectories; + _includeDirectories = includeDirectories ?? Array.Empty<string>(); _excludedSourceFiles = excludedSourceFiles; _mergeWith = mergeWith; @@ -49,16 +49,26 @@ public void PrepareModules() foreach (var module in modules) { - if (InstrumentationHelper.IsModuleExcluded(module, _excludeFilters) - || !InstrumentationHelper.IsModuleIncluded(module, _includeFilters)) + if (InstrumentationHelper.IsModuleExcluded(module, _excludeFilters) || + !InstrumentationHelper.IsModuleIncluded(module, _includeFilters)) continue; var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes); if (instrumenter.CanInstrument()) { InstrumentationHelper.BackupOriginalModule(module, _identifier); - var result = instrumenter.Instrument(); - _results.Add(result); + + // Guard code path and restore if instrumentation fails. + try + { + var result = instrumenter.Instrument(); + _results.Add(result); + } + catch (Exception) + { + InstrumentationHelper.RestoreOriginalModule(module, _identifier); + throw; + } } } } @@ -148,12 +158,11 @@ public CoverageResult GetCoverageResult() } } - modules.Add(result.ModulePath, documents); + modules.Add(Path.GetFileName(result.ModulePath), documents); InstrumentationHelper.RestoreOriginalModule(result.ModulePath, _identifier); } - var coverageResult = new CoverageResult { Identifier = _identifier, Modules = new Modules() }; - coverageResult.Merge(modules); + var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules }; if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith) && File.Exists(_mergeWith)) { diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index c78aa7dcd..ed17451a7 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -44,9 +45,7 @@ internal void Merge(Modules modules) { foreach (var module in modules) { - var existingKey = Modules.Keys.SingleOrDefault(key => Path.GetFileName(key) == Path.GetFileName(module.Key)); - - if (existingKey == null) + if (!this.Modules.Keys.Contains(module.Key)) { this.Modules.Add(module.Key, module.Value); } @@ -54,43 +53,43 @@ internal void Merge(Modules modules) { foreach (var document in module.Value) { - if (!this.Modules[existingKey].ContainsKey(document.Key)) + if (!this.Modules[module.Key].ContainsKey(document.Key)) { - this.Modules[existingKey].Add(document.Key, document.Value); + this.Modules[module.Key].Add(document.Key, document.Value); } else { foreach (var @class in document.Value) { - if (!this.Modules[existingKey][document.Key].ContainsKey(@class.Key)) + if (!this.Modules[module.Key][document.Key].ContainsKey(@class.Key)) { - this.Modules[existingKey][document.Key].Add(@class.Key, @class.Value); + this.Modules[module.Key][document.Key].Add(@class.Key, @class.Value); } else { foreach (var method in @class.Value) { - if (!this.Modules[existingKey][document.Key][@class.Key].ContainsKey(method.Key)) + if (!this.Modules[module.Key][document.Key][@class.Key].ContainsKey(method.Key)) { - this.Modules[existingKey][document.Key][@class.Key].Add(method.Key, method.Value); + this.Modules[module.Key][document.Key][@class.Key].Add(method.Key, method.Value); } else { foreach (var line in method.Value.Lines) { - if (!this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) + if (!this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) { - this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); + this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); } else { - this.Modules[existingKey][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; + this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; } } foreach (var branch in method.Value.Branches) { - var branches = this.Modules[existingKey][document.Key][@class.Key][method.Key].Branches; + var branches = this.Modules[module.Key][document.Key][@class.Key][method.Key].Branches; var branchInfo = branches.FirstOrDefault(b => b.EndOffset == branch.EndOffset && b.Line == branch.Line && b.Offset == branch.Offset && b.Ordinal == branch.Ordinal && b.Path == branch.Path); if (branchInfo == null) branches.Add(branch); diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index 5321247ea..5b3d7c0c0 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -1,14 +1,10 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.PortableExecutable; -using System.Security.Cryptography; -using System.Text; using System.Text.RegularExpressions; using Microsoft.Extensions.FileSystemGlobbing; @@ -20,15 +16,45 @@ internal static class InstrumentationHelper { public static string[] GetCoverableModules(string module, string[] includeDirectories) { - var moduleDirectory = Path.GetDirectoryName(module); - var files = new List<string>(Directory.GetFiles(moduleDirectory)); + Debug.Assert(includeDirectories != null, "Parameter " + nameof(includeDirectories) + " in method " + + nameof(InstrumentationHelper) + "." + nameof(GetCoverableModules) + " must not be null"); - if (includeDirectories != null) - foreach (var includeDirectory in ExpandIncludeDirectories(includeDirectories, moduleDirectory)) - files.AddRange(Directory.GetFiles(includeDirectory)); + string moduleDirectory = Path.GetDirectoryName(module); + if (moduleDirectory == string.Empty) + { + moduleDirectory = Directory.GetCurrentDirectory(); + } + + var dirs = new List<string>(1 + includeDirectories.Length) + { + // Add the test assembly's directory. + moduleDirectory + }; - return files.Where(m => IsAssembly(m) && Path.GetFileName(m) != Path.GetFileName(module)) - .Distinct().ToArray(); + // Prepare all the directories in which we probe for modules. + foreach (var includeDirectory in includeDirectories.Where(d => d != null)) + { + var fullPath = (!Path.IsPathRooted(includeDirectory) + ? Path.GetFullPath(Path.Combine(moduleDirectory, includeDirectory)) + : includeDirectory).TrimEnd('*'); + + if (!Directory.Exists(fullPath)) continue; + + if (includeDirectory.EndsWith("*", StringComparison.Ordinal)) + dirs.AddRange(Directory.GetDirectories(fullPath)); + else + dirs.Add(fullPath); + } + + // The test module's name must be unique. + var uniqueModules = new HashSet<string> + { + Path.GetFileName(module) + }; + + return dirs.SelectMany(d => Directory.EnumerateFiles(d)) + .Where(m => IsAssembly(m) && uniqueModules.Add(Path.GetFileName(m))) + .ToArray(); } public static bool HasPdb(string module) @@ -254,32 +280,11 @@ private static bool IsTypeFilterMatch(string module, string type, string[] filte return false; } - private static IEnumerable<string> ExpandIncludeDirectories(string[] includeDirectories, string moduleDirectory) - { - var result = new List<string>(includeDirectories.Length); - - foreach (var includeDirectory in includeDirectories.Where(d => d != null)) - { - var fullPath = (!Path.IsPathRooted(includeDirectory) - ? Path.GetFullPath(Path.Combine(moduleDirectory, includeDirectory)) - : includeDirectory).TrimEnd('*'); - - if (!Directory.Exists(fullPath)) continue; - - if (includeDirectory.EndsWith("*", StringComparison.Ordinal)) - result.AddRange(Directory.GetDirectories(fullPath)); - else - result.Add(fullPath); - } - - return result; - } - private static string GetBackupPath(string module, string identifier) { return Path.Combine( Path.GetTempPath(), - Path.GetFileNameWithoutExtension(module) + "_" + GetPathHash(Path.GetDirectoryName(module)) + "_" + identifier + ".dll" + Path.GetFileNameWithoutExtension(module) + "_" + identifier + ".dll" ); } @@ -304,11 +309,14 @@ private static string WildcardToRegex(string pattern) private static bool IsAssembly(string filePath) { + Debug.Assert(filePath != null, "Parameter " + nameof(filePath) + " in " + nameof(InstrumentationHelper) + + "." + nameof(IsAssembly) + " must not be null."); + + if (!(filePath.EndsWith(".exe") || filePath.EndsWith(".dll"))) + return false; + try { - if (!(filePath.EndsWith(".exe") || filePath.EndsWith(".dll"))) - return false; - AssemblyName.GetAssemblyName(filePath); return true; } @@ -317,13 +325,6 @@ private static bool IsAssembly(string filePath) return false; } } - - private static string GetPathHash(string path) - { - using (var md5 = MD5.Create()) - return BitConverter.ToString(md5.ComputeHash(Encoding.Unicode.GetBytes(path))) - .Replace("-", string.Empty); - } } } diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs index c7043cf86..28530599d 100644 --- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs @@ -12,7 +12,7 @@ public class InstrumentationHelperTests public void TestGetDependencies() { string module = typeof(InstrumentationHelperTests).Assembly.Location; - var modules = InstrumentationHelper.GetCoverableModules(module, null); + var modules = InstrumentationHelper.GetCoverableModules(module, Array.Empty<string>()); Assert.False(Array.Exists(modules, m => m == module)); } From d6e008fba9afc2bde3d45d64bf6d66eac912e48d Mon Sep 17 00:00:00 2001 From: Viktor Hofer <viktor.hofer@microsoft.com> Date: Wed, 28 Nov 2018 16:36:52 +0100 Subject: [PATCH 8/9] Singular argument name, code cleanup, cwd change --- README.md | 26 +++++++++--------- src/coverlet.console/Program.cs | 2 +- src/coverlet.core/Coverage.cs | 2 +- .../Helpers/InstrumentationHelper.cs | 27 ++++++++++--------- .../InstrumentationTask.cs | 10 +++---- src/coverlet.msbuild/coverlet.msbuild.props | 2 +- src/coverlet.msbuild/coverlet.msbuild.targets | 4 +-- 7 files changed, 37 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index c25c0e694..00aa7e0f0 100644 --- a/README.md +++ b/README.md @@ -56,19 +56,19 @@ Arguments: <ASSEMBLY> Path to the test assembly. Options: - -h|--help Show help information - -v|--version Show version information - -t|--target Path to the test runner application. - -a|--targetargs Arguments to be passed to the test runner. - -o|--output Output of the generated coverage report - -f|--format Format of the generated coverage report. - --threshold Exits with error if the coverage % is below value. - --threshold-type Coverage type to apply the threshold to. - --exclude Filter expressions to exclude specific modules and types. - --include Filter expressions to include specific modules and types. - --include-directories Include directories containing additional assemblies to be instrumented. - --exclude-by-file Glob patterns specifying source files to exclude. - --merge-with Path to existing coverage result to merge. + -h|--help Show help information + -v|--version Show version information + -t|--target Path to the test runner application. + -a|--targetargs Arguments to be passed to the test runner. + -o|--output Output of the generated coverage report + -f|--format Format of the generated coverage report. + --threshold Exits with error if the coverage % is below value. + --threshold-type Coverage type to apply the threshold to. + --exclude Filter expressions to exclude specific modules and types. + --include Filter expressions to include specific modules and types. + --include-directory Include directories containing additional assemblies to be instrumented. + --exclude-by-file Glob patterns specifying source files to exclude. + --merge-with Path to existing coverage result to merge. ``` #### Code Coverage diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 5d7e837bd..f5156336c 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -32,7 +32,7 @@ static int Main(string[] args) CommandOption thresholdTypes = app.Option("--threshold-type", "Coverage type to apply the threshold to.", 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 includeDirectories = app.Option("--include-directories", "Include directories containing additional assemblies to be instrumented.", CommandOptionType.MultipleValue); + CommandOption includeDirectories = app.Option("--include-directory", "Include directories containing additional assemblies to be instrumented.", 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); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index c9a4a906c..c6669257e 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -66,8 +66,8 @@ public void PrepareModules() } catch (Exception) { + // TODO: With verbose logging we should note that instrumentation failed. InstrumentationHelper.RestoreOriginalModule(module, _identifier); - throw; } } } diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index 5b3d7c0c0..6982ea1b6 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -14,10 +14,9 @@ namespace Coverlet.Core.Helpers { internal static class InstrumentationHelper { - public static string[] GetCoverableModules(string module, string[] includeDirectories) + public static string[] GetCoverableModules(string module, string[] directories) { - Debug.Assert(includeDirectories != null, "Parameter " + nameof(includeDirectories) + " in method " + - nameof(InstrumentationHelper) + "." + nameof(GetCoverableModules) + " must not be null"); + Debug.Assert(directories != null); string moduleDirectory = Path.GetDirectoryName(module); if (moduleDirectory == string.Empty) @@ -25,28 +24,31 @@ public static string[] GetCoverableModules(string module, string[] includeDirect moduleDirectory = Directory.GetCurrentDirectory(); } - var dirs = new List<string>(1 + includeDirectories.Length) + var dirs = new List<string>() { // Add the test assembly's directory. moduleDirectory }; - // Prepare all the directories in which we probe for modules. - foreach (var includeDirectory in includeDirectories.Where(d => d != null)) + // Prepare all the directories we probe for modules. + foreach (string directory in directories) { - var fullPath = (!Path.IsPathRooted(includeDirectory) - ? Path.GetFullPath(Path.Combine(moduleDirectory, includeDirectory)) - : includeDirectory).TrimEnd('*'); + if (string.IsNullOrWhiteSpace(directory)) continue; + + string fullPath = (!Path.IsPathRooted(directory) + ? Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), directory)) + : directory).TrimEnd('*'); if (!Directory.Exists(fullPath)) continue; - if (includeDirectory.EndsWith("*", StringComparison.Ordinal)) + if (directory.EndsWith("*", StringComparison.Ordinal)) dirs.AddRange(Directory.GetDirectories(fullPath)); else dirs.Add(fullPath); } - // The test module's name must be unique. + // The module's name must be unique. + // Add the test module itself to exclude it from the files enumeration. var uniqueModules = new HashSet<string> { Path.GetFileName(module) @@ -309,8 +311,7 @@ private static string WildcardToRegex(string pattern) private static bool IsAssembly(string filePath) { - Debug.Assert(filePath != null, "Parameter " + nameof(filePath) + " in " + nameof(InstrumentationHelper) + - "." + nameof(IsAssembly) + " must not be null."); + Debug.Assert(filePath != null); if (!(filePath.EndsWith(".exe") || filePath.EndsWith(".dll"))) return false; diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index 400f7f1d6..40410e024 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -11,7 +11,7 @@ public class InstrumentationTask : Task private string _path; private string _exclude; private string _include; - private string _includeDirectories; + private string _includeDirectory; private string _excludeByFile; private string _mergeWith; @@ -39,10 +39,10 @@ public string Include set { _include = value; } } - public string IncludeDirectories + public string IncludeDirectory { - get { return _includeDirectories; } - set { _includeDirectories = value; } + get { return _includeDirectory; } + set { _includeDirectory = value; } } public string ExcludeByFile @@ -64,7 +64,7 @@ public override bool Execute() var excludedSourceFiles = _excludeByFile?.Split(','); var excludeFilters = _exclude?.Split(','); var includeFilters = _include?.Split(','); - var includeDirectories = _includeDirectories?.Split(','); + var includeDirectories = _includeDirectory?.Split(','); _coverage = new Coverage(_path, excludeFilters, includeFilters, includeDirectories, excludedSourceFiles, _mergeWith); _coverage.PrepareModules(); diff --git a/src/coverlet.msbuild/coverlet.msbuild.props b/src/coverlet.msbuild/coverlet.msbuild.props index d4beab8e3..729a6e2fa 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.props +++ b/src/coverlet.msbuild/coverlet.msbuild.props @@ -9,6 +9,6 @@ <MergeWith Condition="$(MergeWith) == ''"></MergeWith> <Threshold Condition="$(Threshold) == ''">0</Threshold> <ThresholdType Condition="$(ThresholdType) == ''">line,branch,method</ThresholdType> - <IncludeDirectories Condition="$(IncludeDirectories) == ''"></IncludeDirectories> + <IncludeDirectory Condition="$(IncludeDirectory) == ''"></IncludeDirectory> </PropertyGroup> </Project> diff --git a/src/coverlet.msbuild/coverlet.msbuild.targets b/src/coverlet.msbuild/coverlet.msbuild.targets index ec8807f9f..acbb8801a 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.targets +++ b/src/coverlet.msbuild/coverlet.msbuild.targets @@ -7,7 +7,7 @@ <Coverlet.MSbuild.Tasks.InstrumentationTask Condition="'$(VSTestNoBuild)' == 'true' and $(CollectCoverage) == 'true'" Include="$(Include)" - IncludeDirectories="$(IncludeDirectories)" + IncludeDirectory="$(IncludeDirectory)" Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" MergeWith="$(MergeWith)" @@ -18,7 +18,7 @@ <Coverlet.MSbuild.Tasks.InstrumentationTask Condition="'$(VSTestNoBuild)' != 'true' and $(CollectCoverage) == 'true'" Include="$(Include)" - IncludeDirectories="$(IncludeDirectories)" + IncludeDirectory="$(IncludeDirectory)" Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" MergeWith="$(MergeWith)" From 89f5be5081bf7019de2039246f52e451126a23fc Mon Sep 17 00:00:00 2001 From: Viktor Hofer <viktor.hofer@microsoft.com> Date: Wed, 28 Nov 2018 17:50:40 +0100 Subject: [PATCH 9/9] BackupOriginalModule no return value and overwrite --- src/coverlet.core/Helpers/InstrumentationHelper.cs | 5 ++--- .../Helpers/InstrumentationHelperTests.cs | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs index 6982ea1b6..426b76c7e 100644 --- a/src/coverlet.core/Helpers/InstrumentationHelper.cs +++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs @@ -78,11 +78,10 @@ public static bool HasPdb(string module) } } - public static string BackupOriginalModule(string module, string identifier) + public static void BackupOriginalModule(string module, string identifier) { var backupPath = GetBackupPath(module, identifier); - File.Copy(module, backupPath); - return backupPath; + File.Copy(module, backupPath, true); } public static void RestoreOriginalModule(string module, string identifier) diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs index 28530599d..c2147f50b 100644 --- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs +++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs @@ -28,7 +28,12 @@ public void TestBackupOriginalModule() string module = typeof(InstrumentationHelperTests).Assembly.Location; string identifier = Guid.NewGuid().ToString(); - var backupPath = InstrumentationHelper.BackupOriginalModule(module, identifier); + InstrumentationHelper.BackupOriginalModule(module, identifier); + + var backupPath = Path.Combine( + Path.GetTempPath(), + Path.GetFileNameWithoutExtension(module) + "_" + identifier + ".dll" + ); Assert.True(File.Exists(backupPath)); }