diff --git a/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs b/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs index bb74ba5f17..f98ab9b6f9 100644 --- a/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs +++ b/src/Cake.Common.Tests/Unit/Tools/DotCover/Cover/DotCoverCovererTests.cs @@ -4,6 +4,7 @@ using Cake.Common.Tests.Fixtures.Tools.DotCover.Cover; using Cake.Common.Tools.DotCover; +using Cake.Common.Tools.DotCover.Cover; using Cake.Common.Tools.NUnit; using Cake.Common.Tools.XUnit; using Cake.Core.IO; @@ -96,9 +97,9 @@ public void Should_Capture_Tool_And_Arguments_From_Action() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); } [Theory] @@ -122,8 +123,8 @@ public void Should_Not_Capture_Arguments_From_Action_If_Excluded(string argument var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); } [Fact] @@ -137,10 +138,10 @@ public void Should_Append_TargetWorkingDir() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + - "/TargetWorkingDir=\"/Working\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--target-working-directory \"/Working\"", result.Args); } [Fact] @@ -155,9 +156,9 @@ public void Should_Append_Scope() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/Scope=\"/Working/*.dll;/Some/**/Other/*.dll\"", result.Args); } @@ -173,9 +174,9 @@ public void Should_Append_Filters() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/Filters=\"+:module=Test.*;-:myassembly\"", result.Args); } @@ -191,9 +192,9 @@ public void Should_Append_AttributeFilters() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/AttributeFilters=\"filter1;filter2\"", result.Args); } @@ -208,9 +209,9 @@ public void Should_Append_DisableDefaultFilters() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/DisableDefaultFilters", result.Args); } @@ -226,9 +227,9 @@ public void Should_Append_ProcessFilters() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/Test.exe\" " + - "/TargetArguments=\"-argument\" " + - "/Output=\"/Working/result.dcvr\" " + + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + "/ProcessFilters=\"+:test.exe;-:sqlservr.exe\"", result.Args); } @@ -249,9 +250,9 @@ public void Should_Capture_XUnit() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/xunit.console.exe\" " + - "/TargetArguments=\"\\\"/Working/Test.dll\\\" -noshadow\" " + - "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/xunit.console.exe\" " + + "--target-arguments \"\\\"/Working/Test.dll\\\" -noshadow\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); } [Fact] @@ -271,9 +272,9 @@ public void Should_Capture_NUnit() var result = fixture.Run(); // Then - Assert.Equal("Cover /TargetExecutable=\"/Working/tools/nunit-console.exe\" " + - "/TargetArguments=\"\\\"/Working/Test.dll\\\" -noshadow\" " + - "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.Equal("cover --target-executable \"/Working/tools/nunit-console.exe\" " + + "--target-arguments \"\\\"/Working/Test.dll\\\" -noshadow\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); } [Fact] @@ -287,10 +288,252 @@ public void Should_Append_ConfigurationFile() var result = fixture.Run(); // Then - Assert.Equal("Cover \"/Working/config.xml\" /TargetExecutable=\"/Working/tools/Test.exe\" " + + Assert.Equal("cover \"/Working/config.xml\" --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); + } + + [Fact] + public void Should_Append_ExcludeAssemblies() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithExcludeAssembly("*.Tests") + .WithExcludeAssembly("Test.*"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--exclude-assemblies \"*.Tests,Test.*\"", result.Args); + } + + [Fact] + public void Should_Append_ExcludeAttributes() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithExcludeAttribute("System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute") + .WithExcludeAttribute("Custom.*Attribute"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--exclude-attributes \"System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute,Custom.*Attribute\"", result.Args); + } + + [Fact] + public void Should_Append_ExcludeProcesses() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithExcludeProcess("test.exe") + .WithExcludeProcess("*.vshost.exe"); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--exclude-processes \"test.exe,*.vshost.exe\"", result.Args); + } + + [Fact] + public void Should_Append_JsonReportOutput() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithJsonReportOutput(new FilePath("/Working/coverage.json")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--json-report-output \"/Working/coverage.json\"", result.Args); + } + + [Fact] + public void Should_Append_JsonReportCoveringTestsScope() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithJsonReportCoveringTestsScope(DotCoverReportScope.Method); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--json-report-covering-tests-scope \"method\"", result.Args); + } + + [Fact] + public void Should_Append_XmlReportOutput() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithXmlReportOutput(new FilePath("/Working/coverage.xml")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--xml-report-output \"/Working/coverage.xml\"", result.Args); + } + + [Fact] + public void Should_Append_XmlReportCoveringTestsScope() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithXmlReportCoveringTestsScope(DotCoverReportScope.Statement); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--xml-report-covering-tests-scope \"statement\"", result.Args); + } + + [Fact] + public void Should_Append_TemporaryDirectory() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithTemporaryDirectory(new DirectoryPath("/Working/temp")); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--temporary-directory \"/Working/temp\"", result.Args); + } + + [Fact] + public void Should_Append_UseApi() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithUseApi(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--use-api", result.Args); + } + + [Fact] + public void Should_Append_NoNGen() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithNoNGen(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\" " + + "--no-ngen", result.Args); + } + + [Fact] + public void Should_Use_Legacy_Syntax_When_Enabled() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithLegacySyntax(); + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover /TargetExecutable=\"/Working/tools/Test.exe\" " + "/TargetArguments=\"-argument\" " + "/Output=\"/Working/result.dcvr\"", result.Args); } + + [Fact] + public void Should_Use_New_Syntax_By_Default() + { + // Given + var fixture = new DotCoverCovererFixture(); + // Don't set UseLegacySyntax - should default to false + + // When + var result = fixture.Run(); + + // Then + Assert.Equal("cover --target-executable \"/Working/tools/Test.exe\" " + + "--target-arguments \"-argument\" " + + "--snapshot-output \"/Working/result.dcvr\"", result.Args); + } + + [Fact] + public void Should_Not_Support_New_Features_In_Legacy_Mode() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithLegacySyntax() + .WithJsonReportOutput(new FilePath("/Working/report.json")) + .WithExcludeAssembly("*.Tests"); + + // When + var result = fixture.Run(); + + // Then - New format features should not appear in legacy mode + Assert.Equal("cover /TargetExecutable=\"/Working/tools/Test.exe\" " + + "/TargetArguments=\"-argument\" " + + "/Output=\"/Working/result.dcvr\"", result.Args); + Assert.DoesNotContain("--json-report-output", result.Args); + Assert.DoesNotContain("--exclude-assemblies", result.Args); + } + + [Fact] + public void Should_Support_New_Features_In_New_Mode() + { + // Given + var fixture = new DotCoverCovererFixture(); + fixture.Settings.WithJsonReportOutput(new FilePath("/Working/report.json")) + .WithExcludeAssembly("*.Tests"); + + // When + var result = fixture.Run(); + + // Then - New format features should appear + Assert.Contains("--json-report-output \"/Working/report.json\"", result.Args); + Assert.Contains("--exclude-assemblies \"*.Tests\"", result.Args); + Assert.Contains("--snapshot-output", result.Args); + } } } } \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs index 6fd5c47a94..eadc7ce291 100644 --- a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs +++ b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettings.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Cake.Core.IO; + namespace Cake.Common.Tools.DotCover.Cover { /// @@ -9,5 +11,54 @@ namespace Cake.Common.Tools.DotCover.Cover /// public sealed class DotCoverCoverSettings : DotCoverCoverageSettings { + /// + /// Gets or sets the path to save formatted JSON report. + /// This represents the --json-report-output option. + /// + public FilePath JsonReportOutput { get; set; } + + /// + /// Gets or sets the granularity for including covering tests in JSON reports. + /// This represents the --json-report-covering-tests-scope option. + /// + public DotCoverReportScope? JsonReportCoveringTestsScope { get; set; } + + /// + /// Gets or sets the path to save formatted XML report. + /// This represents the --xml-report-output option. + /// + public FilePath XmlReportOutput { get; set; } + + /// + /// Gets or sets the granularity for including covering tests in XML reports. + /// This represents the --xml-report-covering-tests-scope option. + /// + public DotCoverReportScope? XmlReportCoveringTestsScope { get; set; } + + /// + /// Gets or sets the directory for temporary files. + /// This represents the --temporary-directory option. + /// + public DirectoryPath TemporaryDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to control the coverage session using the profiler API. + /// This represents the --use-api option. + /// + public bool UseApi { get; set; } + + /// + /// Gets or sets a value indicating whether to disable loading of NGen images during coverage. + /// This represents the --no-ngen option. + /// + public bool NoNGen { get; set; } + + /// + /// Gets or sets a value indicating whether to use the legacy command syntax. + /// When true, uses old format like '/TargetExecutable="/path"'. + /// When false, uses new format like '--target-executable "/path"'. + /// Default is false (new format). + /// + public bool UseLegacySyntax { get; set; } } } \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettingsExtensions.cs b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettingsExtensions.cs new file mode 100644 index 0000000000..f8b5263f6c --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverSettingsExtensions.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Cake.Core.IO; + +namespace Cake.Common.Tools.DotCover.Cover +{ + /// + /// Contains extensions for . + /// + public static class DotCoverCoverSettingsExtensions + { + /// + /// Sets the JSON report output path. + /// + /// The settings. + /// The JSON report output path. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithJsonReportOutput(this DotCoverCoverSettings settings, FilePath outputPath) + { + ArgumentNullException.ThrowIfNull(settings); + settings.JsonReportOutput = outputPath; + return settings; + } + + /// + /// Sets the JSON report covering tests scope. + /// + /// The settings. + /// The granularity for including covering tests in JSON reports. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithJsonReportCoveringTestsScope(this DotCoverCoverSettings settings, DotCoverReportScope scope) + { + ArgumentNullException.ThrowIfNull(settings); + settings.JsonReportCoveringTestsScope = scope; + return settings; + } + + /// + /// Sets the XML report output path. + /// + /// The settings. + /// The XML report output path. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithXmlReportOutput(this DotCoverCoverSettings settings, FilePath outputPath) + { + ArgumentNullException.ThrowIfNull(settings); + settings.XmlReportOutput = outputPath; + return settings; + } + + /// + /// Sets the XML report covering tests scope. + /// + /// The settings. + /// The granularity for including covering tests in XML reports. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithXmlReportCoveringTestsScope(this DotCoverCoverSettings settings, DotCoverReportScope scope) + { + ArgumentNullException.ThrowIfNull(settings); + settings.XmlReportCoveringTestsScope = scope; + return settings; + } + + /// + /// Sets the temporary directory for files. + /// + /// The settings. + /// The temporary directory path. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithTemporaryDirectory(this DotCoverCoverSettings settings, DirectoryPath directory) + { + ArgumentNullException.ThrowIfNull(settings); + settings.TemporaryDirectory = directory; + return settings; + } + + /// + /// Enables control of the coverage session using the profiler API. + /// + /// The settings. + /// Whether to use the API. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithUseApi(this DotCoverCoverSettings settings, bool useApi = true) + { + ArgumentNullException.ThrowIfNull(settings); + settings.UseApi = useApi; + return settings; + } + + /// + /// Disables loading of NGen images during coverage. + /// + /// The settings. + /// Whether to disable NGen. + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithNoNGen(this DotCoverCoverSettings settings, bool noNGen = true) + { + ArgumentNullException.ThrowIfNull(settings); + settings.NoNGen = noNGen; + return settings; + } + + /// + /// Configures whether to use legacy command syntax. + /// + /// The settings. + /// Whether to use legacy syntax. Default is false (new syntax). + /// The same instance so that multiple calls can be chained. + public static DotCoverCoverSettings WithLegacySyntax(this DotCoverCoverSettings settings, bool useLegacySyntax = true) + { + ArgumentNullException.ThrowIfNull(settings); + settings.UseLegacySyntax = useLegacySyntax; + return settings; + } + } +} \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs index b37c455853..c3d53ab657 100644 --- a/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs +++ b/src/Cake.Common/Tools/DotCover/Cover/DotCoverCoverer.cs @@ -61,23 +61,163 @@ private ProcessArgumentBuilder GetArguments( { var builder = new ProcessArgumentBuilder(); - builder.Append("Cover"); + // Command name - always lowercase 'cover' for both formats + builder.Append("cover"); // Set configuration file if exists. GetConfigurationFileArgument(settings).CopyTo(builder); - // Get Target executable arguments - GetTargetArguments(context, action).CopyTo(builder); + if (settings.UseLegacySyntax) + { + // Use legacy format + GetTargetArguments(context, action).CopyTo(builder); + // Set the output file - legacy format + outputPath = outputPath.MakeAbsolute(_environment); + builder.AppendSwitch("/Output", "=", outputPath.FullPath.Quote()); + // Get Coverage arguments - legacy format + GetCoverageArguments(settings).CopyTo(builder); + // Get base arguments - legacy format + GetArguments(settings).CopyTo(builder); + } + else + { + // Use new format + GetCoverTargetArguments(context, action).CopyTo(builder); + // Set the output file - new format + outputPath = outputPath.MakeAbsolute(_environment); + builder.AppendSwitch("--snapshot-output", outputPath.FullPath.Quote()); + // Get Coverage arguments - new format + GetCoverCoverageArguments(settings).CopyTo(builder); + // New report options (only available in new format) + if (settings.JsonReportOutput != null) + { + builder.AppendSwitch("--json-report-output", settings.JsonReportOutput.MakeAbsolute(_environment).FullPath.Quote()); + } - // Set the output file. - outputPath = outputPath.MakeAbsolute(_environment); - builder.AppendSwitch("/Output", "=", outputPath.FullPath.Quote()); + if (settings.JsonReportCoveringTestsScope.HasValue) + { + builder.AppendSwitch("--json-report-covering-tests-scope", settings.JsonReportCoveringTestsScope.Value.ToString().ToLowerInvariant().Quote()); + } - // Get Coverage arguments - GetCoverageArguments(settings).CopyTo(builder); + if (settings.XmlReportOutput != null) + { + builder.AppendSwitch("--xml-report-output", settings.XmlReportOutput.MakeAbsolute(_environment).FullPath.Quote()); + } - // Get base arguments - GetArguments(settings).CopyTo(builder); + if (settings.XmlReportCoveringTestsScope.HasValue) + { + builder.AppendSwitch("--xml-report-covering-tests-scope", settings.XmlReportCoveringTestsScope.Value.ToString().ToLowerInvariant().Quote()); + } + + if (settings.TemporaryDirectory != null) + { + builder.AppendSwitch("--temporary-directory", settings.TemporaryDirectory.MakeAbsolute(_environment).FullPath.Quote()); + } + + if (settings.UseApi) + { + builder.Append("--use-api"); + } + + if (settings.NoNGen) + { + builder.Append("--no-ngen"); + } + + // Get base arguments - new format + GetCoverArguments(settings).CopyTo(builder); + } + + return builder; + } + + /// + /// Get arguments from coverage settings for Cover command (using new format). + /// + /// The settings. + /// The process arguments. + private ProcessArgumentBuilder GetCoverCoverageArguments(DotCoverCoverageSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // TargetWorkingDir - using new format for Cover command + if (settings.TargetWorkingDir != null) + { + builder.AppendSwitch("--target-working-directory", settings.TargetWorkingDir.MakeAbsolute(_environment).FullPath.Quote()); + } + + // New filtering options (only available in new format) + if (settings.ExcludeAssemblies.Count > 0) + { + var excludeAssemblies = string.Join(',', settings.ExcludeAssemblies); + builder.AppendSwitch("--exclude-assemblies", excludeAssemblies.Quote()); + } + + if (settings.ExcludeAttributes.Count > 0) + { + var excludeAttributes = string.Join(',', settings.ExcludeAttributes); + builder.AppendSwitch("--exclude-attributes", excludeAttributes.Quote()); + } + + if (settings.ExcludeProcesses.Count > 0) + { + var excludeProcesses = string.Join(',', settings.ExcludeProcesses); + builder.AppendSwitch("--exclude-processes", excludeProcesses.Quote()); + } + + // Legacy filtering options (maintain backward compatibility with old format) + // Scope + if (settings.Scope.Count > 0) + { + var scope = string.Join(';', settings.Scope); + builder.AppendSwitch("/Scope", "=", scope.Quote()); + } + + // Filters + if (settings.Filters.Count > 0) + { + var filters = string.Join(';', settings.Filters); + builder.AppendSwitch("/Filters", "=", filters.Quote()); + } + + // AttributeFilters + if (settings.AttributeFilters.Count > 0) + { + var attributeFilters = string.Join(';', settings.AttributeFilters); + builder.AppendSwitch("/AttributeFilters", "=", attributeFilters.Quote()); + } + + // ProcessFilters + if (settings.ProcessFilters.Count > 0) + { + var processFilters = string.Join(';', settings.ProcessFilters); + builder.AppendSwitch("/ProcessFilters", "=", processFilters.Quote()); + } + + // DisableDefaultFilters + if (settings.DisableDefaultFilters) + { + builder.Append("/DisableDefaultFilters"); + } + + return builder; + } + + /// + /// Get arguments from global settings for Cover command (using new format). + /// + /// The settings. + /// The process arguments. + private ProcessArgumentBuilder GetCoverArguments(DotCoverSettings settings) + { + var builder = new ProcessArgumentBuilder(); + + // LogFile - using new format for Cover command + if (settings.LogFile != null) + { + var logFilePath = settings.LogFile.MakeAbsolute(_environment); + builder.AppendSwitch("--log-file", logFilePath.FullPath.Quote()); + } return builder; } diff --git a/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettings.cs b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettings.cs index a499d5d944..2e37d78c3a 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettings.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettings.cs @@ -17,10 +17,13 @@ public abstract class DotCoverCoverageSettings : DotCoverSettings private readonly HashSet _filters; private readonly HashSet _processFilters; private readonly HashSet _attributeFilters; + private readonly HashSet _excludeAssemblies; + private readonly HashSet _excludeAttributes; + private readonly HashSet _excludeProcesses; /// /// Gets or sets program working directory - /// This represents the /TargetWorkingDir option. + /// This represents the --target-working-directory option for Cover command, /TargetWorkingDir for others. /// public DirectoryPath TargetWorkingDir { get; set; } @@ -65,6 +68,34 @@ public ISet ProcessFilters get { return _processFilters; } } + /// + /// Gets assembly names to exclude from analysis. Wildcards (*) allowed. + /// This represents the --exclude-assemblies option. + /// + public ISet ExcludeAssemblies + { + get { return _excludeAssemblies; } + } + + /// + /// Gets fully qualified attribute names to exclude from analysis. Wildcards (*) allowed. + /// Code marked with these attributes will be excluded from coverage. + /// This represents the --exclude-attributes option. + /// + public ISet ExcludeAttributes + { + get { return _excludeAttributes; } + } + + /// + /// Gets process names to ignore during analysis. Wildcards (*) allowed. + /// This represents the --exclude-processes option. + /// + public ISet ExcludeProcesses + { + get { return _excludeProcesses; } + } + /// /// Gets or sets a value indicating whether the default (automatically added) filters should be disabled /// This represents the /DisableDefaultFilters option. @@ -80,6 +111,9 @@ protected DotCoverCoverageSettings() _filters = new HashSet(StringComparer.OrdinalIgnoreCase); _attributeFilters = new HashSet(StringComparer.OrdinalIgnoreCase); _processFilters = new HashSet(StringComparer.OrdinalIgnoreCase); + _excludeAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); + _excludeAttributes = new HashSet(StringComparer.OrdinalIgnoreCase); + _excludeProcesses = new HashSet(StringComparer.OrdinalIgnoreCase); } } } \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettingsExtensions.cs b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettingsExtensions.cs index 5c8b0e4d8a..b2b02b94b5 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettingsExtensions.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverCoverageSettingsExtensions.cs @@ -66,5 +66,47 @@ public static T WithProcessFilter(this T settings, string filter) where T : D settings.ProcessFilters.Add(filter); return settings; } + + /// + /// Adds an assembly name to exclude from analysis. + /// + /// The settings. + /// The assembly name to exclude. Wildcards (*) are allowed. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithExcludeAssembly(this T settings, string assemblyName) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.ExcludeAssemblies.Add(assemblyName); + return settings; + } + + /// + /// Adds a fully qualified attribute name to exclude from analysis. + /// + /// The settings. + /// The fully qualified attribute name. Wildcards (*) are allowed. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithExcludeAttribute(this T settings, string attributeName) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.ExcludeAttributes.Add(attributeName); + return settings; + } + + /// + /// Adds a process name to ignore during analysis. + /// + /// The settings. + /// The process name to ignore. Wildcards (*) are allowed. + /// The settings type, derived from . + /// The same instance so that multiple calls can be chained. + public static T WithExcludeProcess(this T settings, string processName) where T : DotCoverCoverageSettings + { + ArgumentNullException.ThrowIfNull(settings); + settings.ExcludeProcesses.Add(processName); + return settings; + } } } \ No newline at end of file diff --git a/src/Cake.Common/Tools/DotCover/DotCoverCoverageTool.cs b/src/Cake.Common/Tools/DotCover/DotCoverCoverageTool.cs index fafa0d4a16..030fe51e71 100644 --- a/src/Cake.Common/Tools/DotCover/DotCoverCoverageTool.cs +++ b/src/Cake.Common/Tools/DotCover/DotCoverCoverageTool.cs @@ -33,6 +33,35 @@ public DotCoverCoverageTool( _environment = environment; } + /// + /// Get arguments from the target executable for Cover command (using new format). + /// + /// The context. + /// The action to run DotCover for. + /// The process arguments. + protected ProcessArgumentBuilder GetCoverTargetArguments(ICakeContext context, Action action) + { + // Run the tool using the interceptor. + var targetContext = InterceptAction(context, action); + + var builder = new ProcessArgumentBuilder(); + // The target application to call. + builder.AppendSwitch("--target-executable", targetContext.FilePath.FullPath.Quote()); + + // The arguments to the target application. + if (targetContext.Settings != null && targetContext.Settings.Arguments != null) + { + var arguments = targetContext.Settings.Arguments.Render(); + if (!string.IsNullOrWhiteSpace(arguments)) + { + arguments = arguments.Replace("\"", "\\\""); + builder.AppendSwitch("--target-arguments", arguments.Quote()); + } + } + + return builder; + } + /// /// Get arguments from the target executable. /// @@ -99,7 +128,7 @@ protected ProcessArgumentBuilder GetCoverageArguments(DotCoverCoverageSettings s builder.AppendSwitch("/AttributeFilters", "=", attributeFilters.Quote()); } - // Filters + // ProcessFilters if (settings.ProcessFilters.Count > 0) { var processFilters = string.Join(';', settings.ProcessFilters); @@ -131,4 +160,4 @@ private static DotCoverContext InterceptAction( return interceptor; } } -} \ No newline at end of file +} diff --git a/src/Cake.Common/Tools/DotCover/DotCoverReportScope.cs b/src/Cake.Common/Tools/DotCover/DotCoverReportScope.cs new file mode 100644 index 0000000000..f7753a2446 --- /dev/null +++ b/src/Cake.Common/Tools/DotCover/DotCoverReportScope.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Cake.Common.Tools.DotCover +{ + /// + /// Represents the granularity for including covering tests in reports. + /// + public enum DotCoverReportScope + { + /// + /// No covering tests included. + /// + None, + + /// + /// Include covering tests at assembly level. + /// + Assembly, + + /// + /// Include covering tests at type level. + /// + Type, + + /// + /// Include covering tests at method level. + /// + Method, + + /// + /// Include covering tests at statement level. + /// + Statement + } +} \ No newline at end of file