From 3e78d22e7ed928d97fdd1b843d288790b249982e Mon Sep 17 00:00:00 2001 From: Joseph Sun <90574040+JosephSun2003@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:11:14 -0400 Subject: [PATCH 01/17] Adjust unit test commands due to changes noted here: https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/8.0/dotnet-pack-config (#1) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4cd6e68d..ffe337455 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ Building, testing, and packing use all the standard dotnet commands: dotnet restore dotnet build --no-restore - dotnet pack + dotnet pack -c Debug dotnet test --no-build /p:CollectCoverage=true /p:Include=\"[coverlet.collector]*,[coverlet.core]*,[coverlet.msbuild.tasks]*\" /p:Exclude=\"[coverlet.core.tests.samples.netstandard]*,[coverlet.tests.xunit.extensions]*\" NB. You need to `pack` before testing because we have some integration testing that consume packages From 88a5782f85207e7e4887a6b98e099211de479dd3 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Thu, 18 Jul 2024 13:30:56 -0400 Subject: [PATCH 02/17] Add new method to manually call the unloading of modules --- .../DataCollection/CoverageWrapper.cs | 5 +++++ .../Utilities/Interfaces/ICoverageWrapper.cs | 6 ++++++ src/coverlet.core/Coverage.cs | 17 ++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/coverlet.collector/DataCollection/CoverageWrapper.cs b/src/coverlet.collector/DataCollection/CoverageWrapper.cs index 4e3f5a729..ef4698f6c 100644 --- a/src/coverlet.collector/DataCollection/CoverageWrapper.cs +++ b/src/coverlet.collector/DataCollection/CoverageWrapper.cs @@ -70,5 +70,10 @@ public void PrepareModules(Coverage coverage) { coverage.PrepareModules(); } + + public void UnloadModule(Coverage coverage, string modulePath) + { + coverage.UnloadModule(modulePath); + } } } diff --git a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs index 48410be09..97e35e016 100644 --- a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs @@ -39,5 +39,11 @@ internal interface ICoverageWrapper /// void PrepareModules(Coverage coverage); + /// + /// Unload module in the specified path + /// + /// + void UnloadModule(Coverage coverage, string modulePath); + } } diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 07c0297fa..576de8928 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -59,6 +59,7 @@ internal class Coverage private readonly CoverageParameters _parameters; public string Identifier { get; } + private List unloadedModules { get; set; } public Coverage(string moduleOrDirectory, CoverageParameters parameters, @@ -78,6 +79,7 @@ public Coverage(string moduleOrDirectory, _cecilSymbolHelper = cecilSymbolHelper; Identifier = Guid.NewGuid().ToString(); _results = new List(); + unloadedModules = new List(); } public Coverage(CoveragePrepareResult prepareResult, @@ -241,7 +243,10 @@ public CoverageResult GetCoverageResult() } modules.Add(Path.GetFileName(result.ModulePath), documents); - _instrumentationHelper.RestoreOriginalModule(result.ModulePath, Identifier); + if (!unloadedModules.Contains(result.ModulePath)) + { + UnloadModule(result.ModulePath); + } } // In case of anonymous delegate compiler generate a custom class and passes it as type.method delegate. @@ -326,6 +331,16 @@ public CoverageResult GetCoverageResult() return coverageResult; } + /// + /// Manually invoke the unloading of modules and restoration of the original assembly files + /// + /// + public void UnloadModule(string modulePath) + { + unloadedModules.Add(modulePath); + _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + } + private bool BranchInCompilerGeneratedClass(string methodName) { foreach (InstrumenterResult instrumentedResult in _results) From dcc3c29292c66a1f7f67d31d3aaf5c9b37b46ca2 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Thu, 18 Jul 2024 13:30:56 -0400 Subject: [PATCH 03/17] Fix problems encountered with newly added method and variables from unit testing with existing tests --- .../Utilities/Interfaces/ICoverageWrapper.cs | 3 ++- src/coverlet.core/Coverage.cs | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs index 97e35e016..eb1259516 100644 --- a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs @@ -42,7 +42,8 @@ internal interface ICoverageWrapper /// /// Unload module in the specified path /// - /// + /// + /// path of the module to be unloaded void UnloadModule(Coverage coverage, string modulePath); } diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 576de8928..c7e1bdc94 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -57,9 +57,9 @@ internal class Coverage private readonly ICecilSymbolHelper _cecilSymbolHelper; private readonly List _results; private readonly CoverageParameters _parameters; + private readonly List _unloadedModules; public string Identifier { get; } - private List unloadedModules { get; set; } public Coverage(string moduleOrDirectory, CoverageParameters parameters, @@ -79,7 +79,7 @@ public Coverage(string moduleOrDirectory, _cecilSymbolHelper = cecilSymbolHelper; Identifier = Guid.NewGuid().ToString(); _results = new List(); - unloadedModules = new List(); + _unloadedModules = new List(); } public Coverage(CoveragePrepareResult prepareResult, @@ -96,6 +96,7 @@ public Coverage(CoveragePrepareResult prepareResult, _instrumentationHelper = instrumentationHelper; _fileSystem = fileSystem; _sourceRootTranslator = sourceRootTranslator; + _unloadedModules = new List(); } public CoveragePrepareResult PrepareModules() @@ -243,7 +244,7 @@ public CoverageResult GetCoverageResult() } modules.Add(Path.GetFileName(result.ModulePath), documents); - if (!unloadedModules.Contains(result.ModulePath)) + if (!_unloadedModules.Contains(result.ModulePath)) { UnloadModule(result.ModulePath); } @@ -337,7 +338,7 @@ public CoverageResult GetCoverageResult() /// public void UnloadModule(string modulePath) { - unloadedModules.Add(modulePath); + _unloadedModules.Add(modulePath); _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); } From 0dfef6f2b5b5c65e0dd297f5d9fa5fdfb72dcc0b Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Fri, 19 Jul 2024 15:28:50 -0400 Subject: [PATCH 04/17] Update documentation to clarify the modifications made and add some basic code clean up --- src/coverlet.console/Program.cs | 1 - src/coverlet.core/Coverage.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 864df117f..4605038a0 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -385,7 +385,6 @@ string sourceMappingFile return Task.FromResult(exitCode); - } catch (Win32Exception we) when (we.Source == "System.Diagnostics.Process") diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index c7e1bdc94..c4e8b30a9 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -333,7 +333,8 @@ public CoverageResult GetCoverageResult() } /// - /// Manually invoke the unloading of modules and restoration of the original assembly files + /// Invoke the unloading of modules and restoration of the original assembly files, made public to allow unloading + /// of instrumentation is testing using parallelization /// /// public void UnloadModule(string modulePath) From da1257e0d069ef5f637aaf7ccd1847ee881113c3 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Fri, 26 Jul 2024 10:40:02 -0400 Subject: [PATCH 05/17] Add minor documentation corrections and remove unneeded changes --- src/coverlet.collector/DataCollection/CoverageWrapper.cs | 5 ----- .../Utilities/Interfaces/ICoverageWrapper.cs | 7 ------- src/coverlet.core/Coverage.cs | 2 +- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/coverlet.collector/DataCollection/CoverageWrapper.cs b/src/coverlet.collector/DataCollection/CoverageWrapper.cs index ef4698f6c..4e3f5a729 100644 --- a/src/coverlet.collector/DataCollection/CoverageWrapper.cs +++ b/src/coverlet.collector/DataCollection/CoverageWrapper.cs @@ -70,10 +70,5 @@ public void PrepareModules(Coverage coverage) { coverage.PrepareModules(); } - - public void UnloadModule(Coverage coverage, string modulePath) - { - coverage.UnloadModule(modulePath); - } } } diff --git a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs index eb1259516..48410be09 100644 --- a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs @@ -39,12 +39,5 @@ internal interface ICoverageWrapper /// void PrepareModules(Coverage coverage); - /// - /// Unload module in the specified path - /// - /// - /// path of the module to be unloaded - void UnloadModule(Coverage coverage, string modulePath); - } } diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index c4e8b30a9..15eacff5a 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -334,7 +334,7 @@ public CoverageResult GetCoverageResult() /// /// Invoke the unloading of modules and restoration of the original assembly files, made public to allow unloading - /// of instrumentation is testing using parallelization + /// of instrumentation in large scale testing utilising parallelization /// /// public void UnloadModule(string modulePath) From e67ba6989b8859cebfa2329d63a977cffaa73e12 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Mon, 29 Jul 2024 10:20:52 -0400 Subject: [PATCH 06/17] Refactor explicit and implicit variable usage to comply with dotnet code style rules --- src/coverlet.console/Program.cs | 21 ++++++++++++++----- src/coverlet.core/Coverage.cs | 18 ++++++++++++---- .../DeterministicBuild.cs | 6 +++--- .../Program.cs | 4 ++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 4605038a0..a84b8fdd6 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -34,7 +34,7 @@ static int Main(string[] args) var verbosity = new Option(new[] { "--verbosity", "-v" }, () => LogLevel.Normal, "Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed.") { Arity = ArgumentArity.ZeroOrOne }; var formats = new Option(new[] { "--format", "-f" }, () => new[] { "json" }, "Format of the generated coverage report.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var threshold = new Option("--threshold", "Exits with error if the coverage % is below value.") { Arity = ArgumentArity.ZeroOrOne }; - var thresholdTypes = new Option>("--threshold-type", () => new List(new string[] { "line", "branch", "method" }), "Coverage type to apply the threshold to.").FromAmong("line", "branch", "method"); + Option> thresholdTypes = new Option>("--threshold-type", () => new List(new string[] { "line", "branch", "method" }), "Coverage type to apply the threshold to.").FromAmong("line", "branch", "method"); var thresholdStat = new Option("--threshold-stat", () => ThresholdStatistic.Minimum, "Coverage statistic used to enforce the threshold value.") { Arity = ArgumentArity.ZeroOrOne }; var excludeFilters = new Option("--exclude", "Filter expressions to exclude specific modules and types.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var includeFilters = new Option("--include", "Filter expressions to include only specific modules and types.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; @@ -49,6 +49,7 @@ static int Main(string[] args) var doesNotReturnAttributes = new Option("--does-not-return-attribute", "Attributes that mark methods that do not return") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var excludeAssembliesWithoutSources = new Option("--exclude-assemblies-without-sources", "Specifies behaviour of heuristic to ignore assemblies with missing source documents.") { Arity = ArgumentArity.ZeroOrOne }; var sourceMappingFile = new Option("--source-mapping-file", "Specifies the path to a SourceRootsMappings file.") { Arity = ArgumentArity.ZeroOrOne }; + var unloadCoverletModuleOnly = new Option("--unload-coverlet-module-only", "Specifies Whether or not coverlet will generate a report"){ Arity = ArgumentArity.ZeroOrOne }; RootCommand rootCommand = new() { @@ -73,7 +74,8 @@ static int Main(string[] args) useSourceLink, doesNotReturnAttributes, excludeAssembliesWithoutSources, - sourceMappingFile + sourceMappingFile, + unloadCoverletModuleOnly }; rootCommand.Description = "Cross platform .NET Core code coverage tool"; @@ -102,11 +104,12 @@ static int Main(string[] args) string[] doesNotReturnAttributesValue = context.ParseResult.GetValueForOption(doesNotReturnAttributes); string excludeAssembliesWithoutSourcesValue = context.ParseResult.GetValueForOption(excludeAssembliesWithoutSources); string sourceMappingFileValue = context.ParseResult.GetValueForOption(sourceMappingFile); + bool unloadCoverletModuleOnlyBool = context.ParseResult.GetValueForOption(unloadCoverletModuleOnly); if (string.IsNullOrEmpty(moduleOrAppDirectoryValue) || string.IsNullOrWhiteSpace(moduleOrAppDirectoryValue)) throw new ArgumentException("No test assembly or application directory specified."); - var taskStatus = await HandleCommand(moduleOrAppDirectoryValue, + int taskStatus = await HandleCommand(moduleOrAppDirectoryValue, targetValue, targsValue, outputValue, @@ -127,7 +130,8 @@ static int Main(string[] args) useSourceLinkValue, doesNotReturnAttributesValue, excludeAssembliesWithoutSourcesValue, - sourceMappingFileValue); + sourceMappingFileValue, + unloadCoverletModuleOnlyBool); context.ExitCode = taskStatus; }); @@ -154,7 +158,8 @@ private static Task HandleCommand(string moduleOrAppDirectory, bool useSourceLink, string[] doesNotReturnAttributes, string excludeAssembliesWithoutSources, - string sourceMappingFile + string sourceMappingFile, + bool unloadCoverletModuleOnly ) { @@ -232,6 +237,12 @@ string sourceMappingFile string dOutput = output != null ? output : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); + if (unloadCoverletModuleOnly) + { + int unloadModuleExitCode = coverage.UnloadModule(moduleOrAppDirectory); + return Task.FromResult(unloadModuleExitCode); + } + logger.LogInformation("\nCalculating coverage result..."); CoverageResult result = coverage.GetCoverageResult(); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 15eacff5a..1e4ffe1cf 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -111,7 +111,7 @@ public CoveragePrepareResult PrepareModules() _parameters.IncludeFilters = _parameters.IncludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); IReadOnlyList validModules = _instrumentationHelper.SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters).ToList(); - foreach (var excludedModule in modules.Except(validModules)) + foreach (string excludedModule in modules.Except(validModules)) { _logger.LogVerbose($"Excluded module: '{excludedModule}'"); } @@ -337,10 +337,20 @@ public CoverageResult GetCoverageResult() /// of instrumentation in large scale testing utilising parallelization /// /// - public void UnloadModule(string modulePath) + public int UnloadModule(string modulePath) { - _unloadedModules.Add(modulePath); - _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + try + { + _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + _unloadedModules.Add(modulePath); + } + catch (Exception e) + { + _logger.LogVerbose($"{e.InnerException} occured, module unloading aborted."); + return -1; + } + + return 0; } private bool BranchInCompilerGeneratedClass(string methodName) diff --git a/test/coverlet.integration.tests/DeterministicBuild.cs b/test/coverlet.integration.tests/DeterministicBuild.cs index cdef93c62..e62306522 100644 --- a/test/coverlet.integration.tests/DeterministicBuild.cs +++ b/test/coverlet.integration.tests/DeterministicBuild.cs @@ -300,7 +300,7 @@ private static void DeleteTestIntermediateFiles(string testResultsPath) { if (Directory.Exists(testResultsPath)) { - DirectoryInfo hdDirectory = new DirectoryInfo(testResultsPath); + var hdDirectory = new DirectoryInfo(testResultsPath); // search for directory "In" which has second copy e.g. '_fv-az365-374_2023-10-10_14_26_42\In\fv-az365-374\coverage.json' DirectoryInfo[] intermediateFolder = hdDirectory.GetDirectories("In", SearchOption.AllDirectories); @@ -316,7 +316,7 @@ private static void DeleteLogFiles(string directory) { if (Directory.Exists(directory)) { - DirectoryInfo hdDirectory = new DirectoryInfo(directory); + var hdDirectory = new DirectoryInfo(directory); FileInfo[] filesInDir = hdDirectory.GetFiles("log.*.txt"); foreach (FileInfo foundFile in filesInDir) @@ -344,7 +344,7 @@ private static void DeleteCoverageFiles(string directory) { if (Directory.Exists(directory)) { - DirectoryInfo hdDirectory = new DirectoryInfo(directory); + var hdDirectory = new DirectoryInfo(directory); FileInfo[] filesInDir = hdDirectory.GetFiles("coverage.cobertura.xml"); foreach (FileInfo foundFile in filesInDir) diff --git a/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs b/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs index 4088806b3..72ab4869a 100644 --- a/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs +++ b/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs @@ -1,12 +1,12 @@ // Copyright (c) Toni Solarin-Sodara // Licensed under the MIT license. See LICENSE file in the project root for full license information. -var builder = WebApplication.CreateBuilder(args); +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); -var app = builder.Build(); +WebApplication app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) From 9d4de1090cb87a8c934274876dcbeacebdd50b25 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Mon, 29 Jul 2024 11:09:00 -0400 Subject: [PATCH 07/17] Unrollback change removals to integrate new changes with VSTest Integration --- src/coverlet.collector/DataCollection/CoverageWrapper.cs | 5 +++++ .../Utilities/Interfaces/ICoverageWrapper.cs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/src/coverlet.collector/DataCollection/CoverageWrapper.cs b/src/coverlet.collector/DataCollection/CoverageWrapper.cs index 4e3f5a729..ef4698f6c 100644 --- a/src/coverlet.collector/DataCollection/CoverageWrapper.cs +++ b/src/coverlet.collector/DataCollection/CoverageWrapper.cs @@ -70,5 +70,10 @@ public void PrepareModules(Coverage coverage) { coverage.PrepareModules(); } + + public void UnloadModule(Coverage coverage, string modulePath) + { + coverage.UnloadModule(modulePath); + } } } diff --git a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs index 48410be09..eb1259516 100644 --- a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs @@ -39,5 +39,12 @@ internal interface ICoverageWrapper /// void PrepareModules(Coverage coverage); + /// + /// Unload module in the specified path + /// + /// + /// path of the module to be unloaded + void UnloadModule(Coverage coverage, string modulePath); + } } From 6d2dd57c6968d2b43490ef7b97565e0060b2c7d8 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Mon, 29 Jul 2024 13:31:00 -0400 Subject: [PATCH 08/17] Remove possibly unneeded variables --- src/coverlet.core/Coverage.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 1e4ffe1cf..f25ebe78f 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -57,7 +57,6 @@ internal class Coverage private readonly ICecilSymbolHelper _cecilSymbolHelper; private readonly List _results; private readonly CoverageParameters _parameters; - private readonly List _unloadedModules; public string Identifier { get; } @@ -79,7 +78,6 @@ public Coverage(string moduleOrDirectory, _cecilSymbolHelper = cecilSymbolHelper; Identifier = Guid.NewGuid().ToString(); _results = new List(); - _unloadedModules = new List(); } public Coverage(CoveragePrepareResult prepareResult, @@ -96,7 +94,6 @@ public Coverage(CoveragePrepareResult prepareResult, _instrumentationHelper = instrumentationHelper; _fileSystem = fileSystem; _sourceRootTranslator = sourceRootTranslator; - _unloadedModules = new List(); } public CoveragePrepareResult PrepareModules() @@ -244,10 +241,7 @@ public CoverageResult GetCoverageResult() } modules.Add(Path.GetFileName(result.ModulePath), documents); - if (!_unloadedModules.Contains(result.ModulePath)) - { - UnloadModule(result.ModulePath); - } + UnloadModule(result.ModulePath); } // In case of anonymous delegate compiler generate a custom class and passes it as type.method delegate. @@ -342,7 +336,6 @@ public int UnloadModule(string modulePath) try { _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); - _unloadedModules.Add(modulePath); } catch (Exception e) { From 98f1f752af6c872783bc9a3b45a69c2213614a7a Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Tue, 13 Aug 2024 13:25:03 -0400 Subject: [PATCH 09/17] add unload modules unique specifically for use in coverlet.console --- src/coverlet.console/Program.cs | 14 +++++++------- src/coverlet.core/Coverage.cs | 32 ++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index a84b8fdd6..2c0e1d155 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -49,7 +49,7 @@ static int Main(string[] args) var doesNotReturnAttributes = new Option("--does-not-return-attribute", "Attributes that mark methods that do not return") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var excludeAssembliesWithoutSources = new Option("--exclude-assemblies-without-sources", "Specifies behaviour of heuristic to ignore assemblies with missing source documents.") { Arity = ArgumentArity.ZeroOrOne }; var sourceMappingFile = new Option("--source-mapping-file", "Specifies the path to a SourceRootsMappings file.") { Arity = ArgumentArity.ZeroOrOne }; - var unloadCoverletModuleOnly = new Option("--unload-coverlet-module-only", "Specifies Whether or not coverlet will generate a report"){ Arity = ArgumentArity.ZeroOrOne }; + var unloadCoverletFromModulesOnly = new Option("--unload-coverlet-from-modules-only", "Specifies Whether or not coverlet will only unload after unit tests are finished"){ Arity = ArgumentArity.ZeroOrOne }; RootCommand rootCommand = new() { @@ -75,7 +75,7 @@ static int Main(string[] args) doesNotReturnAttributes, excludeAssembliesWithoutSources, sourceMappingFile, - unloadCoverletModuleOnly + unloadCoverletFromModulesOnly }; rootCommand.Description = "Cross platform .NET Core code coverage tool"; @@ -104,7 +104,7 @@ static int Main(string[] args) string[] doesNotReturnAttributesValue = context.ParseResult.GetValueForOption(doesNotReturnAttributes); string excludeAssembliesWithoutSourcesValue = context.ParseResult.GetValueForOption(excludeAssembliesWithoutSources); string sourceMappingFileValue = context.ParseResult.GetValueForOption(sourceMappingFile); - bool unloadCoverletModuleOnlyBool = context.ParseResult.GetValueForOption(unloadCoverletModuleOnly); + bool unloadCoverletFromModulesOnlyBool = context.ParseResult.GetValueForOption(unloadCoverletFromModulesOnly); if (string.IsNullOrEmpty(moduleOrAppDirectoryValue) || string.IsNullOrWhiteSpace(moduleOrAppDirectoryValue)) throw new ArgumentException("No test assembly or application directory specified."); @@ -131,7 +131,7 @@ static int Main(string[] args) doesNotReturnAttributesValue, excludeAssembliesWithoutSourcesValue, sourceMappingFileValue, - unloadCoverletModuleOnlyBool); + unloadCoverletFromModulesOnlyBool); context.ExitCode = taskStatus; }); @@ -159,7 +159,7 @@ private static Task HandleCommand(string moduleOrAppDirectory, string[] doesNotReturnAttributes, string excludeAssembliesWithoutSources, string sourceMappingFile, - bool unloadCoverletModuleOnly + bool unloadCoverletFromModulesOnly ) { @@ -237,9 +237,9 @@ bool unloadCoverletModuleOnly string dOutput = output != null ? output : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); - if (unloadCoverletModuleOnly) + if (unloadCoverletFromModulesOnly) { - int unloadModuleExitCode = coverage.UnloadModule(moduleOrAppDirectory); + int unloadModuleExitCode = coverage.UnloadModule(); return Task.FromResult(unloadModuleExitCode); } diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index f25ebe78f..ddcc46437 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -326,12 +326,39 @@ public CoverageResult GetCoverageResult() return coverageResult; } + /// + /// unloads all modules that were instrumented + /// + /// exit code of module unloading + public int UnloadModule() + { + string[] modules = _instrumentationHelper.GetCoverableModules(_moduleOrAppDirectory, + _parameters.IncludeDirectories, _parameters.IncludeTestAssembly); + + IReadOnlyList validModules = _instrumentationHelper + .SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters).ToList(); + foreach (string modulePath in validModules) { + try + { + _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + } + catch (Exception e) + { + _logger.LogVerbose($"{e.InnerException} occured, module unloading aborted."); + return -1; + } + } + + return 0; + } + /// /// Invoke the unloading of modules and restoration of the original assembly files, made public to allow unloading /// of instrumentation in large scale testing utilising parallelization /// /// - public int UnloadModule(string modulePath) + /// exist code of unloading modules + public void UnloadModule(string modulePath) { try { @@ -340,10 +367,7 @@ public int UnloadModule(string modulePath) catch (Exception e) { _logger.LogVerbose($"{e.InnerException} occured, module unloading aborted."); - return -1; } - - return 0; } private bool BranchInCompilerGeneratedClass(string methodName) From cbe14ccf6e25ba04c84339038927edaa4683d8dc Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Tue, 13 Aug 2024 15:19:14 -0400 Subject: [PATCH 10/17] Revert refactorings caught by Geotab Analyzers --- src/coverlet.console/Program.cs | 4 ++-- src/coverlet.core/Coverage.cs | 2 +- test/coverlet.integration.tests/DeterministicBuild.cs | 6 +++--- test/coverlet.tests.projectsample.aspmvcrazor/Program.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 2c0e1d155..dec938314 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -34,7 +34,7 @@ static int Main(string[] args) var verbosity = new Option(new[] { "--verbosity", "-v" }, () => LogLevel.Normal, "Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed.") { Arity = ArgumentArity.ZeroOrOne }; var formats = new Option(new[] { "--format", "-f" }, () => new[] { "json" }, "Format of the generated coverage report.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var threshold = new Option("--threshold", "Exits with error if the coverage % is below value.") { Arity = ArgumentArity.ZeroOrOne }; - Option> thresholdTypes = new Option>("--threshold-type", () => new List(new string[] { "line", "branch", "method" }), "Coverage type to apply the threshold to.").FromAmong("line", "branch", "method"); + var thresholdTypes = new Option>("--threshold-type", () => new List(new string[] { "line", "branch", "method" }), "Coverage type to apply the threshold to.").FromAmong("line", "branch", "method"); var thresholdStat = new Option("--threshold-stat", () => ThresholdStatistic.Minimum, "Coverage statistic used to enforce the threshold value.") { Arity = ArgumentArity.ZeroOrOne }; var excludeFilters = new Option("--exclude", "Filter expressions to exclude specific modules and types.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var includeFilters = new Option("--include", "Filter expressions to include only specific modules and types.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; @@ -109,7 +109,7 @@ static int Main(string[] args) if (string.IsNullOrEmpty(moduleOrAppDirectoryValue) || string.IsNullOrWhiteSpace(moduleOrAppDirectoryValue)) throw new ArgumentException("No test assembly or application directory specified."); - int taskStatus = await HandleCommand(moduleOrAppDirectoryValue, + var taskStatus = await HandleCommand(moduleOrAppDirectoryValue, targetValue, targsValue, outputValue, diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index ddcc46437..0b89220b8 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -108,7 +108,7 @@ public CoveragePrepareResult PrepareModules() _parameters.IncludeFilters = _parameters.IncludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); IReadOnlyList validModules = _instrumentationHelper.SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters).ToList(); - foreach (string excludedModule in modules.Except(validModules)) + foreach (var excludedModule in modules.Except(validModules)) { _logger.LogVerbose($"Excluded module: '{excludedModule}'"); } diff --git a/test/coverlet.integration.tests/DeterministicBuild.cs b/test/coverlet.integration.tests/DeterministicBuild.cs index e62306522..cdef93c62 100644 --- a/test/coverlet.integration.tests/DeterministicBuild.cs +++ b/test/coverlet.integration.tests/DeterministicBuild.cs @@ -300,7 +300,7 @@ private static void DeleteTestIntermediateFiles(string testResultsPath) { if (Directory.Exists(testResultsPath)) { - var hdDirectory = new DirectoryInfo(testResultsPath); + DirectoryInfo hdDirectory = new DirectoryInfo(testResultsPath); // search for directory "In" which has second copy e.g. '_fv-az365-374_2023-10-10_14_26_42\In\fv-az365-374\coverage.json' DirectoryInfo[] intermediateFolder = hdDirectory.GetDirectories("In", SearchOption.AllDirectories); @@ -316,7 +316,7 @@ private static void DeleteLogFiles(string directory) { if (Directory.Exists(directory)) { - var hdDirectory = new DirectoryInfo(directory); + DirectoryInfo hdDirectory = new DirectoryInfo(directory); FileInfo[] filesInDir = hdDirectory.GetFiles("log.*.txt"); foreach (FileInfo foundFile in filesInDir) @@ -344,7 +344,7 @@ private static void DeleteCoverageFiles(string directory) { if (Directory.Exists(directory)) { - var hdDirectory = new DirectoryInfo(directory); + DirectoryInfo hdDirectory = new DirectoryInfo(directory); FileInfo[] filesInDir = hdDirectory.GetFiles("coverage.cobertura.xml"); foreach (FileInfo foundFile in filesInDir) diff --git a/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs b/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs index 72ab4869a..4088806b3 100644 --- a/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs +++ b/test/coverlet.tests.projectsample.aspmvcrazor/Program.cs @@ -1,12 +1,12 @@ // Copyright (c) Toni Solarin-Sodara // Licensed under the MIT license. See LICENSE file in the project root for full license information. -WebApplicationBuilder builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); -WebApplication app = builder.Build(); +var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) From c3905a17e488d2cc5a000cb3713f162f726ae948 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Fri, 16 Aug 2024 15:26:06 -0400 Subject: [PATCH 11/17] Add unit tests to new methods --- src/coverlet.console/Program.cs | 2 +- src/coverlet.core/Coverage.cs | 14 ++-- .../Coverage/CoverageTests.cs | 71 +++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index dec938314..b0a48d2a6 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -239,7 +239,7 @@ bool unloadCoverletFromModulesOnly if (unloadCoverletFromModulesOnly) { - int unloadModuleExitCode = coverage.UnloadModule(); + int unloadModuleExitCode = coverage.UnloadModules(); return Task.FromResult(unloadModuleExitCode); } diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 0b89220b8..70809cd6a 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -330,17 +330,19 @@ public CoverageResult GetCoverageResult() /// unloads all modules that were instrumented /// /// exit code of module unloading - public int UnloadModule() + public int UnloadModules() { string[] modules = _instrumentationHelper.GetCoverableModules(_moduleOrAppDirectory, _parameters.IncludeDirectories, _parameters.IncludeTestAssembly); - IReadOnlyList validModules = _instrumentationHelper - .SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters).ToList(); - foreach (string modulePath in validModules) { + var validModules = _instrumentationHelper + .SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters); + var validModulesAsList = validModules.ToList(); + foreach (string modulePath in validModulesAsList) { try { _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + _logger.LogVerbose("All Modules unloaded."); } catch (Exception e) { @@ -353,8 +355,7 @@ public int UnloadModule() } /// - /// Invoke the unloading of modules and restoration of the original assembly files, made public to allow unloading - /// of instrumentation in large scale testing utilising parallelization + /// Invoke the unloading of modules and restoration of the original assembly files /// /// /// exist code of unloading modules @@ -363,6 +364,7 @@ public void UnloadModule(string modulePath) try { _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + _logger.LogVerbose($"Module at {modulePath} is unloaded."); } catch (Exception e) { diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs index 701df8f30..31a6f509e 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs @@ -177,6 +177,77 @@ public void TestCoverageMergeWithWrongParameter() directory.Delete(true); } + + [Fact] + public void TestCoverageUnloadWithParameters() + { + string module = GetType().Assembly.Location; + string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); + + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + + File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); + File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); + + var mockInstrumentationHelper = new Mock(); + + var parameters = new CoverageParameters + { + IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, + IncludeDirectories = Array.Empty(), + ExcludeFilters = Array.Empty(), + ExcludedSourceFiles = Array.Empty(), + ExcludeAttributes = Array.Empty(), + IncludeTestAssembly = false, + SingleHit = false, + MergeWith = string.Empty, + UseSourceLink = false + }; + + var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, mockInstrumentationHelper.Object, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + coverage.PrepareModules(); + coverage.UnloadModule(Path.Combine(directory.FullName, Path.GetFileName(module))); + + mockInstrumentationHelper.Verify(i => i.RestoreOriginalModule(It.Is(v => v.Equals(Path.Combine(directory.FullName, Path.GetFileName(module)))), It.IsAny()), Times.Once); + _mockLogger.Verify(l => l.LogVerbose(It.Is(v => v.Equals($"Module at {Path.Combine(directory.FullName, Path.GetFileName(module))} is unloaded."))), Times.Once); + } + + [Fact] + public void TestCoverageUnloadWithNoParameters() + { + string module = GetType().Assembly.Location; + string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); + + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + + File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); + File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); + + var mockInstrumentationHelper = new Mock(); + mockInstrumentationHelper + .Setup(x => x.SelectModules(It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns(new List(){"ModuleX"}); + + var parameters = new CoverageParameters + { + IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, + IncludeDirectories = Array.Empty(), + ExcludeFilters = Array.Empty(), + ExcludedSourceFiles = Array.Empty(), + ExcludeAttributes = Array.Empty(), + IncludeTestAssembly = false, + SingleHit = false, + MergeWith = string.Empty, + UseSourceLink = false + }; + + var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, mockInstrumentationHelper.Object, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + coverage.PrepareModules(); + coverage.UnloadModules(); + + mockInstrumentationHelper.Verify(i => i.RestoreOriginalModule(It.Is(v => v.Equals("ModuleX")), It.IsAny()), Times.Once); + _mockLogger.Verify(l => l.LogVerbose(It.Is(v => v.Equals("All Modules unloaded."))), Times.Once); + } } } From c54c5cf617c8864953c3cb15a3e6075293f71eb3 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Fri, 16 Aug 2024 15:42:41 -0400 Subject: [PATCH 12/17] Adjust arguement name to trigger the skip of coverage calculation --- src/coverlet.console/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index b0a48d2a6..5ecd4c1a8 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -49,7 +49,7 @@ static int Main(string[] args) var doesNotReturnAttributes = new Option("--does-not-return-attribute", "Attributes that mark methods that do not return") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var excludeAssembliesWithoutSources = new Option("--exclude-assemblies-without-sources", "Specifies behaviour of heuristic to ignore assemblies with missing source documents.") { Arity = ArgumentArity.ZeroOrOne }; var sourceMappingFile = new Option("--source-mapping-file", "Specifies the path to a SourceRootsMappings file.") { Arity = ArgumentArity.ZeroOrOne }; - var unloadCoverletFromModulesOnly = new Option("--unload-coverlet-from-modules-only", "Specifies Whether or not coverlet will only unload after unit tests are finished"){ Arity = ArgumentArity.ZeroOrOne }; + var unloadCoverletFromModulesOnly = new Option("---only-unload-modules", "Specifies Whether or not coverlet will only unload after unit tests are finished and skip coverage calculation"){ Arity = ArgumentArity.ZeroOrOne }; RootCommand rootCommand = new() { From 25feb244c8d63b7da284a3ba930cfb47c464b468 Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Fri, 16 Aug 2024 15:44:10 -0400 Subject: [PATCH 13/17] Revert changes to CoverageWrapper.cs and ICoverageWrapper.cs --- src/coverlet.collector/DataCollection/CoverageWrapper.cs | 5 ----- .../Utilities/Interfaces/ICoverageWrapper.cs | 7 ------- 2 files changed, 12 deletions(-) diff --git a/src/coverlet.collector/DataCollection/CoverageWrapper.cs b/src/coverlet.collector/DataCollection/CoverageWrapper.cs index ef4698f6c..4e3f5a729 100644 --- a/src/coverlet.collector/DataCollection/CoverageWrapper.cs +++ b/src/coverlet.collector/DataCollection/CoverageWrapper.cs @@ -70,10 +70,5 @@ public void PrepareModules(Coverage coverage) { coverage.PrepareModules(); } - - public void UnloadModule(Coverage coverage, string modulePath) - { - coverage.UnloadModule(modulePath); - } } } diff --git a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs index eb1259516..48410be09 100644 --- a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs +++ b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs @@ -39,12 +39,5 @@ internal interface ICoverageWrapper /// void PrepareModules(Coverage coverage); - /// - /// Unload module in the specified path - /// - /// - /// path of the module to be unloaded - void UnloadModule(Coverage coverage, string modulePath); - } } From d9621e2cdf7aa1b91ce24372e3539ba212045d4a Mon Sep 17 00:00:00 2001 From: Joseph Sun <90574040+JosephSun2003@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:57:12 -0400 Subject: [PATCH 14/17] Revert (#3) * Revert "Revert changes to CoverageWrapper.cs and ICoverageWrapper.cs" This reverts commit 25feb244c8d63b7da284a3ba930cfb47c464b468. * Revert "Adjust arguement name to trigger the skip of coverage calculation" This reverts commit c54c5cf617c8864953c3cb15a3e6075293f71eb3. * Revert "Add unit tests to new methods" This reverts commit c3905a17e488d2cc5a000cb3713f162f726ae948. * Revert "Revert refactorings caught by Geotab Analyzers" This reverts commit cbe14ccf6e25ba04c84339038927edaa4683d8dc. * Revert "add unload modules unique specifically for use in coverlet.console" This reverts commit 98f1f752af6c872783bc9a3b45a69c2213614a7a. * Revert "Remove possibly unneeded variables" This reverts commit 6d2dd57c6968d2b43490ef7b97565e0060b2c7d8. * Revert "Unrollback change removals to integrate new changes with VSTest Integration" This reverts commit 9d4de1090cb87a8c934274876dcbeacebdd50b25. * Revert "Refactor explicit and implicit variable usage to comply with dotnet code style rules" This reverts commit e67ba6989b8859cebfa2329d63a977cffaa73e12. * Revert "Add minor documentation corrections and remove unneeded changes" This reverts commit da1257e0d069ef5f637aaf7ccd1847ee881113c3. * Revert "Update documentation to clarify the modifications made and add some basic code clean up" This reverts commit 0dfef6f2b5b5c65e0dd297f5d9fa5fdfb72dcc0b. * Revert "Fix problems encountered with newly added method and variables from unit testing with existing tests" This reverts commit dcc3c29292c66a1f7f67d31d3aaf5c9b37b46ca2. * Revert "Add new method to manually call the unloading of modules" This reverts commit 88a5782f85207e7e4887a6b98e099211de479dd3. --------- Co-authored-by: Joseph Sun --- src/coverlet.console/Program.cs | 18 ++--- src/coverlet.core/Coverage.cs | 48 +------------ .../Coverage/CoverageTests.cs | 71 ------------------- 3 files changed, 5 insertions(+), 132 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 5ecd4c1a8..864df117f 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -49,7 +49,6 @@ static int Main(string[] args) var doesNotReturnAttributes = new Option("--does-not-return-attribute", "Attributes that mark methods that do not return") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var excludeAssembliesWithoutSources = new Option("--exclude-assemblies-without-sources", "Specifies behaviour of heuristic to ignore assemblies with missing source documents.") { Arity = ArgumentArity.ZeroOrOne }; var sourceMappingFile = new Option("--source-mapping-file", "Specifies the path to a SourceRootsMappings file.") { Arity = ArgumentArity.ZeroOrOne }; - var unloadCoverletFromModulesOnly = new Option("---only-unload-modules", "Specifies Whether or not coverlet will only unload after unit tests are finished and skip coverage calculation"){ Arity = ArgumentArity.ZeroOrOne }; RootCommand rootCommand = new() { @@ -74,8 +73,7 @@ static int Main(string[] args) useSourceLink, doesNotReturnAttributes, excludeAssembliesWithoutSources, - sourceMappingFile, - unloadCoverletFromModulesOnly + sourceMappingFile }; rootCommand.Description = "Cross platform .NET Core code coverage tool"; @@ -104,7 +102,6 @@ static int Main(string[] args) string[] doesNotReturnAttributesValue = context.ParseResult.GetValueForOption(doesNotReturnAttributes); string excludeAssembliesWithoutSourcesValue = context.ParseResult.GetValueForOption(excludeAssembliesWithoutSources); string sourceMappingFileValue = context.ParseResult.GetValueForOption(sourceMappingFile); - bool unloadCoverletFromModulesOnlyBool = context.ParseResult.GetValueForOption(unloadCoverletFromModulesOnly); if (string.IsNullOrEmpty(moduleOrAppDirectoryValue) || string.IsNullOrWhiteSpace(moduleOrAppDirectoryValue)) throw new ArgumentException("No test assembly or application directory specified."); @@ -130,8 +127,7 @@ static int Main(string[] args) useSourceLinkValue, doesNotReturnAttributesValue, excludeAssembliesWithoutSourcesValue, - sourceMappingFileValue, - unloadCoverletFromModulesOnlyBool); + sourceMappingFileValue); context.ExitCode = taskStatus; }); @@ -158,8 +154,7 @@ private static Task HandleCommand(string moduleOrAppDirectory, bool useSourceLink, string[] doesNotReturnAttributes, string excludeAssembliesWithoutSources, - string sourceMappingFile, - bool unloadCoverletFromModulesOnly + string sourceMappingFile ) { @@ -237,12 +232,6 @@ bool unloadCoverletFromModulesOnly string dOutput = output != null ? output : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); - if (unloadCoverletFromModulesOnly) - { - int unloadModuleExitCode = coverage.UnloadModules(); - return Task.FromResult(unloadModuleExitCode); - } - logger.LogInformation("\nCalculating coverage result..."); CoverageResult result = coverage.GetCoverageResult(); @@ -396,6 +385,7 @@ bool unloadCoverletFromModulesOnly return Task.FromResult(exitCode); + } catch (Win32Exception we) when (we.Source == "System.Diagnostics.Process") diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 70809cd6a..07c0297fa 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -241,7 +241,7 @@ public CoverageResult GetCoverageResult() } modules.Add(Path.GetFileName(result.ModulePath), documents); - UnloadModule(result.ModulePath); + _instrumentationHelper.RestoreOriginalModule(result.ModulePath, Identifier); } // In case of anonymous delegate compiler generate a custom class and passes it as type.method delegate. @@ -326,52 +326,6 @@ public CoverageResult GetCoverageResult() return coverageResult; } - /// - /// unloads all modules that were instrumented - /// - /// exit code of module unloading - public int UnloadModules() - { - string[] modules = _instrumentationHelper.GetCoverableModules(_moduleOrAppDirectory, - _parameters.IncludeDirectories, _parameters.IncludeTestAssembly); - - var validModules = _instrumentationHelper - .SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters); - var validModulesAsList = validModules.ToList(); - foreach (string modulePath in validModulesAsList) { - try - { - _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); - _logger.LogVerbose("All Modules unloaded."); - } - catch (Exception e) - { - _logger.LogVerbose($"{e.InnerException} occured, module unloading aborted."); - return -1; - } - } - - return 0; - } - - /// - /// Invoke the unloading of modules and restoration of the original assembly files - /// - /// - /// exist code of unloading modules - public void UnloadModule(string modulePath) - { - try - { - _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); - _logger.LogVerbose($"Module at {modulePath} is unloaded."); - } - catch (Exception e) - { - _logger.LogVerbose($"{e.InnerException} occured, module unloading aborted."); - } - } - private bool BranchInCompilerGeneratedClass(string methodName) { foreach (InstrumenterResult instrumentedResult in _results) diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs index 31a6f509e..701df8f30 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs @@ -177,77 +177,6 @@ public void TestCoverageMergeWithWrongParameter() directory.Delete(true); } - - [Fact] - public void TestCoverageUnloadWithParameters() - { - string module = GetType().Assembly.Location; - string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); - - DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); - - File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); - File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); - - var mockInstrumentationHelper = new Mock(); - - var parameters = new CoverageParameters - { - IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, - IncludeDirectories = Array.Empty(), - ExcludeFilters = Array.Empty(), - ExcludedSourceFiles = Array.Empty(), - ExcludeAttributes = Array.Empty(), - IncludeTestAssembly = false, - SingleHit = false, - MergeWith = string.Empty, - UseSourceLink = false - }; - - var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, mockInstrumentationHelper.Object, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); - coverage.PrepareModules(); - coverage.UnloadModule(Path.Combine(directory.FullName, Path.GetFileName(module))); - - mockInstrumentationHelper.Verify(i => i.RestoreOriginalModule(It.Is(v => v.Equals(Path.Combine(directory.FullName, Path.GetFileName(module)))), It.IsAny()), Times.Once); - _mockLogger.Verify(l => l.LogVerbose(It.Is(v => v.Equals($"Module at {Path.Combine(directory.FullName, Path.GetFileName(module))} is unloaded."))), Times.Once); - } - - [Fact] - public void TestCoverageUnloadWithNoParameters() - { - string module = GetType().Assembly.Location; - string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); - - DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); - - File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); - File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); - - var mockInstrumentationHelper = new Mock(); - mockInstrumentationHelper - .Setup(x => x.SelectModules(It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(new List(){"ModuleX"}); - - var parameters = new CoverageParameters - { - IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, - IncludeDirectories = Array.Empty(), - ExcludeFilters = Array.Empty(), - ExcludedSourceFiles = Array.Empty(), - ExcludeAttributes = Array.Empty(), - IncludeTestAssembly = false, - SingleHit = false, - MergeWith = string.Empty, - UseSourceLink = false - }; - - var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, mockInstrumentationHelper.Object, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); - coverage.PrepareModules(); - coverage.UnloadModules(); - - mockInstrumentationHelper.Verify(i => i.RestoreOriginalModule(It.Is(v => v.Equals("ModuleX")), It.IsAny()), Times.Once); - _mockLogger.Verify(l => l.LogVerbose(It.Is(v => v.Equals("All Modules unloaded."))), Times.Once); - } } } From ca9fed55abd851d256105d0f41df2248b695c7fe Mon Sep 17 00:00:00 2001 From: Joseph Sun Date: Fri, 16 Aug 2024 16:00:02 -0400 Subject: [PATCH 15/17] Revert "Revert (#3)" This reverts commit d9621e2cdf7aa1b91ce24372e3539ba212045d4a. --- src/coverlet.console/Program.cs | 17 ++++- src/coverlet.core/Coverage.cs | 48 +++++++++++- .../Coverage/CoverageTests.cs | 73 ++++++++++++++++++- 3 files changed, 133 insertions(+), 5 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index c97942bbf..52659c8c4 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -49,6 +49,7 @@ static int Main(string[] args) var doesNotReturnAttributes = new Option("--does-not-return-attribute", "Attributes that mark methods that do not return") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; var excludeAssembliesWithoutSources = new Option("--exclude-assemblies-without-sources", "Specifies behavior of heuristic to ignore assemblies with missing source documents.") { Arity = ArgumentArity.ZeroOrOne }; var sourceMappingFile = new Option("--source-mapping-file", "Specifies the path to a SourceRootsMappings file.") { Arity = ArgumentArity.ZeroOrOne }; + var unloadCoverletFromModulesOnly = new Option("---only-unload-modules", "Specifies Whether or not coverlet will only unload after unit tests are finished and skip coverage calculation"){ Arity = ArgumentArity.ZeroOrOne }; RootCommand rootCommand = new() { @@ -73,7 +74,8 @@ static int Main(string[] args) useSourceLink, doesNotReturnAttributes, excludeAssembliesWithoutSources, - sourceMappingFile + sourceMappingFile, + unloadCoverletFromModulesOnly }; rootCommand.Description = "Cross platform .NET Core code coverage tool"; @@ -102,6 +104,7 @@ static int Main(string[] args) string[] doesNotReturnAttributesValue = context.ParseResult.GetValueForOption(doesNotReturnAttributes); string excludeAssembliesWithoutSourcesValue = context.ParseResult.GetValueForOption(excludeAssembliesWithoutSources); string sourceMappingFileValue = context.ParseResult.GetValueForOption(sourceMappingFile); + bool unloadCoverletFromModulesOnlyBool = context.ParseResult.GetValueForOption(unloadCoverletFromModulesOnly); if (string.IsNullOrEmpty(moduleOrAppDirectoryValue) || string.IsNullOrWhiteSpace(moduleOrAppDirectoryValue)) throw new ArgumentException("No test assembly or application directory specified."); @@ -127,7 +130,8 @@ static int Main(string[] args) useSourceLinkValue, doesNotReturnAttributesValue, excludeAssembliesWithoutSourcesValue, - sourceMappingFileValue); + sourceMappingFileValue, + unloadCoverletFromModulesOnlyBool); context.ExitCode = taskStatus; }); @@ -154,7 +158,8 @@ private static Task HandleCommand(string moduleOrAppDirectory, bool useSourceLink, string[] doesNotReturnAttributes, string excludeAssembliesWithoutSources, - string sourceMappingFile + string sourceMappingFile, + bool unloadCoverletFromModulesOnly ) { @@ -232,6 +237,12 @@ string sourceMappingFile string dOutput = output != null ? output : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); + if (unloadCoverletFromModulesOnly) + { + int unloadModuleExitCode = coverage.UnloadModules(); + return Task.FromResult(unloadModuleExitCode); + } + logger.LogInformation("\nCalculating coverage result..."); CoverageResult result = coverage.GetCoverageResult(); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 241667a0b..72270d744 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -241,7 +241,7 @@ public CoverageResult GetCoverageResult() } modules.Add(Path.GetFileName(result.ModulePath), documents); - _instrumentationHelper.RestoreOriginalModule(result.ModulePath, Identifier); + UnloadModule(result.ModulePath); } // In case of anonymous delegate compiler generate a custom class and passes it as type.method delegate. @@ -324,6 +324,52 @@ public CoverageResult GetCoverageResult() return coverageResult; } + /// + /// unloads all modules that were instrumented + /// + /// exit code of module unloading + public int UnloadModules() + { + string[] modules = _instrumentationHelper.GetCoverableModules(_moduleOrAppDirectory, + _parameters.IncludeDirectories, _parameters.IncludeTestAssembly); + + var validModules = _instrumentationHelper + .SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters); + var validModulesAsList = validModules.ToList(); + foreach (string modulePath in validModulesAsList) { + try + { + _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + _logger.LogVerbose("All Modules unloaded."); + } + catch (Exception e) + { + _logger.LogVerbose($"{e.InnerException} occured, module unloading aborted."); + return -1; + } + } + + return 0; + } + + /// + /// Invoke the unloading of modules and restoration of the original assembly files + /// + /// + /// exist code of unloading modules + public void UnloadModule(string modulePath) + { + try + { + _instrumentationHelper.RestoreOriginalModule(modulePath, Identifier); + _logger.LogVerbose($"Module at {modulePath} is unloaded."); + } + catch (Exception e) + { + _logger.LogVerbose($"{e.InnerException} occured, module unloading aborted."); + } + } + private bool BranchInCompilerGeneratedClass(string methodName) { foreach (InstrumenterResult instrumentedResult in _results) diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs index d5a73e90b..8447f90ab 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs @@ -219,10 +219,81 @@ public void GetSourceLinkUrl_ReturnsOriginalDocument_WhenNoMatch() // Assert Assert.Equal("other/coverlet.core/Coverage.cs", result); } + + [Fact] + public void TestCoverageUnloadWithParameters() + { + string module = GetType().Assembly.Location; + string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); + + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + + File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); + File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); + + var mockInstrumentationHelper = new Mock(); + + var parameters = new CoverageParameters + { + IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, + IncludeDirectories = Array.Empty(), + ExcludeFilters = Array.Empty(), + ExcludedSourceFiles = Array.Empty(), + ExcludeAttributes = Array.Empty(), + IncludeTestAssembly = false, + SingleHit = false, + MergeWith = string.Empty, + UseSourceLink = false + }; + + var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, mockInstrumentationHelper.Object, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + coverage.PrepareModules(); + coverage.UnloadModule(Path.Combine(directory.FullName, Path.GetFileName(module))); + + mockInstrumentationHelper.Verify(i => i.RestoreOriginalModule(It.Is(v => v.Equals(Path.Combine(directory.FullName, Path.GetFileName(module)))), It.IsAny()), Times.Once); + _mockLogger.Verify(l => l.LogVerbose(It.Is(v => v.Equals($"Module at {Path.Combine(directory.FullName, Path.GetFileName(module))} is unloaded."))), Times.Once); + } + + [Fact] + public void TestCoverageUnloadWithNoParameters() + { + string module = GetType().Assembly.Location; + string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); + + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + + File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); + File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); + + var mockInstrumentationHelper = new Mock(); + mockInstrumentationHelper + .Setup(x => x.SelectModules(It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns(new List(){"ModuleX"}); + + var parameters = new CoverageParameters + { + IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, + IncludeDirectories = Array.Empty(), + ExcludeFilters = Array.Empty(), + ExcludedSourceFiles = Array.Empty(), + ExcludeAttributes = Array.Empty(), + IncludeTestAssembly = false, + SingleHit = false, + MergeWith = string.Empty, + UseSourceLink = false + }; + + var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, mockInstrumentationHelper.Object, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + coverage.PrepareModules(); + coverage.UnloadModules(); + + mockInstrumentationHelper.Verify(i => i.RestoreOriginalModule(It.Is(v => v.Equals("ModuleX")), It.IsAny()), Times.Once); + _mockLogger.Verify(l => l.LogVerbose(It.Is(v => v.Equals("All Modules unloaded."))), Times.Once); + } } } -public class BranchDictionaryConverter : JsonConverter +public class BranchDictionaryConverter: JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { From 2a72fe55cdcdda88a515ff07790c066e0d36ee28 Mon Sep 17 00:00:00 2001 From: sunjosep Date: Mon, 19 Aug 2024 17:53:17 -0400 Subject: [PATCH 16/17] Possible fix to unit test failure --- test/coverlet.core.tests/Coverage/CoverageTests.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs index 8447f90ab..5ea9e0ef2 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs @@ -232,6 +232,7 @@ public void TestCoverageUnloadWithParameters() File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); var mockInstrumentationHelper = new Mock(); + mockInstrumentationHelper.Setup(x => x.RestoreOriginalModule(It.IsAny(), It.IsAny())); var parameters = new CoverageParameters { @@ -266,9 +267,10 @@ public void TestCoverageUnloadWithNoParameters() File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); var mockInstrumentationHelper = new Mock(); - mockInstrumentationHelper - .Setup(x => x.SelectModules(It.IsAny>(), It.IsAny(), It.IsAny())) - .Returns(new List(){"ModuleX"}); + mockInstrumentationHelper + .Setup(x => x.SelectModules(It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns(new List(){"ModuleX"}); + mockInstrumentationHelper.Setup(x => x.RestoreOriginalModule(It.IsAny(), It.IsAny())); var parameters = new CoverageParameters { From dc6b8cffec3ff1a6673ef23ffb3b0d0bcfe1c3a5 Mon Sep 17 00:00:00 2001 From: sunjosep Date: Mon, 3 Feb 2025 08:53:54 -0500 Subject: [PATCH 17/17] Add back in space --- test/coverlet.core.tests/Coverage/CoverageTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs index 5ea9e0ef2..87f5544e3 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs @@ -295,7 +295,7 @@ public void TestCoverageUnloadWithNoParameters() } } -public class BranchDictionaryConverter: JsonConverter +public class BranchDictionaryConverter : JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {