From e1ff1c5d3ac0ab114be22dbc66cead259b4480c4 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 23 Jan 2025 16:51:04 +0100 Subject: [PATCH 01/14] use System.CommandLine V2.0.0-beta4 (latest) --- .editorconfig | 1 + Directory.Build.props | 3 +- Directory.Packages.props | 3 +- eng/azure-pipelines-nightly.yml | 3 + .../coverlet.collector.csproj | 1 - src/coverlet.console/Program.cs | 138 ++++++++++-------- .../Properties/AssemblyInfo.cs | 2 +- src/coverlet.console/coverlet.console.csproj | 12 +- src/coverlet.core/coverlet.core.csproj | 3 +- .../coverlet.msbuild.tasks.csproj | 1 - test/coverlet.integration.tests/Collectors.cs | 20 +-- .../DeterministicBuild.cs | 8 +- test/coverlet.integration.tests/DotnetTool.cs | 38 +++-- .../coverlet.integration.tests.csproj | 1 + 14 files changed, 128 insertions(+), 106 deletions(-) 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/Directory.Build.props b/Directory.Build.props index 0b0e187d0..a7606cd0a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,9 +15,10 @@ false true 12.0 - $(NoWarn);NU1507;NU5105;CS1591 + $(NoWarn);NU1507;NU5105;CS1591;NU1608;NU1900 true + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json https://api.nuget.org/v3/index.json; diff --git a/Directory.Packages.props b/Directory.Packages.props index eba94e254..0f354aed8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -48,7 +48,7 @@ - + @@ -57,6 +57,7 @@ + diff --git a/eng/azure-pipelines-nightly.yml b/eng/azure-pipelines-nightly.yml index ab78bae71..65b101fb4 100644 --- a/eng/azure-pipelines-nightly.yml +++ b/eng/azure-pipelines-nightly.yml @@ -15,6 +15,9 @@ steps: - task: NuGetAuthenticate@1 displayName: Authenticate with NuGet feeds +- script: dotnet restore + displayName: Restore packages + - script: dotnet pack -c Release /p:PublicRelease=false displayName: Create NuGet packages 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/Program.cs b/src/coverlet.console/Program.cs index ec763612d..ca653a0e4 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -1,9 +1,11 @@ -// 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; 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,34 @@ 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() + CliArgument moduleOrAppDirectory = new ("path") { Description = "Path to the test assembly or application directory." }; + CliOption target = new ("--target", aliases: new[]{ "--target", "-t" }) { Description = "Path to the test runner application." , Arity = ArgumentArity.ZeroOrOne, Required = true }; + CliOption targs = new ("--targetargs", aliases: new[] { "--targetargs", "-a" } ) { Description = "Arguments to be passed to the test runner.", Arity = ArgumentArity.ZeroOrOne }; + CliOption output = new ("--output", aliases: new[] { "--output", "-o" }) { Description = "Output of the generated coverage report", Arity = ArgumentArity.ZeroOrOne }; + CliOption 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 }; + CliOption formats = new ("--format", aliases: new[] { "--format", "-f" }) { DefaultValueFactory = (_) => new[] { "json" }, Description = "Format of the generated coverage report.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + CliOption threshold = new ("--threshold") { Description = "Exits with error if the coverage % is below value.", Arity = ArgumentArity.ZeroOrOne }; + CliOption> thresholdTypes = new ("--threshold-type") { DefaultValueFactory = (_) => new List(new string[] { "line", "branch", "method" }), Description = ("Coverage type to apply the threshold to.")}; + thresholdTypes.AcceptOnlyFromAmong("line", "branch", "method"); + CliOption thresholdStat = new ("--threshold-stat") {DefaultValueFactory = (_) => ThresholdStatistic.Minimum, Description = "Coverage statistic used to enforce the threshold value." , Arity = ArgumentArity.ZeroOrOne }; + CliOption excludeFilters = new ("--exclude") { Description = "Filter expressions to exclude specific modules and types.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + CliOption includeFilters = new ("--include") { Description = "Filter expressions to include only specific modules and types.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + CliOption excludedSourceFiles = new ("--exclude-by-file") { Description = "Glob patterns specifying source files to exclude.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + CliOption includeDirectories = new("--include-directory") { Description = "Include directories containing additional assemblies to be instrumented.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + CliOption excludeAttributes = new ("--exclude-by-attribute") { Description = "Attributes to exclude from code coverage.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + CliOption includeTestAssembly = new ("--include-test-assembly") { Description = "Specifies whether to report code coverage of the test assembly.", Arity = ArgumentArity.Zero }; + CliOption singleHit = new ("--single-hit") { Description = "Specifies whether to limit code coverage hit reporting to a single hit for each location", Arity = ArgumentArity.Zero }; + CliOption skipAutoProp = new ("--skipautoprops") { Description = "Neither track nor record auto-implemented properties.", Arity = ArgumentArity.Zero }; + CliOption mergeWith = new ("--merge-with") { Description = "Path to existing coverage result to merge.", Arity = ArgumentArity.ZeroOrOne }; + CliOption useSourceLink = new ("--use-source-link") { Description = "Specifies whether to use SourceLink URIs in place of file system paths.", Arity = ArgumentArity.Zero }; + CliOption doesNotReturnAttributes = new ("--does-not-return-attribute") { Description = "Attributes that mark methods that do not return", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; + CliOption excludeAssembliesWithoutSources = new ("--exclude-assemblies-without-sources") { Description = "Specifies behaviour of heuristic to ignore assemblies with missing source documents.", Arity = ArgumentArity.ZeroOrOne }; + CliOption sourceMappingFile = new ("--source-mapping-file") { Description = "Specifies the path to a SourceRootsMappings file.", Arity = ArgumentArity.ZeroOrOne }; + + CliRootCommand rootCommand = new("Cross platform .NET Core code coverage tool") { moduleOrAppDirectory, target, @@ -75,33 +79,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 = CliParser.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 +134,14 @@ static int Main(string[] args) doesNotReturnAttributesValue, excludeAssembliesWithoutSourcesValue, sourceMappingFileValue); - context.ExitCode = taskStatus; + //context.ExitCode = taskStatus; }); - return rootCommand.Invoke(args); + + CliConfiguration config = new(rootCommand); + + await config.InvokeAsync(args).ConfigureAwait(false); + Environment.Exit(s_exitCode); } private static Task HandleCommand(string moduleOrAppDirectory, string target, @@ -175,7 +185,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 +367,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..bbfe4bbdc 100644 --- a/src/coverlet.console/coverlet.console.csproj +++ b/src/coverlet.console/coverlet.console.csproj @@ -1,4 +1,4 @@ - + Exe @@ -16,15 +16,21 @@ 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 + + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json + https://api.nuget.org/v3/index.json; + - + + + + diff --git a/src/coverlet.core/coverlet.core.csproj b/src/coverlet.core/coverlet.core.csproj index f3e16e3f1..f8d10b7eb 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 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.integration.tests/Collectors.cs b/test/coverlet.integration.tests/Collectors.cs index a19e814e1..bb9e76863 100644 --- a/test/coverlet.integration.tests/Collectors.cs +++ b/test/coverlet.integration.tests/Collectors.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; @@ -80,7 +80,7 @@ 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 @@ -95,9 +95,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 +106,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 +121,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..b787ae462 100644 --- a/test/coverlet.integration.tests/DotnetTool.cs +++ b/test/coverlet.integration.tests/DotnetTool.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.IO; @@ -21,6 +21,11 @@ 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); + if (!string.IsNullOrEmpty(standardError)) + { + _output.WriteLine(standardError); + } + Assert.Contains("", standardError); Assert.Contains("was successfully installed.", standardOutput); Assert.Empty(standardError); return Path.Combine(projectPath, "coverletTool", "coverlet"); @@ -35,13 +40,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 result = RunCommand(coverletToolCommandPath, $"\"{publishedTestFile}\" --target \"dotnet\" --targetargs \"test {Path.Combine(clonedTemplateProject.ProjectRootPath, ClonedTemplateProject.ProjectFileName)} --no-build\" --include-test-assembly --output \"{outputPath}\"", out standardOutput, out standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); } Assert.Contains("Passed!", standardOutput); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); + Assert.Equal((int)CommandExitCodes.Success, result); } [Fact] @@ -53,7 +59,7 @@ 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 result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --output \"{outputPath}\"", out standardOutput, out standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -61,6 +67,7 @@ public void StandAlone() //Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); + Assert.Equal((int)CommandExitCodes.Success, result); } [Fact] @@ -72,7 +79,7 @@ public void StandAloneThreshold() 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")); - int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --output \"{outputPath}\"", out string standardOutput, out string standardError); + int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --output \"{outputPath}\"", out standardOutput, out standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -85,10 +92,9 @@ public void StandAloneThreshold() //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, result); + 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] @@ -100,7 +106,7 @@ public void StandAloneThresholdLine() 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")); - int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --output \"{outputPath}\"", out string standardOutput, out string standardError); + int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --output \"{outputPath}\"", out standardOutput, out standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -113,9 +119,9 @@ public void StandAloneThresholdLine() // 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, result); + 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] @@ -127,7 +133,7 @@ public void StandAloneThresholdLineAndMethod() 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")); - int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --threshold-type method --output \"{outputPath}\"", out string standardOutput, out string standardError); + int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --threshold-type method --output \"{outputPath}\"", out standardOutput, out standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -140,9 +146,9 @@ public void StandAloneThresholdLineAndMethod() // 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, result); + 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/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 @@ + From 8096da9e3762d391b1aa74466a43f4135ad83f72 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 23 Jan 2025 17:45:12 +0100 Subject: [PATCH 02/14] update System.CommandLine version and add nuget.config --- Directory.Build.props | 4 +-- nuget.config | 9 ++++++ src/coverlet.console/Program.cs | 56 ++++++++++++++++----------------- 3 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 nuget.config diff --git a/Directory.Build.props b/Directory.Build.props index a7606cd0a..655455e62 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -17,10 +17,10 @@ 12.0 $(NoWarn);NU1507;NU5105;CS1591;NU1608;NU1900 true - + diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..520f939fb --- /dev/null +++ b/nuget.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index ca653a0e4..890fa30b4 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.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; @@ -30,31 +30,31 @@ public static class Program static int s_exitCode; static async Task Main(string[] args) { - CliArgument moduleOrAppDirectory = new ("path") { Description = "Path to the test assembly or application directory." }; - CliOption target = new ("--target", aliases: new[]{ "--target", "-t" }) { Description = "Path to the test runner application." , Arity = ArgumentArity.ZeroOrOne, Required = true }; - CliOption targs = new ("--targetargs", aliases: new[] { "--targetargs", "-a" } ) { Description = "Arguments to be passed to the test runner.", Arity = ArgumentArity.ZeroOrOne }; - CliOption output = new ("--output", aliases: new[] { "--output", "-o" }) { Description = "Output of the generated coverage report", Arity = ArgumentArity.ZeroOrOne }; - CliOption 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 }; - CliOption formats = new ("--format", aliases: new[] { "--format", "-f" }) { DefaultValueFactory = (_) => new[] { "json" }, Description = "Format of the generated coverage report.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - CliOption threshold = new ("--threshold") { Description = "Exits with error if the coverage % is below value.", Arity = ArgumentArity.ZeroOrOne }; - CliOption> thresholdTypes = new ("--threshold-type") { DefaultValueFactory = (_) => new List(new string[] { "line", "branch", "method" }), Description = ("Coverage type to apply the threshold to.")}; + 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 }; + Option threshold = new("--threshold") { Description = "Exits with error if the coverage % is below value.", Arity = ArgumentArity.ZeroOrOne }; + Option> thresholdTypes = new("--threshold-type") { DefaultValueFactory = (_) => new List(new string[] { "line", "branch", "method" }), Description = ("Coverage type to apply the threshold to.") }; thresholdTypes.AcceptOnlyFromAmong("line", "branch", "method"); - CliOption thresholdStat = new ("--threshold-stat") {DefaultValueFactory = (_) => ThresholdStatistic.Minimum, Description = "Coverage statistic used to enforce the threshold value." , Arity = ArgumentArity.ZeroOrOne }; - CliOption excludeFilters = new ("--exclude") { Description = "Filter expressions to exclude specific modules and types.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - CliOption includeFilters = new ("--include") { Description = "Filter expressions to include only specific modules and types.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - CliOption excludedSourceFiles = new ("--exclude-by-file") { Description = "Glob patterns specifying source files to exclude.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - CliOption includeDirectories = new("--include-directory") { Description = "Include directories containing additional assemblies to be instrumented.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - CliOption excludeAttributes = new ("--exclude-by-attribute") { Description = "Attributes to exclude from code coverage.", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - CliOption includeTestAssembly = new ("--include-test-assembly") { Description = "Specifies whether to report code coverage of the test assembly.", Arity = ArgumentArity.Zero }; - CliOption singleHit = new ("--single-hit") { Description = "Specifies whether to limit code coverage hit reporting to a single hit for each location", Arity = ArgumentArity.Zero }; - CliOption skipAutoProp = new ("--skipautoprops") { Description = "Neither track nor record auto-implemented properties.", Arity = ArgumentArity.Zero }; - CliOption mergeWith = new ("--merge-with") { Description = "Path to existing coverage result to merge.", Arity = ArgumentArity.ZeroOrOne }; - CliOption useSourceLink = new ("--use-source-link") { Description = "Specifies whether to use SourceLink URIs in place of file system paths.", Arity = ArgumentArity.Zero }; - CliOption doesNotReturnAttributes = new ("--does-not-return-attribute") { Description = "Attributes that mark methods that do not return", Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = true }; - CliOption excludeAssembliesWithoutSources = new ("--exclude-assemblies-without-sources") { Description = "Specifies behaviour of heuristic to ignore assemblies with missing source documents.", Arity = ArgumentArity.ZeroOrOne }; - CliOption sourceMappingFile = new ("--source-mapping-file") { Description = "Specifies the path to a SourceRootsMappings file.", Arity = ArgumentArity.ZeroOrOne }; - - CliRootCommand rootCommand = new("Cross platform .NET Core code coverage tool") + 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") { Description = "Specifies behaviour of heuristic to ignore assemblies with missing source documents.", Arity = ArgumentArity.ZeroOrOne }; + 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, @@ -82,7 +82,7 @@ static async Task Main(string[] args) rootCommand.Add(new HelpOption()); rootCommand.Add(new VersionOption()); - ParseResult parseResult = CliParser.Parse(rootCommand, args); + ParseResult parseResult = CommandLineParser.Parse(rootCommand, args); rootCommand.SetAction(async (context) => { @@ -138,7 +138,7 @@ static async Task Main(string[] args) }); - CliConfiguration config = new(rootCommand); + CommandLineConfiguration config = new(rootCommand); await config.InvokeAsync(args).ConfigureAwait(false); Environment.Exit(s_exitCode); @@ -391,7 +391,7 @@ string sourceMappingFile } logger.LogError(errorMessageBuilder.ToString()); } - + return Task.FromResult(s_exitCode); } From 2e65a0df5d6fc3418287da25d16f592f4ce08d30 Mon Sep 17 00:00:00 2001 From: Bert Date: Thu, 23 Jan 2025 18:06:04 +0100 Subject: [PATCH 03/14] use DotNetCoreCLI@2 task for restore --- eng/build.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/eng/build.yml b/eng/build.yml index 570bd0f2c..1d5f4306e 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -15,8 +15,17 @@ 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' + +- 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 From b3ee5582bd334924f3d2116bbf140384446c2a95 Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 24 Jan 2025 08:32:30 +0100 Subject: [PATCH 04/14] activate forceReinstallCredentialProvider --- eng/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eng/build.yml b/eng/build.yml index 1d5f4306e..ef76396ed 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -18,6 +18,8 @@ steps: # Authenticate Azure DevOps NuGet feed - task: NuGetAuthenticate@1 displayName: 'Authenticate Azure DevOps NuGet feed' + inputs: + forceReinstallCredentialProvider: true - task: DotNetCoreCLI@2 displayName: Restore packages From 358b74e5c7797bc63011125e1d9d899996029edc Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 17 Feb 2025 11:20:01 +0100 Subject: [PATCH 05/14] Trigger Build From 12f0eef01ae399ec6c1971c00f35761ffbe07a6a Mon Sep 17 00:00:00 2001 From: Bert Date: Mon, 17 Feb 2025 11:34:57 +0100 Subject: [PATCH 06/14] remove nuget source dotnet-libraries --- Directory.Build.props | 5 ++--- src/coverlet.console/coverlet.console.csproj | 4 ---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 655455e62..aa2af0cc4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -17,10 +17,9 @@ 12.0 $(NoWarn);NU1507;NU5105;CS1591;NU1608;NU1900 true - + diff --git a/src/coverlet.console/coverlet.console.csproj b/src/coverlet.console/coverlet.console.csproj index bbfe4bbdc..6cdd85623 100644 --- a/src/coverlet.console/coverlet.console.csproj +++ b/src/coverlet.console/coverlet.console.csproj @@ -20,10 +20,6 @@ https://github.com/coverlet-coverage/coverlet MIT git - - https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json - https://api.nuget.org/v3/index.json; - From 04bdf5ff7cc228171373f977e139a0974303a75d Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 1 Apr 2025 10:21:20 +0200 Subject: [PATCH 07/14] use .NET SDK 8.0.114 --- eng/build.yml | 4 +++- global.json | 2 +- test/coverlet.integration.tests/Collectors.cs | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/eng/build.yml b/eng/build.yml index ef76396ed..b56116539 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: | @@ -21,6 +21,8 @@ steps: inputs: forceReinstallCredentialProvider: true +- script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" + - task: DotNetCoreCLI@2 displayName: Restore packages inputs: diff --git a/global.json b/global.json index bd1052c7a..1cd0393fc 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.113" + "version": "8.0.114" } } diff --git a/test/coverlet.integration.tests/Collectors.cs b/test/coverlet.integration.tests/Collectors.cs index bb9e76863..b334dca4c 100644 --- a/test/coverlet.integration.tests/Collectors.cs +++ b/test/coverlet.integration.tests/Collectors.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; @@ -84,7 +84,6 @@ public void TestVsTest_Test() // 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); From 1af5d934aab42e7a7bece2e1d261712a906823b6 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 1 Apr 2025 10:37:56 +0200 Subject: [PATCH 08/14] fix build --- eng/azure-pipelines-nightly.yml | 4 ++-- eng/build.yml | 2 +- test/coverlet.integration.tests/DotnetTool.cs | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/eng/azure-pipelines-nightly.yml b/eng/azure-pipelines-nightly.yml index 65b101fb4..d78680bd7 100644 --- a/eng/azure-pipelines-nightly.yml +++ b/eng/azure-pipelines-nightly.yml @@ -10,12 +10,12 @@ 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 +- script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" displayName: Restore packages - script: dotnet pack -c Release /p:PublicRelease=false diff --git a/eng/build.yml b/eng/build.yml index b56116539..cc15bc83c 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -21,7 +21,7 @@ steps: inputs: forceReinstallCredentialProvider: true -- script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" +- script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" -s "https://api.nuget.org/v3/index.json" - task: DotNetCoreCLI@2 displayName: Restore packages diff --git a/test/coverlet.integration.tests/DotnetTool.cs b/test/coverlet.integration.tests/DotnetTool.cs index b787ae462..d69f7ab31 100644 --- a/test/coverlet.integration.tests/DotnetTool.cs +++ b/test/coverlet.integration.tests/DotnetTool.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.IO; @@ -40,7 +40,7 @@ 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{publishedTestFile}\" --target \"dotnet\" --targetargs \"test {Path.Combine(clonedTemplateProject.ProjectRootPath, ClonedTemplateProject.ProjectFileName)} --no-build\" --include-test-assembly --output \"{outputPath}\"", out standardOutput, out standardError); + int result = 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); @@ -59,7 +59,7 @@ 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --output \"{outputPath}\"", out standardOutput, out standardError); + int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -79,7 +79,7 @@ public void StandAloneThreshold() 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --output \"{outputPath}\"", out standardOutput, out standardError); + int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -106,7 +106,7 @@ public void StandAloneThresholdLine() 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --output \"{outputPath}\"", out standardOutput, out standardError); + int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -133,7 +133,7 @@ public void StandAloneThresholdLineAndMethod() 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --threshold-type method --output \"{outputPath}\"", out standardOutput, out standardError); + int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --threshold-type method --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); From 0ab773a57a47b1cb5b38413b41ce9e86dcac5b00 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 1 Apr 2025 11:04:48 +0200 Subject: [PATCH 09/14] update version --- Directory.Packages.props | 10 +++++----- eng/azure-pipelines-nightly.yml | 2 +- eng/build.yml | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0f354aed8..90444d092 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -48,25 +48,25 @@ - + - + - + - + - + diff --git a/eng/azure-pipelines-nightly.yml b/eng/azure-pipelines-nightly.yml index d78680bd7..6c8d4ddbc 100644 --- a/eng/azure-pipelines-nightly.yml +++ b/eng/azure-pipelines-nightly.yml @@ -15,7 +15,7 @@ steps: - task: NuGetAuthenticate@1 displayName: Authenticate with NuGet feeds -- script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" +- script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" -s "https://api.nuget.org/v3/index.json" displayName: Restore packages - script: dotnet pack -c Release /p:PublicRelease=false diff --git a/eng/build.yml b/eng/build.yml index cc15bc83c..1eb8d4c1e 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -22,6 +22,7 @@ steps: forceReinstallCredentialProvider: true - script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" -s "https://api.nuget.org/v3/index.json" + displayName: Restore packages - task: DotNetCoreCLI@2 displayName: Restore packages From e769b908f7b2886088583308de53e99d60c37973 Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 1 Apr 2025 11:11:06 +0200 Subject: [PATCH 10/14] disable task DotNetCoreCLI@2 --- eng/build.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/build.yml b/eng/build.yml index 1eb8d4c1e..46ea65097 100644 --- a/eng/build.yml +++ b/eng/build.yml @@ -21,16 +21,16 @@ steps: inputs: forceReinstallCredentialProvider: true -- script: dotnet restore -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" -s "https://api.nuget.org/v3/index.json" +- script: dotnet restore -v n -s "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-libraries/nuget/v3/index.json" -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' +# - 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 From 8f42963a542ec9b8991445ecc9ac6d4cd5b76b4d Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 1 Apr 2025 12:05:11 +0200 Subject: [PATCH 11/14] add TheUntestedMethod for threshold tests --- .../DeepThought.cs | 23 +++++++++++++----- test/coverlet.integration.tests/Msbuild.cs | 24 +++++++++---------- 2 files changed, 29 insertions(+), 18 deletions(-) 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/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) { From ebe32909f54e2643d690a5861102d4518e51016d Mon Sep 17 00:00:00 2001 From: Bert Date: Tue, 1 Apr 2025 12:20:05 +0200 Subject: [PATCH 12/14] rename result to cmdExitCode --- test/coverlet.integration.tests/DotnetTool.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/coverlet.integration.tests/DotnetTool.cs b/test/coverlet.integration.tests/DotnetTool.cs index d69f7ab31..722d1dbaa 100644 --- a/test/coverlet.integration.tests/DotnetTool.cs +++ b/test/coverlet.integration.tests/DotnetTool.cs @@ -40,14 +40,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")); - int result = 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, result); + Assert.Equal((int)CommandExitCodes.Success, cmdExitCode); } [Fact] @@ -59,7 +59,7 @@ 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")); - int result = 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); @@ -67,7 +67,7 @@ public void StandAlone() //Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); - Assert.Equal((int)CommandExitCodes.Success, result); + Assert.Equal((int)CommandExitCodes.Success, cmdExitCode); } [Fact] @@ -79,7 +79,7 @@ public void StandAloneThreshold() 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --output \"{outputPath}\"", out string standardOutput, out string standardError); + int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -92,7 +92,7 @@ public void StandAloneThreshold() //Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); - Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, result); + 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); } @@ -106,7 +106,7 @@ public void StandAloneThresholdLine() 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --output \"{outputPath}\"", out string standardOutput, out string standardError); + int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -119,7 +119,7 @@ public void StandAloneThresholdLine() // Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); - Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, result); + 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); } @@ -133,7 +133,7 @@ public void StandAloneThresholdLineAndMethod() 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")); - int result = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --threshold-type method --output \"{outputPath}\"", out string standardOutput, out string standardError); + int cmdExitCode = RunCommand(coverletToolCommandPath, $"\"{Path.GetDirectoryName(publishedTestFile)}\" --target \"dotnet\" --targetargs \"{publishedTestFile}\" --threshold 80 --threshold-type line --threshold-type method --output \"{outputPath}\"", out string standardOutput, out string standardError); if (!string.IsNullOrEmpty(standardError)) { _output.WriteLine(standardError); @@ -146,7 +146,7 @@ public void StandAloneThresholdLineAndMethod() // Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); - Assert.Equal((int)CommandExitCodes.CoverageBelowThreshold, result); + 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); } From 7d76c17e2321974abf43f649aa559bf071421878 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 2 Apr 2025 08:26:14 +0200 Subject: [PATCH 13/14] cleanup - remove commented code --- src/coverlet.console/coverlet.console.csproj | 3 --- test/coverlet.integration.tests/DotnetTool.cs | 4 ---- 2 files changed, 7 deletions(-) diff --git a/src/coverlet.console/coverlet.console.csproj b/src/coverlet.console/coverlet.console.csproj index 6cdd85623..cd3f6afc8 100644 --- a/src/coverlet.console/coverlet.console.csproj +++ b/src/coverlet.console/coverlet.console.csproj @@ -24,9 +24,6 @@ - - - diff --git a/test/coverlet.integration.tests/DotnetTool.cs b/test/coverlet.integration.tests/DotnetTool.cs index 722d1dbaa..da1d98cbd 100644 --- a/test/coverlet.integration.tests/DotnetTool.cs +++ b/test/coverlet.integration.tests/DotnetTool.cs @@ -64,7 +64,6 @@ public void StandAlone() { _output.WriteLine(standardError); } - //Assert.Contains("Hello World!", standardOutput); Assert.True(File.Exists(outputPath)); AssertCoverage(clonedTemplateProject, standardOutput: standardOutput); Assert.Equal((int)CommandExitCodes.Success, cmdExitCode); @@ -89,7 +88,6 @@ 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); @@ -116,7 +114,6 @@ 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); @@ -143,7 +140,6 @@ 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); From cc46e3100cd5d6d8560955dd2bdb98ecef85f702 Mon Sep 17 00:00:00 2001 From: Bert Date: Fri, 4 Apr 2025 09:11:51 +0200 Subject: [PATCH 14/14] remove obsolete exit code --- Documentation/GlobalTool.md | 1 - src/coverlet.console/ExitCodes.cs | 4 ---- 2 files changed, 5 deletions(-) diff --git a/Documentation/GlobalTool.md b/Documentation/GlobalTool.md index e91f67c9d..6b9161b4f 100644 --- a/Documentation/GlobalTool.md +++ b/Documentation/GlobalTool.md @@ -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/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 }