diff --git a/.editorconfig b/.editorconfig index 8e51a965d..0ec829371 100644 --- a/.editorconfig +++ b/.editorconfig @@ -227,3 +227,4 @@ csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent +dotnet_diagnostic.IDE0007.severity = suggestion diff --git a/.gitignore b/.gitignore index 514880624..2179436d8 100644 --- a/.gitignore +++ b/.gitignore @@ -318,3 +318,4 @@ FolderProfile.pubxml /NuGet.config nuget.config *.dmp +Playground*/ diff --git a/Directory.Build.props b/Directory.Build.props index 0b0e187d0..aa2af0cc4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,7 +15,7 @@ false true 12.0 - $(NoWarn);NU1507;NU5105;CS1591 + $(NoWarn);NU1507;NU5105;CS1591;NU1608;NU1900 true https://api.nuget.org/v3/index.json; diff --git a/Directory.Packages.props b/Directory.Packages.props index eba94e254..eff914526 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,7 +12,7 @@ 4.12.0 17.13.0 - 6.13.2 + 6.14.0 2.0.0 3.0.2 @@ -48,24 +48,25 @@ - + - + - - + + + - + - + diff --git a/Documentation/GlobalTool.md b/Documentation/GlobalTool.md index e91f67c9d..b8db7faa9 100644 --- a/Documentation/GlobalTool.md +++ b/Documentation/GlobalTool.md @@ -38,7 +38,7 @@ Options: --merge-with Path to existing coverage result to merge. --use-source-link Specifies whether to use SourceLink URIs in place of file system paths. --does-not-return-attribute Attributes that mark methods that do not return - --exclude-assemblies-without-sources Specifies behaviour of heuristic to ignore assemblies with missing source documents. + --exclude-assemblies-without-sources Specifies behavior of heuristic to ignore assemblies with missing source documents. [default: MissingAll] --source-mapping-file Specifies the path to a SourceRootsMappings file. --version Show version information -?, -h, --help Show help and usage information @@ -275,5 +275,4 @@ Coverlet outputs specific exit codes to better support build automation systems 2 - Coverage percentage is below threshold. 3 - Test fails and also coverage percentage is below threshold. 101 - General exception occurred during coverlet process. -102 - Missing options or invalid arguments for coverlet process. ``` diff --git a/eng/azure-pipelines-nightly.yml b/eng/azure-pipelines-nightly.yml index ab78bae71..4da015bf2 100644 --- a/eng/azure-pipelines-nightly.yml +++ b/eng/azure-pipelines-nightly.yml @@ -10,11 +10,14 @@ steps: - task: UseDotNet@2 inputs: useGlobalJson: true - displayName: Install .NET Core SDK 8.0.113 + displayName: Install .NET Core SDK 8.0.114 - task: NuGetAuthenticate@1 displayName: Authenticate with NuGet feeds +- script: dotnet restore -s "https://api.nuget.org/v3/index.json" + displayName: Restore packages + - script: dotnet pack -c Release /p:PublicRelease=false displayName: Create NuGet packages diff --git a/eng/build.yml b/eng/build.yml index 570bd0f2c..c734efcf2 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -7,7 +7,7 @@ steps: - task: UseDotNet@2 inputs: useGlobalJson: true - displayName: Install .NET Core SDK 8.0.113 + displayName: Install .NET Core SDK 8.0.114 # create artifact/package folder - pwsh: | @@ -15,9 +15,23 @@ steps: New-Item -ItemType Directory -Path artifacts/package/release -Force displayName: create folder artifacts/package/$(BuildConfiguration) -- script: dotnet restore +# Authenticate Azure DevOps NuGet feed +- task: NuGetAuthenticate@1 + displayName: 'Authenticate Azure DevOps NuGet feed' + inputs: + forceReinstallCredentialProvider: true + +- script: dotnet restore -v n -s "https://api.nuget.org/v3/index.json" displayName: Restore packages +# - task: DotNetCoreCLI@2 +# displayName: Restore packages +# inputs: +# command: restore +# projects: 'coverlet.sln' +# feedsToUse: 'config' +# nugetConfigPath: 'nuget.config' + - script: dotnet build -c $(BuildConfiguration) --no-restore -bl:build.msbuild.binlog displayName: Build diff --git a/global.json b/global.json index 6dfc6666e..8b2877a60 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.407" + "version": "8.0.409" } } diff --git a/src/coverlet.collector/coverlet.collector.csproj b/src/coverlet.collector/coverlet.collector.csproj index 7630bb845..fa18b7b3c 100644 --- a/src/coverlet.collector/coverlet.collector.csproj +++ b/src/coverlet.collector/coverlet.collector.csproj @@ -27,7 +27,6 @@ tonerdo MIT https://github.com/coverlet-coverage/coverlet - https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true coverlet-icon.png false Coverlet is a cross platform code coverage library for .NET, with support for line, branch and method coverage. diff --git a/src/coverlet.console/ExitCodes.cs b/src/coverlet.console/ExitCodes.cs index 93a7d395f..7737ba034 100644 --- a/src/coverlet.console/ExitCodes.cs +++ b/src/coverlet.console/ExitCodes.cs @@ -29,9 +29,5 @@ internal enum CommandExitCodes /// Exception = 101, - /// - /// Indicates missing options or empty arguments for Coverlet process. - /// - CommandParsingException = 102 } diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index ec763612d..e7c9fec59 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.CommandLine; +using System.CommandLine.Help; +using System.CommandLine.Parsing; using System.ComponentModel; using System.Diagnostics; using System.Globalization; @@ -25,32 +27,36 @@ namespace Coverlet.Console { public static class Program { - static int Main(string[] args) + static int s_exitCode; + static async Task Main(string[] args) { - var moduleOrAppDirectory = new Argument("path", "Path to the test assembly or application directory."); - var target = new Option(["--target", "-t"], "Path to the test runner application.") { Arity = ArgumentArity.ZeroOrOne, IsRequired = true }; - var targs = new Option(["--targetargs", "-a"], "Arguments to be passed to the test runner.") { Arity = ArgumentArity.ZeroOrOne }; - var output = new Option(["--output", "-o"], "Output of the generated coverage report") { Arity = ArgumentArity.ZeroOrOne }; - var verbosity = new Option(["--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(["--format", "-f"], () => ["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 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 }; - var excludedSourceFiles = new Option("--exclude-by-file", "Glob patterns specifying source files to exclude.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - var includeDirectories = new Option("--include-directory", "Include directories containing additional assemblies to be instrumented.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - var excludeAttributes = new Option("--exclude-by-attribute", "Attributes to exclude from code coverage.") { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - var includeTestAssembly = new Option("--include-test-assembly", "Specifies whether to report code coverage of the test assembly.") { Arity = ArgumentArity.Zero }; - var singleHit = new Option("--single-hit", "Specifies whether to limit code coverage hit reporting to a single hit for each location") { Arity = ArgumentArity.Zero }; - var skipAutoProp = new Option("--skipautoprops", "Neither track nor record auto-implemented properties.") { Arity = ArgumentArity.Zero }; - var mergeWith = new Option("--merge-with", "Path to existing coverage result to merge.") { Arity = ArgumentArity.ZeroOrOne }; - var useSourceLink = new Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.") { Arity = ArgumentArity.Zero }; - 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 }; - - RootCommand rootCommand = new() + Argument moduleOrAppDirectory = new("path") { Description = "Path to the test assembly or application directory." }; + Option target = new("--target", aliases: new[] { "--target", "-t" }) { Description = "Path to the test runner application.", Arity = ArgumentArity.ZeroOrOne, Required = true }; + Option targs = new("--targetargs", aliases: new[] { "--targetargs", "-a" }) { Description = "Arguments to be passed to the test runner.", Arity = ArgumentArity.ZeroOrOne }; + Option output = new("--output", aliases: new[] { "--output", "-o" }) { Description = "Output of the generated coverage report", Arity = ArgumentArity.ZeroOrOne }; + Option verbosity = new("--verbosity", aliases: new[] { "--verbosity", "-v" }) { DefaultValueFactory = (_) => LogLevel.Normal, Description = "Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed.", Arity = ArgumentArity.ZeroOrOne }; + Option formats = new("--format", aliases: new[] { "--format", "-f" }) { DefaultValueFactory = (_) => new[] { "json" }, Description = "Format of the generated coverage report.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + formats.AcceptOnlyFromAmong("json", "lcov", "opencover", "cobertura", "teamcity"); + Option threshold = new("--threshold") { Description = "Exits with error if the coverage % is below value.", Arity = ArgumentArity.ZeroOrOne }; + Option> thresholdTypes = new("--threshold-type") { DefaultValueFactory = (_) => ["line", "branch", "method"], Description = "Coverage type to apply the threshold to." }; + thresholdTypes.AcceptOnlyFromAmong("line", "branch", "method"); + Option thresholdStat = new("--threshold-stat") { DefaultValueFactory = (_) => ThresholdStatistic.Minimum, Description = "Coverage statistic used to enforce the threshold value.", Arity = ArgumentArity.ZeroOrOne }; + Option excludeFilters = new("--exclude") { Description = "Filter expressions to exclude specific modules and types.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + Option includeFilters = new("--include") { Description = "Filter expressions to include only specific modules and types.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + Option excludedSourceFiles = new("--exclude-by-file") { Description = "Glob patterns specifying source files to exclude.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + Option includeDirectories = new("--include-directory") { Description = "Include directories containing additional assemblies to be instrumented.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + Option excludeAttributes = new("--exclude-by-attribute") { Description = "Attributes to exclude from code coverage.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + Option includeTestAssembly = new("--include-test-assembly") { Description = "Specifies whether to report code coverage of the test assembly.", Arity = ArgumentArity.Zero }; + Option singleHit = new("--single-hit") { Description = "Specifies whether to limit code coverage hit reporting to a single hit for each location", Arity = ArgumentArity.Zero }; + Option skipAutoProp = new("--skipautoprops") { Description = "Neither track nor record auto-implemented properties.", Arity = ArgumentArity.Zero }; + Option mergeWith = new("--merge-with") { Description = "Path to existing coverage result to merge.", Arity = ArgumentArity.ZeroOrOne }; + Option useSourceLink = new("--use-source-link") { Description = "Specifies whether to use SourceLink URIs in place of file system paths.", Arity = ArgumentArity.Zero }; + Option doesNotReturnAttributes = new("--does-not-return-attribute") { Description = "Attributes that mark methods that do not return", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + Option excludeAssembliesWithoutSources = new("--exclude-assemblies-without-sources") { DefaultValueFactory = (_) => "MissingAll", Description = "Specifies behavior of heuristic to ignore assemblies with missing source documents." }; + excludeAssembliesWithoutSources.AcceptOnlyFromAmong("MissingAll", "MissingAny", "None"); + Option sourceMappingFile = new("--source-mapping-file") { Description = "Specifies the path to a SourceRootsMappings file.", Arity = ArgumentArity.ZeroOrOne }; + + RootCommand rootCommand = new("Cross platform .NET Core code coverage tool") { moduleOrAppDirectory, target, @@ -75,33 +81,35 @@ static int Main(string[] args) excludeAssembliesWithoutSources, sourceMappingFile }; + rootCommand.Add(new HelpOption()); + rootCommand.Add(new VersionOption()); - rootCommand.Description = "Cross platform .NET Core code coverage tool"; + ParseResult parseResult = CommandLineParser.Parse(rootCommand, args); - rootCommand.SetHandler(async (context) => + rootCommand.SetAction(async (context) => { - string moduleOrAppDirectoryValue = context.ParseResult.GetValueForArgument(moduleOrAppDirectory); - string targetValue = context.ParseResult.GetValueForOption(target); - string targsValue = context.ParseResult.GetValueForOption(targs); - string outputValue = context.ParseResult.GetValueForOption(output); - LogLevel verbosityValue = context.ParseResult.GetValueForOption(verbosity); - string[] formatsValue = context.ParseResult.GetValueForOption(formats); - string thresholdValue = context.ParseResult.GetValueForOption(threshold); - List thresholdTypesValue = context.ParseResult.GetValueForOption(thresholdTypes); - ThresholdStatistic thresholdStatValue = context.ParseResult.GetValueForOption(thresholdStat); - string[] excludeFiltersValue = context.ParseResult.GetValueForOption(excludeFilters); - string[] includeFiltersValue = context.ParseResult.GetValueForOption(includeFilters); - string[] excludedSourceFilesValue = context.ParseResult.GetValueForOption(excludedSourceFiles); - string[] includeDirectoriesValue = context.ParseResult.GetValueForOption(includeDirectories); - string[] excludeAttributesValue = context.ParseResult.GetValueForOption(excludeAttributes); - bool includeTestAssemblyValue = context.ParseResult.GetValueForOption(includeTestAssembly); - bool singleHitValue = context.ParseResult.GetValueForOption(singleHit); - bool skipAutoPropValue = context.ParseResult.GetValueForOption(skipAutoProp); - string mergeWithValue = context.ParseResult.GetValueForOption(mergeWith); - bool useSourceLinkValue = context.ParseResult.GetValueForOption(useSourceLink); - string[] doesNotReturnAttributesValue = context.ParseResult.GetValueForOption(doesNotReturnAttributes); - string excludeAssembliesWithoutSourcesValue = context.ParseResult.GetValueForOption(excludeAssembliesWithoutSources); - string sourceMappingFileValue = context.ParseResult.GetValueForOption(sourceMappingFile); + string moduleOrAppDirectoryValue = parseResult.GetValue(moduleOrAppDirectory); + string targetValue = parseResult.GetValue(target); + string targsValue = parseResult.GetValue(targs); + string outputValue = parseResult.GetValue(output); + LogLevel verbosityValue = parseResult.GetValue(verbosity); + string[] formatsValue = parseResult.GetValue(formats); + string thresholdValue = parseResult.GetValue(threshold); + List thresholdTypesValue = parseResult.GetValue(thresholdTypes); + ThresholdStatistic thresholdStatValue = parseResult.GetValue(thresholdStat); + string[] excludeFiltersValue = parseResult.GetValue(excludeFilters); + string[] includeFiltersValue = parseResult.GetValue(includeFilters); + string[] excludedSourceFilesValue = parseResult.GetValue(excludedSourceFiles); + string[] includeDirectoriesValue = parseResult.GetValue(includeDirectories); + string[] excludeAttributesValue = parseResult.GetValue(excludeAttributes); + bool includeTestAssemblyValue = parseResult.GetValue(includeTestAssembly); + bool singleHitValue = parseResult.GetValue(singleHit); + bool skipAutoPropValue = parseResult.GetValue(skipAutoProp); + string mergeWithValue = parseResult.GetValue(mergeWith); + bool useSourceLinkValue = parseResult.GetValue(useSourceLink); + string[] doesNotReturnAttributesValue = parseResult.GetValue(doesNotReturnAttributes); + string excludeAssembliesWithoutSourcesValue = parseResult.GetValue(excludeAssembliesWithoutSources); + string sourceMappingFileValue = parseResult.GetValue(sourceMappingFile); if (string.IsNullOrEmpty(moduleOrAppDirectoryValue) || string.IsNullOrWhiteSpace(moduleOrAppDirectoryValue)) throw new ArgumentException("No test assembly or application directory specified."); @@ -128,10 +136,14 @@ static int Main(string[] args) doesNotReturnAttributesValue, excludeAssembliesWithoutSourcesValue, sourceMappingFileValue); - context.ExitCode = taskStatus; + //context.ExitCode = taskStatus; }); - return rootCommand.Invoke(args); + + CommandLineConfiguration config = new(rootCommand); + + await config.InvokeAsync(args).ConfigureAwait(false); + Environment.Exit(s_exitCode); } private static Task HandleCommand(string moduleOrAppDirectory, string target, @@ -175,7 +187,7 @@ string sourceMappingFile // Adjust log level based on user input. logger.Level = verbosity; - int exitCode = (int)CommandExitCodes.Success; + s_exitCode = (int)CommandExitCodes.Success; try { @@ -357,44 +369,44 @@ string sourceMappingFile logger.LogInformation(coverageTable.ToStringAlternative()); if (process.ExitCode > 0) { - exitCode += (int)CommandExitCodes.TestFailed; + s_exitCode = (int)CommandExitCodes.TestFailed; } ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(thresholdTypeFlagValues, thresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { - exitCode += (int)CommandExitCodes.CoverageBelowThreshold; - var exceptionMessageBuilder = new StringBuilder(); + s_exitCode = (int)CommandExitCodes.CoverageBelowThreshold; + var errorMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { - exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}"); + errorMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { - exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Branch]}"); + errorMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Branch]}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { - exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}"); + errorMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}"); } - throw new InvalidOperationException(exceptionMessageBuilder.ToString()); + logger.LogError(errorMessageBuilder.ToString()); } - return Task.FromResult(exitCode); + return Task.FromResult(s_exitCode); } catch (Win32Exception we) when (we.Source == "System.Diagnostics.Process") { logger.LogError($"Start process '{target}' failed with '{we.Message}'"); - return Task.FromResult(exitCode > 0 ? exitCode : (int)CommandExitCodes.Exception); + return Task.FromResult(s_exitCode > 0 ? s_exitCode : (int)CommandExitCodes.Exception); } catch (Exception ex) { logger.LogError(ex.Message); - return Task.FromResult(exitCode > 0 ? exitCode : (int)CommandExitCodes.Exception); + return Task.FromResult(s_exitCode > 0 ? s_exitCode : (int)CommandExitCodes.Exception); } } diff --git a/src/coverlet.console/Properties/AssemblyInfo.cs b/src/coverlet.console/Properties/AssemblyInfo.cs index 427662b26..b89dff2f4 100644 --- a/src/coverlet.console/Properties/AssemblyInfo.cs +++ b/src/coverlet.console/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) Toni Solarin-Sodara +// Copyright (c) Toni Solarin-Sodara // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Runtime.CompilerServices; diff --git a/src/coverlet.console/coverlet.console.csproj b/src/coverlet.console/coverlet.console.csproj index 2a7181b18..cd3f6afc8 100644 --- a/src/coverlet.console/coverlet.console.csproj +++ b/src/coverlet.console/coverlet.console.csproj @@ -1,4 +1,4 @@ - + Exe @@ -16,13 +16,12 @@ coverage;testing;unit-test;lcov;opencover;quality GlobalTool.md https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/Changelog.md - https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true coverlet-icon.png https://github.com/coverlet-coverage/coverlet MIT git - + diff --git a/src/coverlet.core/coverlet.core.csproj b/src/coverlet.core/coverlet.core.csproj index f3e16e3f1..a0f0f7621 100644 --- a/src/coverlet.core/coverlet.core.csproj +++ b/src/coverlet.core/coverlet.core.csproj @@ -1,9 +1,10 @@ - + Library netstandard2.0 false + $(NoWarn);IDE0057 @@ -17,6 +18,7 @@ + diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj b/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj index c04e823b0..08e532b4f 100644 --- a/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj +++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj @@ -28,7 +28,6 @@ tonerdo MIT https://github.com/coverlet-coverage/coverlet - https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true coverlet-icon.png false true diff --git a/test/coverlet.core.tests.samples.netstandard/coverlet.core.tests.samples.netstandard.csproj b/test/coverlet.core.tests.samples.netstandard/coverlet.core.tests.samples.netstandard.csproj index 36bc43a36..a7ce141f5 100644 --- a/test/coverlet.core.tests.samples.netstandard/coverlet.core.tests.samples.netstandard.csproj +++ b/test/coverlet.core.tests.samples.netstandard/coverlet.core.tests.samples.netstandard.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/coverlet.integration.template/DeepThought.cs b/test/coverlet.integration.template/DeepThought.cs index e2a3f4b9c..998a001a2 100644 --- a/test/coverlet.integration.template/DeepThought.cs +++ b/test/coverlet.integration.template/DeepThought.cs @@ -1,10 +1,21 @@ -namespace Coverlet.Integration.Template +namespace Coverlet.Integration.Template { - public class DeepThought + public class DeepThought + { + public int AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything() { - public int AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything() - { - return 42; - } + return 42; } + + // This method is not covered by any test + // It is here to demonstrate how Coverlet will report on untested code + // required for Coverlet.Integration.Tests.DotnetGlobalTools.StandAloneThreshold + // required for Coverlet.Integration.Tests.DotnetGlobalTools.DotnetToolThreshold + public void TheUntestedMethod() + { +#pragma warning disable CS0219 // Variable is assigned but its value is never used + string s = "this will never be covered by any test"; +#pragma warning restore CS0219 // Variable is assigned but its value is never used + } + } } diff --git a/test/coverlet.integration.tests/Collectors.cs b/test/coverlet.integration.tests/Collectors.cs index a19e814e1..b334dca4c 100644 --- a/test/coverlet.integration.tests/Collectors.cs +++ b/test/coverlet.integration.tests/Collectors.cs @@ -80,11 +80,10 @@ private protected virtual void AssertCollectorsInjection(ClonedTemplateProject c public void TestVsTest_Test() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); - int cmdExitCode = DotnetCli($"test -c {_buildConfiguration} -f {_buildTargetFramework} \"{clonedTemplateProject.ProjectRootPath}\" --collect:\"XPlat Code Coverage\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out string standardOutput, out string standardError, clonedTemplateProject.ProjectRootPath!); + Assert.Equal(0, DotnetCli($"test -c {_buildConfiguration} -f {_buildTargetFramework} \"{clonedTemplateProject.ProjectRootPath}\" --collect:\"XPlat Code Coverage\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out string standardOutput, out string standardError, clonedTemplateProject.ProjectRootPath!)); // We don't have any result to check because tests and code to instrument are in same assembly so we need to pass // IncludeTestAssembly=true we do it in other test - Assert.Equal(0, cmdExitCode); Assert.Contains("Passed!", standardOutput); AssertCollectorsInjection(clonedTemplateProject); @@ -95,9 +94,7 @@ public void TestVsTest_Test_Settings() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); string runSettingsPath = AddCollectorRunsettingsFile(clonedTemplateProject.ProjectRootPath!); - int cmdExitCode = DotnetCli($"test -c {_buildConfiguration} -f {_buildTargetFramework} \"{clonedTemplateProject.ProjectRootPath}\" --collect:\"XPlat Code Coverage\" --settings \"{runSettingsPath}\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out string standardOutput, out string standardError); - - Assert.Equal(0, cmdExitCode); + Assert.Equal(0, DotnetCli($"test -c {_buildConfiguration} -f {_buildTargetFramework} \"{clonedTemplateProject.ProjectRootPath}\" --collect:\"XPlat Code Coverage\" --settings \"{runSettingsPath}\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out string standardOutput, out string standardError)); Assert.Contains("Passed!", standardOutput); AssertCoverage(clonedTemplateProject); AssertCollectorsInjection(clonedTemplateProject); @@ -108,12 +105,10 @@ public void TestVsTest_VsTest() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); string runSettingsPath = AddCollectorRunsettingsFile(clonedTemplateProject.ProjectRootPath!); - int cmdExitCode = DotnetCli($"publish -c {_buildConfiguration} -f {_buildTargetFramework} {clonedTemplateProject.ProjectRootPath}", out string standardOutput, out string standardError); - - Assert.Equal(0, cmdExitCode); + Assert.Equal(0, DotnetCli($"publish -c {_buildConfiguration} -f {_buildTargetFramework} {clonedTemplateProject.ProjectRootPath}", out string standardOutput, out string standardError)); string publishedTestFile = clonedTemplateProject.GetFiles("*" + ClonedTemplateProject.AssemblyName + ".dll").Single(f => f.Contains("publish")); Assert.NotNull(publishedTestFile); - cmdExitCode = DotnetCli($"vstest \"{publishedTestFile}\" --collect:\"XPlat Code Coverage\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out standardOutput, out standardError); + Assert.Equal(0, DotnetCli($"vstest \"{publishedTestFile}\" --collect:\"XPlat Code Coverage\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out standardOutput, out standardError)); // We don't have any result to check because tests and code to instrument are in same assembly so we need to pass // IncludeTestAssembly=true we do it in other test Assert.Contains("Passed!", standardOutput); @@ -125,12 +120,10 @@ public void TestVsTest_VsTest_Settings() { using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); string runSettingsPath = AddCollectorRunsettingsFile(clonedTemplateProject.ProjectRootPath!); - int cmdExitCode = DotnetCli($"publish -c {_buildConfiguration} -f {_buildTargetFramework} \"{clonedTemplateProject.ProjectRootPath}\"", out string standardOutput, out string standardError); - - Assert.Equal(0, cmdExitCode); + Assert.Equal(0, DotnetCli($"publish -c {_buildConfiguration} -f {_buildTargetFramework} \"{clonedTemplateProject.ProjectRootPath}\"", out string standardOutput, out string standardError)); string publishedTestFile = clonedTemplateProject.GetFiles("*" + ClonedTemplateProject.AssemblyName + ".dll").Single(f => f.Contains("publish")); Assert.NotNull(publishedTestFile); - cmdExitCode = DotnetCli($"vstest \"{publishedTestFile}\" --collect:\"XPlat Code Coverage\" --ResultsDirectory:\"{clonedTemplateProject.ProjectRootPath}\" /settings:\"{runSettingsPath}\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out standardOutput, out standardError); + Assert.Equal(0, DotnetCli($"vstest \"{publishedTestFile}\" --collect:\"XPlat Code Coverage\" --ResultsDirectory:\"{clonedTemplateProject.ProjectRootPath}\" /settings:\"{runSettingsPath}\" --diag:{Path.Combine(clonedTemplateProject.ProjectRootPath, "log.txt")}", out standardOutput, out standardError)); Assert.Contains("Passed!", standardOutput); AssertCoverage(clonedTemplateProject); AssertCollectorsInjection(clonedTemplateProject); diff --git a/test/coverlet.integration.tests/DeterministicBuild.cs b/test/coverlet.integration.tests/DeterministicBuild.cs index 9c8a181db..5a7bcd898 100644 --- a/test/coverlet.integration.tests/DeterministicBuild.cs +++ b/test/coverlet.integration.tests/DeterministicBuild.cs @@ -1,4 +1,4 @@ -// Copyright (c) Toni Solarin-Sodara +// Copyright (c) Toni Solarin-Sodara // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -299,7 +299,7 @@ private static void DeleteTestIntermediateFiles(string testResultsPath) { if (Directory.Exists(testResultsPath)) { - DirectoryInfo hdDirectory = new DirectoryInfo(testResultsPath); + DirectoryInfo hdDirectory = new (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); @@ -315,7 +315,7 @@ private static void DeleteLogFiles(string directory) { if (Directory.Exists(directory)) { - DirectoryInfo hdDirectory = new DirectoryInfo(directory); + DirectoryInfo hdDirectory = new (directory); FileInfo[] filesInDir = hdDirectory.GetFiles("log.*.txt"); foreach (FileInfo foundFile in filesInDir) @@ -343,7 +343,7 @@ private static void DeleteCoverageFiles(string directory) { if (Directory.Exists(directory)) { - DirectoryInfo hdDirectory = new DirectoryInfo(directory); + DirectoryInfo hdDirectory = new (directory); FileInfo[] filesInDir = hdDirectory.GetFiles("coverage.cobertura.xml"); foreach (FileInfo foundFile in filesInDir) diff --git a/test/coverlet.integration.tests/DotnetTool.cs b/test/coverlet.integration.tests/DotnetTool.cs index 91e989f02..8a1e408f3 100644 --- a/test/coverlet.integration.tests/DotnetTool.cs +++ b/test/coverlet.integration.tests/DotnetTool.cs @@ -21,8 +21,12 @@ public DotnetGlobalTools(ITestOutputHelper output) private string InstallTool(string projectPath) { _ = DotnetCli($"tool install coverlet.console --version {GetPackageVersion("*console*.nupkg")} --tool-path \"{Path.Combine(projectPath, "coverletTool")}\"", out string standardOutput, out string standardError, projectPath); - Assert.Contains("was successfully installed.", standardOutput); + if (!string.IsNullOrEmpty(standardError)) + { + _output.WriteLine(standardError); + } Assert.Empty(standardError); + Assert.Contains("was successfully installed.", standardOutput); return Path.Combine(projectPath, "coverletTool", "coverlet"); } @@ -35,13 +39,14 @@ public void DotnetTool() string outputPath = $"{clonedTemplateProject.ProjectRootPath}{Path.DirectorySeparatorChar}coverage.json"; DotnetCli($"build -f {_buildTargetFramework} {clonedTemplateProject.ProjectRootPath}", out string buildOutput, out string buildError); string publishedTestFile = clonedTemplateProject.GetFiles("*" + ClonedTemplateProject.AssemblyName + ".dll").Single(f => !f.Contains("obj") && !f.Contains("ref")); - RunCommand(coverletToolCommandPath, $"\"{publishedTestFile}\" --target \"dotnet\" --targetargs \"test {Path.Combine(clonedTemplateProject.ProjectRootPath, ClonedTemplateProject.ProjectFileName)} --no-build\" --include-test-assembly --output \"{outputPath}\"", out string standardOutput, out string standardError); + int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{publishedTestFile}\" --target \"dotnet\" --targetargs \"test {Path.Combine(clonedTemplateProject.ProjectRootPath, ClonedTemplateProject.ProjectFileName)} --no-build\" --include-test-assembly --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); } Assert.Contains("Passed!", standardOutput); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); + Assert.Equal((int)CommandExitCodes.Success, cmdExitCode); } [Fact] @@ -53,14 +58,14 @@ public void StandAlone() string outputPath = $"{clonedTemplateProject.ProjectRootPath}{Path.DirectorySeparatorChar}coverage.json"; DotnetCli($"build -f {_buildTargetFramework} {clonedTemplateProject.ProjectRootPath}", out string buildOutput, out string buildError); string publishedTestFile = clonedTemplateProject.GetFiles("*" + ClonedTemplateProject.AssemblyName + ".dll").Single(f => !f.Contains("obj") && !f.Contains("ref")); - RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --output \"{outputPath}\"", out string standardOutput, out string standardError); + int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); } - //Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); + Assert.Equal((int)CommandExitCodes.Success, cmdExitCode); } [Fact] @@ -82,13 +87,11 @@ public void StandAloneThreshold() // make standard output available in trx file _output.WriteLine(standardOutput); } - //Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); - //Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, cmdExitCode); - // this messages are now in stderr available but standardError stream is empty in test environment - //Assert.Contains("The minimum line coverage is below the specified 80", standardError); - //Assert.Contains("The minimum method coverage is below the specified 80", standardOutput); + Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, cmdExitCode); + Assert.Contains("The minimum line coverage is below the specified 80", standardOutput); + Assert.Contains("The minimum method coverage is below the specified 80", standardOutput); } [Fact] @@ -110,12 +113,11 @@ public void StandAloneThresholdLine() // make standard output available in trx file _output.WriteLine(standardOutput); } - // Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); - //Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, cmdExitCode); - //Assert.Contains("The minimum line coverage is below the specified 80", standardError); - //Assert.DoesNotContain("The minimum method coverage is below the specified 80", standardOutput); + Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, cmdExitCode); + Assert.Contains("The minimum line coverage is below the specified 80", standardOutput); + Assert.DoesNotContain("The minimum method coverage is below the specified 80", standardOutput); } [Fact] @@ -137,12 +139,11 @@ public void StandAloneThresholdLineAndMethod() // make standard output available in trx file _output.WriteLine(standardOutput); } - // Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); - //Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, cmdExitCode); - //Assert.Contains("The minimum line coverage is below the specified 80", standardError); - //Assert.Contains("The minimum method coverage is below the specified 80", standardOutput); + Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, cmdExitCode); + Assert.Contains("The minimum line coverage is below the specified 80", standardOutput); + Assert.Contains("The minimum method coverage is below the specified 80", standardOutput); } } } diff --git a/test/coverlet.integration.tests/Msbuild.cs b/test/coverlet.integration.tests/Msbuild.cs index abb784d10..7e4c8656f 100644 --- a/test/coverlet.integration.tests/Msbuild.cs +++ b/test/coverlet.integration.tests/Msbuild.cs @@ -45,7 +45,7 @@ public void TestMsbuild() } Assert.Equal(0, result); Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); string coverageFileName = $"coverage.json"; Assert.True(File.Exists(Path.Combine(clonedTemplateProject.ProjectRootPath, coverageFileName))); AssertCoverage(clonedTemplateProject, coverageFileName); @@ -66,7 +66,7 @@ public void TestMsbuild_NoCoverletOutput() } Assert.Equal(0, result); Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); string coverageFileName = $"coverage.json"; Assert.True(File.Exists(Path.Combine(clonedTemplateProject.ProjectRootPath, coverageFileName))); AssertCoverage(clonedTemplateProject, coverageFileName); @@ -87,7 +87,7 @@ public void TestMsbuild_CoverletOutput_Folder_FileNameWithoutExtension() } Assert.Equal(0, result); Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); string coverageFileName = $"file.json"; Assert.True(File.Exists(Path.Combine(clonedTemplateProject.ProjectRootPath, coverageFileName))); AssertCoverage(clonedTemplateProject, coverageFileName); @@ -99,7 +99,7 @@ public void TestMsbuild_CoverletOutput_Folder_FileNameExtension() using ClonedTemplateProject clonedTemplateProject = PrepareTemplateProject(); Assert.Equal(0, DotnetCli($"test -c {_buildConfiguration} -f {_buildTargetFramework} \"{clonedTemplateProject.ProjectRootPath}\" /p:CollectCoverage=true /p:Include=\"[{ClonedTemplateProject.AssemblyName}]*DeepThought\" /p:IncludeTestAssembly=true /p:CoverletOutput=\"{clonedTemplateProject.ProjectRootPath}\"\\file.ext", out string standardOutput, out string standardError)); Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); string coverageFileName = $"file.ext"; Assert.True(File.Exists(Path.Combine(clonedTemplateProject.ProjectRootPath, coverageFileName))); AssertCoverage(clonedTemplateProject, coverageFileName); @@ -123,7 +123,7 @@ public void TestMsbuild_CoverletOutput_Folder_FileNameExtension_SpecifyFramework _output.WriteLine(standardOutput); } Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); Assert.True(File.Exists(Path.Combine(clonedTemplateProject.ProjectRootPath, "file.ext"))); AssertCoverage(clonedTemplateProject, "file.ext"); } @@ -142,7 +142,7 @@ public void TestMsbuild_CoverletOutput_Folder_FileNameWithDoubleExtension() _output.WriteLine(standardOutput); } Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); string coverageFileName = $"file.ext1.ext2"; Assert.True(File.Exists(Path.Combine(clonedTemplateProject.ProjectRootPath, coverageFileName))); AssertCoverage(clonedTemplateProject, coverageFileName); @@ -164,7 +164,7 @@ public void Test_MultipleTargetFrameworkReport_NoCoverletOutput() _output.WriteLine(standardOutput); } Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); foreach (string targetFramework in targetFrameworks) { @@ -191,7 +191,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder() } Assert.Equal(0, result); Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); foreach (string targetFramework in targetFrameworks) { @@ -218,7 +218,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWit _output.WriteLine(standardOutput); } Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); foreach (string targetFramework in targetFrameworks) { @@ -248,7 +248,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWit _output.WriteLine(standardOutput); } Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); foreach (string targetFramework in targetFrameworks) { @@ -281,7 +281,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWit _output.WriteLine(standardOutput); } Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); foreach (string targetFramework in targetFrameworks) { @@ -307,7 +307,7 @@ public void Test_MultipleTargetFrameworkReport_CoverletOutput_Folder_FileNameWit _output.WriteLine(standardOutput); } Assert.Contains("Passed!", standardOutput, StringComparison.Ordinal); - Assert.Contains("| coverletsamplelib.integration.template | 100% | 100% | 100% |", standardOutput, StringComparison.Ordinal); + Assert.Contains("| coverletsamplelib.integration.template | 50% | 100% | 50% |", standardOutput, StringComparison.Ordinal); foreach (string targetFramework in targetFrameworks) { diff --git a/test/coverlet.integration.tests/coverlet.integration.tests.csproj b/test/coverlet.integration.tests/coverlet.integration.tests.csproj index 4c8cc9c50..fbb887bc2 100644 --- a/test/coverlet.integration.tests/coverlet.integration.tests.csproj +++ b/test/coverlet.integration.tests/coverlet.integration.tests.csproj @@ -29,6 +29,7 @@ +