From d4ed3a1bc2cf7fbb323e0896776dcc87d68c776c Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 Mar 2025 11:09:37 +0000 Subject: [PATCH 1/8] OpenCover include when necessary - tests to come --- .../OpenCoverExeArgumentsProvider.cs | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs index 785e2531..45b3cdd8 100644 --- a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs +++ b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; +using System.Text.RegularExpressions; namespace FineCodeCoverage.Engine.OpenCover { @@ -22,16 +23,61 @@ public static string AddEscapeQuotes(string arg) [Export(typeof(IOpenCoverExeArgumentsProvider))] internal class OpenCoverExeArgumentsProvider : IOpenCoverExeArgumentsProvider { + private static readonly Regex AssemblyRegex = new Regex(@"^\[(.*?)\]", RegexOptions.Compiled); private enum Delimiter { Semicolon, Space} + + private static bool IncludeTestAssemblyOnlyWhenNecessary( + List includedReferencedProjects, + IEnumerable inclusions, + List exclusions, + string testAssemblyName) + { + return HasInclusions(inclusions, includedReferencedProjects) && !IsTestAssemblySpecificallyExcluded(exclusions, testAssemblyName); + } + + private static string GetAssemblyFromFilter(string input) + { + Match match = AssemblyRegex.Match(input); + return match.Success ? match.Groups[1].Value : null; + } + + private static bool IsTestAssemblySpecificallyExcluded(List exclusions, string testAssemblyName) + { + // not interested in an exclude all + + // note that it could also have been excluded with a wild card - for now for simplicity we are not checking that + foreach (var exclusion in exclusions) + { + var assembly = GetAssemblyFromFilter(exclusion); + if(assembly == testAssemblyName) + { + return true; + } + } + return false; + } + + private static bool HasInclusions(IEnumerable includes, List includedReferencedProjects) + { + return includes.Any() || includedReferencedProjects.Any(); + } + + + private void AddFilter(ICoverageProject project, List opencoverSettings) { + var includes = SanitizeExcludesOrIncludes(project.Settings.Include); + var excludes = SanitizeExcludesOrIncludes(project.Settings.Exclude).ToList(); + var includedModules = project.IncludedReferencedProjects.Select(rp => rp.AssemblyName).ToList(); - if (project.Settings.IncludeTestAssembly) + if (project.Settings.IncludeTestAssembly && + IncludeTestAssemblyOnlyWhenNecessary(project.IncludedReferencedProjects, includes, excludes, project.ProjectName)) { includedModules.Add(project.ProjectName); } - var includeFilters = GetExcludesOrIncludes(project.Settings.Include, includedModules, true); - var excludeFilters = GetExcludesOrIncludes(project.Settings.Exclude, project.ExcludedReferencedProjects.Select(rp => rp.AssemblyName), false); + + var includeFilters = GetExcludesOrIncludes(includes, includedModules, true); + var excludeFilters = GetExcludesOrIncludes(excludes, project.ExcludedReferencedProjects.Select(rp => rp.AssemblyName), false); AddIncludeAllIfExcludingWithoutIncludes(); var filters = includeFilters.Concat(excludeFilters).ToList(); SafeAddToSettingsDelimitedIfAny(opencoverSettings, "filter", filters, Delimiter.Space); @@ -49,9 +95,9 @@ List GetExcludesOrIncludes( { var excludeOrIncludeFilters = new List(); var prefix = IncludeSymbol(isInclude); - var sanitizedExcludesOrIncludes = SanitizeExcludesOrIncludes(excludesOrIncludes); + //var sanitizedExcludesOrIncludes = SanitizeExcludesOrIncludes(excludesOrIncludes); - foreach (var value in sanitizedExcludesOrIncludes) + foreach (var value in excludesOrIncludes) { excludeOrIncludeFilters.Add($@"{prefix}{value}"); } From 7e0dd27b408293c1e8d7ec07f0360cbfeaf1acc6 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 Mar 2025 13:33:00 +0000 Subject: [PATCH 2/8] refactor and tests for OpenCover --- .../OpenCoverExeArgumentsProvider_Tests.cs | 52 ++++++++++++++++++- .../OpenCoverExeArgumentsProvider.cs | 17 ++---- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs b/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs index 127eabb4..19961be6 100644 --- a/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs +++ b/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs @@ -1,8 +1,10 @@ using FineCodeCoverage.Engine.Model; using FineCodeCoverage.Engine.OpenCover; using FineCodeCoverage.Options; +using Microsoft.VisualStudio.Experimentation; using Moq; using NUnit.Framework; +using System; using System.Collections.Generic; using System.Linq; @@ -95,16 +97,62 @@ public void Should_Safely_Include_The_TestDLLFile_In_The_TargetArgs() } [Test] - public void Should_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_IncludeTestAssembly() + public void Should_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_IncludeTestAssembly_And_Required() { var openCoverExeArgumentsProvider = new OpenCoverExeArgumentsProvider(); var mockCoverageProject = SafeMockCoverageProject(); mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.IncludeTestAssembly).Returns(true); + mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.Include).Returns(new string[] { "[anassembly]*"}); mockCoverageProject.SetupGet(coverageProject => coverageProject.ProjectName).Returns("TheTestName"); var arguments = openCoverExeArgumentsProvider.Provide(mockCoverageProject.Object, ""); - AssertHasEscapedSetting(arguments, "-filter:+[TheTestName]*"); + var filters = GetFilters(arguments); + Assert.That(filters, Is.EquivalentTo(new string[] { "+[TheTestName]*", "+[anassembly]*" })); + } + + [Test] + public void Should_Not_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_IncludeTestAssembly_And_No_Other_Includes() + { + var openCoverExeArgumentsProvider = new OpenCoverExeArgumentsProvider(); + var mockCoverageProject = SafeMockCoverageProject(); + mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.IncludeTestAssembly).Returns(true); + mockCoverageProject.SetupGet(coverageProject => coverageProject.ProjectName).Returns("TheTestName"); + + var arguments = openCoverExeArgumentsProvider.Provide(mockCoverageProject.Object, ""); + + var filters = GetFilters(arguments); + Assert.IsEmpty(GetFilters(arguments)); + } + + [Test] + public void Should_Not_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_IncludeTestAssembly_And_Explicitly_Excluded() + { + var openCoverExeArgumentsProvider = new OpenCoverExeArgumentsProvider(); + var mockCoverageProject = SafeMockCoverageProject(); + mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.IncludeTestAssembly).Returns(true); + mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.Include).Returns(new string[] { "[anassembly]*" }); + mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.Exclude).Returns(new string[] { "[TheTestName]*" }); + mockCoverageProject.SetupGet(coverageProject => coverageProject.ProjectName).Returns("TheTestName"); + + var arguments = openCoverExeArgumentsProvider.Provide(mockCoverageProject.Object, ""); + var filters = GetFilters(arguments); + Assert.That(filters, Is.EquivalentTo(new string[] { "+[anassembly]*", "-[TheTestName]*" })); + } + + private IEnumerable GetFilters(IEnumerable arguments) + { + var filterMatch = "-filter:"; + var filter = arguments.FirstOrDefault(arg => arg.StartsWith($@"""{filterMatch}")); + if (filter == null) + { + return Enumerable.Empty(); + } + if (!filter.EndsWith("\"")) + { + throw new Exception("filter should be escaped"); + } + return filter.Replace("\"", "").Substring(filterMatch.Length).Split(' '); } [Test] diff --git a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs index 45b3cdd8..2d30fc5d 100644 --- a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs +++ b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs @@ -62,8 +62,6 @@ private static bool HasInclusions(IEnumerable includes, List opencoverSettings) { var includes = SanitizeExcludesOrIncludes(project.Settings.Include); @@ -94,28 +92,19 @@ List GetExcludesOrIncludes( IEnumerable excludesOrIncludes,IEnumerable moduleExcludesOrIncludes, bool isInclude) { var excludeOrIncludeFilters = new List(); - var prefix = IncludeSymbol(isInclude); - //var sanitizedExcludesOrIncludes = SanitizeExcludesOrIncludes(excludesOrIncludes); + var includeExcludeSymbol = isInclude ? "+" : "-"; foreach (var value in excludesOrIncludes) { - excludeOrIncludeFilters.Add($@"{prefix}{value}"); + excludeOrIncludeFilters.Add($@"{includeExcludeSymbol}{value}"); } foreach (var moduleExcludeOrInclude in moduleExcludesOrIncludes) { - excludeOrIncludeFilters.Add(IncludeOrExcludeModule(isInclude, moduleExcludeOrInclude)); + excludeOrIncludeFilters.Add($"{includeExcludeSymbol}[{moduleExcludeOrInclude}]*"); } return excludeOrIncludeFilters.Distinct().ToList(); } - - string IncludeOrExcludeModule(bool include,string moduleFilter,string classFilter = "*") - { - var filter = IncludeSymbol(include); - return $"{filter}[{moduleFilter}]{classFilter}"; - } - - string IncludeSymbol(bool include) => include ? "+" : "-"; } private IEnumerable SanitizeExcludesOrIncludes(IEnumerable excludesOrIncludes) From d4acf1a950b507ca4aa11324b008cf5a24d195b3 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 Mar 2025 15:39:31 +0000 Subject: [PATCH 3/8] Coverlet console --- .../CoverletConsole_Tests.cs | 89 ++++++++++++++++++- .../Coverlet/Console/CoverletConsoleUtil.cs | 69 ++++++++++---- 2 files changed, 138 insertions(+), 20 deletions(-) diff --git a/FineCodeCoverageTests/CoverletConsole_Tests.cs b/FineCodeCoverageTests/CoverletConsole_Tests.cs index e8821f96..76619c95 100644 --- a/FineCodeCoverageTests/CoverletConsole_Tests.cs +++ b/FineCodeCoverageTests/CoverletConsole_Tests.cs @@ -15,9 +15,9 @@ namespace Test { - public class CoverletExeArgumentsProvider_Tests { + private const string testProjectName = "TestProject"; [Test] public void Should_Have_ExcludeByAttribute_Setting_For_Each_ExcludeByAttribute() { @@ -43,12 +43,99 @@ public void Should_Unqualified_Qualified_ExcludeByAttribute() AssertHasSetting(coverletSettings, "--exclude-by-attribute ExcludeByAttribute1"); } + [Test] + public void Should_Not_Add_Test_Test_Assembly_To_Includes_When_IncludeTestAssembly_And_No_Other_Includes() + { + var mockCoverageProject = SafeMockCoverageProject(); + mockCoverageProject.SetupGet(cp => cp.Settings.IncludeTestAssembly).Returns(true); + + var coverletExeArgumentsProvider = new CoverletExeArgumentsProvider(); + var coverletSettings = coverletExeArgumentsProvider.GetArguments(mockCoverageProject.Object); + + Assert.IsFalse(HasIncludedTestAssemblySetting(coverletSettings)); + } + + private bool HasIncludedTestAssemblySetting(List coverletSettings) + { + return coverletSettings.Any(coverletSetting => coverletSetting == $@"--include ""[{testProjectName}]*"""); + } + + [Test] + public void Should_Add_Test_Test_Assembly_To_Includes_When_IncludeTestAssembly_And_Other_Includes() + { + var mockCoverageProject = SafeMockCoverageProject(); + mockCoverageProject.SetupGet(cp => cp.Settings.IncludeTestAssembly).Returns(true); + mockCoverageProject.SetupGet(cp => cp.Settings.Include).Returns(new string[] { "[anassembly]*" }); + + var coverletExeArgumentsProvider = new CoverletExeArgumentsProvider(); + var coverletSettings = coverletExeArgumentsProvider.GetArguments(mockCoverageProject.Object); + + Assert.IsTrue(HasIncludedTestAssemblySetting(coverletSettings)); + } + + [Test] + public void Should_Add_IncludedReferencedProjects_As_Include() + { + var mockCoverageProject = SafeMockCoverageProject(); + var mockReferencedProject = new Mock(); + mockReferencedProject.SetupGet(rp => rp.AssemblyName).Returns("ReferencedProject"); + mockCoverageProject.SetupGet(cp => cp.IncludedReferencedProjects).Returns(new List { mockReferencedProject.Object}); + + var coverletExeArgumentsProvider = new CoverletExeArgumentsProvider(); + var coverletSettings = coverletExeArgumentsProvider.GetArguments(mockCoverageProject.Object); + + Assert.True(coverletSettings.Contains($@"--include ""[ReferencedProject]*""")); + + } + + [Test] + public void Should_Include_From_Settings() + { + var mockCoverageProject = SafeMockCoverageProject(); + mockCoverageProject.SetupGet(cp => cp.Settings.Include).Returns(new string[]{ "[Include]*" }); + + var coverletExeArgumentsProvider = new CoverletExeArgumentsProvider(); + var coverletSettings = coverletExeArgumentsProvider.GetArguments(mockCoverageProject.Object); + + Assert.True(coverletSettings.Contains($@"--include ""[Include]*""")); + + } + + public void Should_Add_ExcludedReferencedProjects_As_Exclude() + { + var mockCoverageProject = SafeMockCoverageProject(); + var mockReferencedProject = new Mock(); + mockReferencedProject.SetupGet(rp => rp.AssemblyName).Returns("ReferencedProject"); + mockCoverageProject.SetupGet(cp => cp.ExcludedReferencedProjects).Returns(new List { mockReferencedProject.Object }); + + var coverletExeArgumentsProvider = new CoverletExeArgumentsProvider(); + var coverletSettings = coverletExeArgumentsProvider.GetArguments(mockCoverageProject.Object); + + Assert.True(coverletSettings.Contains($@"--exclude ""[ReferencedProject]*""")); + + } + + [Test] + public void Should_Exclude_From_Settings() + { + var mockCoverageProject = SafeMockCoverageProject(); + mockCoverageProject.SetupGet(cp => cp.Settings.Exclude).Returns(new string[] { "[Exclude]*" }); + + var coverletExeArgumentsProvider = new CoverletExeArgumentsProvider(); + var coverletSettings = coverletExeArgumentsProvider.GetArguments(mockCoverageProject.Object); + + Assert.True(coverletSettings.Contains($@"--exclude ""[Exclude]*""")); + + } + + private Mock SafeMockCoverageProject() { var mockCoverageProject = new Mock(); mockCoverageProject.SetupGet(coverageProject => coverageProject.IncludedReferencedProjects).Returns(new List()); mockCoverageProject.SetupGet(coverageProject => coverageProject.ExcludedReferencedProjects).Returns(new List()); mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings).Returns(new Mock().Object); + mockCoverageProject.Setup(coverageProject => coverageProject.ProjectName).Returns(testProjectName); return mockCoverageProject; } diff --git a/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs b/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs index 97cdbdee..07a22397 100644 --- a/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs +++ b/SharedProject/Core/Coverlet/Console/CoverletConsoleUtil.cs @@ -18,47 +18,78 @@ internal interface ICoverletExeArgumentsProvider [Export(typeof(ICoverletExeArgumentsProvider))] internal class CoverletExeArgumentsProvider : ICoverletExeArgumentsProvider { - private IEnumerable SanitizeExcludesOrIncludes(string[] excludesOrIncludes) + private static IEnumerable SanitizeExcludesByAttribute(string[] excludes) { - return (excludesOrIncludes ?? new string[0]) + return (excludes ?? new string[0]) .Where(x => x != null) .Select(x => x.Trim(' ', '\'', '\"')) .Where(x => !string.IsNullOrWhiteSpace(x)); } - public List GetArguments(ICoverageProject project) - { - var coverletSettings = new List(); - - coverletSettings.Add($@"""{project.TestDllFile}"""); - - coverletSettings.Add($@"--format ""cobertura"""); - foreach (var value in (project.Settings.Exclude ?? new string[0]).Where(x => !string.IsNullOrWhiteSpace(x))) + private static IEnumerable SantitizeExcludeInclude(string[] excludesOrIncludes) + { + return (excludesOrIncludes ?? new string[0]).Where(x => !string.IsNullOrWhiteSpace(x)).Select(value => { - coverletSettings.Add($@"--exclude ""{value.Replace("\"", "\\\"").Trim(' ', '\'')}"""); - } + return value.Replace("\"", "\\\"").Trim(' ', '\''); + }); + } - foreach (var referencedProjectExcludedFromCodeCoverage in project.ExcludedReferencedProjects.Select(rp => rp.AssemblyName)) + private static void AddExcludesOrIncludes(List coverletSettings, IEnumerable excludesOrIncludes, bool isInclude) + { + foreach (var value in excludesOrIncludes) { - coverletSettings.Add($@"--exclude ""[{referencedProjectExcludedFromCodeCoverage}]*"""); + coverletSettings.Add($@"--{(isInclude ? "include" : "exclude")} ""{value}"""); } + } - foreach (var value in (project.Settings.Include ?? new string[0]).Where(x => !string.IsNullOrWhiteSpace(x))) + private static IEnumerable AddTestAssemblyIfNecessary( + IEnumerable projectIncludes, + IEnumerable includes, + string projectName) + { + var hasIncludes = projectIncludes.Any() || includes.Any(); + if(!hasIncludes) { - coverletSettings.Add($@"--include ""{value.Replace("\"", "\\\"").Trim(' ', '\'')}"""); + return projectIncludes; } + return projectIncludes.Concat(new string[] { projectName }); + } - foreach (var includedReferencedProject in project.IncludedReferencedProjects.Select(rp => rp.AssemblyName)) + private static void AddProjectExcludesOrIncludes(List coverletSettings, IEnumerable excludesOrIncludes, bool isInclude) + { + AddExcludesOrIncludes(coverletSettings, excludesOrIncludes.Select(excludeOrInclude => $"[{excludeOrInclude}]*"), isInclude); + } + + private static void AddExcludesIncludes(List coverletSettings,ICoverageProject project) + { + AddExcludesOrIncludes(coverletSettings, SantitizeExcludeInclude(project.Settings.Exclude), false); + AddProjectExcludesOrIncludes(coverletSettings, project.ExcludedReferencedProjects.Select(rp => rp.AssemblyName), false); + var includes = SantitizeExcludeInclude(project.Settings.Include); + AddExcludesOrIncludes(coverletSettings, includes, true); + var projectIncludes = project.IncludedReferencedProjects.Select(rp => rp.AssemblyName); + if (project.Settings.IncludeTestAssembly) { - coverletSettings.Add($@"--include ""[{includedReferencedProject}]*"""); + projectIncludes = AddTestAssemblyIfNecessary(projectIncludes, includes, project.ProjectName); } + AddProjectExcludesOrIncludes(coverletSettings, projectIncludes, true); + } + + public List GetArguments(ICoverageProject project) + { + var coverletSettings = new List(); + + coverletSettings.Add($@"""{project.TestDllFile}"""); + + coverletSettings.Add($@"--format ""cobertura"""); + + AddExcludesIncludes(coverletSettings, project); foreach (var value in (project.Settings.ExcludeByFile ?? new string[0]).Where(x => !string.IsNullOrWhiteSpace(x))) { coverletSettings.Add($@"--exclude-by-file ""{value.Replace("\"", "\\\"").Trim(' ', '\'')}"""); } - foreach (var value in SanitizeExcludesOrIncludes(project.Settings.ExcludeByAttribute).Select(EnsureAttributeTypeUnqualified)) + foreach (var value in SanitizeExcludesByAttribute(project.Settings.ExcludeByAttribute).Select(EnsureAttributeTypeUnqualified)) { var withoutAttributeBrackets = value.Trim('[', ']'); coverletSettings.Add($@"--exclude-by-attribute {value}"); From 111885d50eb818f4617b407491a2b07d49374f9a Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 Mar 2025 17:56:52 +0000 Subject: [PATCH 4/8] coverlet collector --- ...overletDataCollectorUtil_RunAsync_Tests.cs | 45 ++++++++++++++++++- .../CoverletDataCollectorUtil.cs | 11 +++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs b/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs index 6f071370..3c18d36d 100644 --- a/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs +++ b/FineCodeCoverageTests/CoverletDataCollectorUtil_RunAsync_Tests.cs @@ -118,17 +118,58 @@ public async Task Should_Get_Settings_With_ExcludeByAttribute_From_CoverageProje } [Test] - public async Task Should_Get_Settings_With_Include_From_CoverageProject_And_RunSettings_Async() + public async Task Should_Include_From_CoverageProject_Settings_Include_And_RunSettings_Async() { var projectInclude= new string[] { "included" }; mockCoverageProject.Setup(cp => cp.Settings.Include).Returns(projectInclude); mockCoverageProject.Setup(cp => cp.CoverageOutputFolder).Returns(""); + await coverletDataCollectorUtil.RunAsync(CancellationToken.None); + mockDataCollectorSettingsBuilder.Verify(b => b.WithInclude(projectInclude, It.IsAny())); + } + + [Test] + public async Task Should_Include_From_CoverageProject_IncludedReferencedProjects_And_RunSettings_Async() + { + var projectInclude = new string[] { "[ReferencedProject]*" }; + mockCoverageProject.Setup(cp => cp.CoverageOutputFolder).Returns(""); + var mockReferencedProject = new Mock(); + mockReferencedProject.SetupGet(rp => rp.AssemblyName).Returns("ReferencedProject"); + mockCoverageProject.Setup(cp => cp.IncludedReferencedProjects).Returns(new List { mockReferencedProject.Object }); + + await coverletDataCollectorUtil.RunAsync(CancellationToken.None); + mockDataCollectorSettingsBuilder.Verify(b => b.WithInclude(projectInclude, It.IsAny())); + } + + [Test] + public async Task Should_Not_Include_Test_Assembly_When_IncludeTestAssembly_True_And_No_Other_Includes_Async() + { + mockCoverageProject.Setup(cp => cp.CoverageOutputFolder).Returns(""); + mockCoverageProject.Setup(cp => cp.Settings.IncludeTestAssembly).Returns(true); mockRunSettingsCoverletConfiguration.Setup(rsc => rsc.Include).Returns("rsincluded"); + + await coverletDataCollectorUtil.RunAsync(CancellationToken.None); + mockDataCollectorSettingsBuilder.Verify(b => b.WithInclude(new string[] { },It.IsAny())); + } + + [Test] + public async Task Should_Include_Test_Assembly_When_IncludeTestAssembly_True_And_Other_Includes_Async() + { + mockCoverageProject.Setup(cp => cp.CoverageOutputFolder).Returns(""); + var projectInclude = new string[] { "included" }; + mockCoverageProject.Setup(cp => cp.Settings.Include).Returns(projectInclude); + mockCoverageProject.Setup(cp => cp.Settings.IncludeTestAssembly).Returns(true); + mockCoverageProject.Setup(cp => cp.ProjectName).Returns("TestProject"); + mockRunSettingsCoverletConfiguration.Setup(rsc => rsc.Include).Returns("rsincluded"); + await coverletDataCollectorUtil.RunAsync(CancellationToken.None); - mockDataCollectorSettingsBuilder.Verify(b => b.WithInclude(projectInclude, "rsincluded")); + mockDataCollectorSettingsBuilder.Verify( + b => b.WithInclude( + It.Is(includes => includes.OrderBy(incl => incl).SequenceEqual(new string[] { "[TestProject]*", "included"})), + It.IsAny())); } + [TestCase(true,"true")] [TestCase(false, "false")] public async Task Should_Get_Settings_With_IncludeTestAssembly_From_CoverageProject_And_RunSettings_Async(bool projectIncludeTestAssembly, string runSettingsIncludeTestAssembly) diff --git a/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs b/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs index 7551b65f..494e241a 100644 --- a/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs +++ b/SharedProject/Core/Coverlet/DataCollector/CoverletDataCollectorUtil.cs @@ -191,14 +191,19 @@ private string GetSettings() SanitizeExcludesOrIncludes(coverageProject.Settings.ExcludeByAttribute), runSettingsCoverletConfiguration.ExcludeByAttribute); - string[] projectIncludes = coverageProject.IncludedReferencedProjects.Select(irp => $"[{irp.AssemblyName}]*").ToArray(); + var projectIncludes = coverageProject.IncludedReferencedProjects.Select(irp => $"[{irp.AssemblyName}]*"); if(coverageProject.Settings.Include != null) { - projectIncludes = projectIncludes.Concat(SanitizeExcludesOrIncludes(coverageProject.Settings.Include)).ToArray(); + projectIncludes = projectIncludes.Concat(SanitizeExcludesOrIncludes(coverageProject.Settings.Include)); + } + + if (coverageProject.Settings.IncludeTestAssembly && projectIncludes.Any()) + { + projectIncludes = projectIncludes.Concat(new string[] { $"[{coverageProject.ProjectName}]*" }).ToArray(); } dataCollectorSettingsBuilder - .WithInclude(projectIncludes, runSettingsCoverletConfiguration.Include); + .WithInclude(projectIncludes.ToArray(), runSettingsCoverletConfiguration.Include); dataCollectorSettingsBuilder .WithIncludeTestAssembly(coverageProject.Settings.IncludeTestAssembly, runSettingsCoverletConfiguration.IncludeTestAssembly); From 64cfd7c3a7c005c790c8f0676ccba2f0b7c7799f Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Mon, 10 Mar 2025 18:02:49 +0000 Subject: [PATCH 5/8] simplify OpenCover --- .../OpenCoverExeArgumentsProvider_Tests.cs | 17 +------- .../OpenCoverExeArgumentsProvider.cs | 41 +------------------ 2 files changed, 3 insertions(+), 55 deletions(-) diff --git a/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs b/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs index 19961be6..d388439f 100644 --- a/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs +++ b/FineCodeCoverageTests/OpenCoverExeArgumentsProvider_Tests.cs @@ -97,7 +97,7 @@ public void Should_Safely_Include_The_TestDLLFile_In_The_TargetArgs() } [Test] - public void Should_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_IncludeTestAssembly_And_Required() + public void Should_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_IncludeTestAssembly_And_Other_Includes() { var openCoverExeArgumentsProvider = new OpenCoverExeArgumentsProvider(); var mockCoverageProject = SafeMockCoverageProject(); @@ -125,21 +125,6 @@ public void Should_Not_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_I Assert.IsEmpty(GetFilters(arguments)); } - [Test] - public void Should_Not_Include_The_Test_Assembly_In_The_Filter_When_AppOptions_IncludeTestAssembly_And_Explicitly_Excluded() - { - var openCoverExeArgumentsProvider = new OpenCoverExeArgumentsProvider(); - var mockCoverageProject = SafeMockCoverageProject(); - mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.IncludeTestAssembly).Returns(true); - mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.Include).Returns(new string[] { "[anassembly]*" }); - mockCoverageProject.SetupGet(coverageProject => coverageProject.Settings.Exclude).Returns(new string[] { "[TheTestName]*" }); - mockCoverageProject.SetupGet(coverageProject => coverageProject.ProjectName).Returns("TheTestName"); - - var arguments = openCoverExeArgumentsProvider.Provide(mockCoverageProject.Object, ""); - var filters = GetFilters(arguments); - Assert.That(filters, Is.EquivalentTo(new string[] { "+[anassembly]*", "-[TheTestName]*" })); - } - private IEnumerable GetFilters(IEnumerable arguments) { var filterMatch = "-filter:"; diff --git a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs index 2d30fc5d..760ebf39 100644 --- a/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs +++ b/SharedProject/Core/OpenCover/OpenCoverExeArgumentsProvider.cs @@ -23,53 +23,16 @@ public static string AddEscapeQuotes(string arg) [Export(typeof(IOpenCoverExeArgumentsProvider))] internal class OpenCoverExeArgumentsProvider : IOpenCoverExeArgumentsProvider { - private static readonly Regex AssemblyRegex = new Regex(@"^\[(.*?)\]", RegexOptions.Compiled); private enum Delimiter { Semicolon, Space} - private static bool IncludeTestAssemblyOnlyWhenNecessary( - List includedReferencedProjects, - IEnumerable inclusions, - List exclusions, - string testAssemblyName) - { - return HasInclusions(inclusions, includedReferencedProjects) && !IsTestAssemblySpecificallyExcluded(exclusions, testAssemblyName); - } - - private static string GetAssemblyFromFilter(string input) - { - Match match = AssemblyRegex.Match(input); - return match.Success ? match.Groups[1].Value : null; - } - - private static bool IsTestAssemblySpecificallyExcluded(List exclusions, string testAssemblyName) - { - // not interested in an exclude all - - // note that it could also have been excluded with a wild card - for now for simplicity we are not checking that - foreach (var exclusion in exclusions) - { - var assembly = GetAssemblyFromFilter(exclusion); - if(assembly == testAssemblyName) - { - return true; - } - } - return false; - } - - private static bool HasInclusions(IEnumerable includes, List includedReferencedProjects) - { - return includes.Any() || includedReferencedProjects.Any(); - } - private void AddFilter(ICoverageProject project, List opencoverSettings) { var includes = SanitizeExcludesOrIncludes(project.Settings.Include); var excludes = SanitizeExcludesOrIncludes(project.Settings.Exclude).ToList(); var includedModules = project.IncludedReferencedProjects.Select(rp => rp.AssemblyName).ToList(); - if (project.Settings.IncludeTestAssembly && - IncludeTestAssemblyOnlyWhenNecessary(project.IncludedReferencedProjects, includes, excludes, project.ProjectName)) + if (project.Settings.IncludeTestAssembly && + (includes.Any() || project.IncludedReferencedProjects.Any())) { includedModules.Add(project.ProjectName); } From e4a53392a087d05ce843a656f835ee6ae8984013 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 11 Mar 2025 12:27:54 +0000 Subject: [PATCH 6/8] ms code coverage --- ...ttingsTemplateReplacementsFactory_Tests.cs | 384 +++++++++++++----- .../RunSettingsTemplateReplacementsFactory.cs | 52 ++- 2 files changed, 334 insertions(+), 102 deletions(-) diff --git a/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs b/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs index f3315a49..4ae882dd 100644 --- a/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs +++ b/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplateReplacementsFactory_Tests.cs @@ -8,6 +8,7 @@ using FineCodeCoverage.Engine.Model; using System; using System.Diagnostics.CodeAnalysis; +using System.Xml.Linq; namespace FineCodeCoverageTests.MsCodeCoverage { @@ -54,7 +55,7 @@ public static void AssertAllEmpty(IRunSettingsTemplateReplacements replacements) } } - + internal class RunSettingsTemplateReplacementsFactory_UserRunSettings_Tests { private RunSettingsTemplateReplacementsFactory runSettingsTemplateReplacementsFactory; @@ -159,7 +160,8 @@ TestMsCodeCoverageOptions CreateSettings(string id) { return new TestMsCodeCoverageOptions { - IncludeTestAssembly = true, + IncludeTestAssembly = true, // not testing ModulePaths here + AttributesExclude = new string[] { $"AttributeExclude{id}" }, AttributesInclude = new string[] { $"AttributeInclude{id}" }, CompanyNamesExclude = new string[] { $"CompanyNameExclude{id}" }, @@ -207,23 +209,16 @@ TestMsCodeCoverageOptions CreateSettings(string id) }; var replacements = runSettingsTemplateReplacementsFactory.Create(testContainers, userRunSettingsProjectDetailsLookup, null); - void AssertReplacement(string replacement, string replacementProperty, bool isInclude) - { - var ie = isInclude ? "Include" : "Exclude"; - Assert.AreEqual($"<{replacementProperty}>{replacementProperty}{ie}1<{replacementProperty}>{replacementProperty}{ie}2", replacement); - } - - AssertReplacement(replacements.FunctionsExclude, "Function", false); - AssertReplacement(replacements.FunctionsInclude, "Function", true); - AssertReplacement(replacements.CompanyNamesExclude, "CompanyName", false); - AssertReplacement(replacements.CompanyNamesInclude, "CompanyName", true); - AssertReplacement(replacements.AttributesExclude, "Attribute", false); - AssertReplacement(replacements.AttributesInclude, "Attribute", true); - AssertReplacement(replacements.PublicKeyTokensExclude, "PublicKeyToken", false); - AssertReplacement(replacements.PublicKeyTokensInclude, "PublicKeyToken", true); - AssertReplacement(replacements.SourcesExclude, "Source", false); - AssertReplacement(replacements.SourcesInclude, "Source", true); - + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.FunctionsExclude, "Function", new[] { "FunctionExclude1", "FunctionExclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.FunctionsInclude, "Function", new[] { "FunctionInclude1", "FunctionInclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.CompanyNamesExclude, "CompanyName", new[] { "CompanyNameExclude1", "CompanyNameExclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.CompanyNamesInclude, "CompanyName", new[] { "CompanyNameInclude1", "CompanyNameInclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.AttributesExclude, "Attribute", new[] { "AttributeExclude1", "AttributeExclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.AttributesInclude, "Attribute", new[] { "AttributeInclude1", "AttributeInclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.PublicKeyTokensExclude, "PublicKeyToken", new[] { "PublicKeyTokenExclude1", "PublicKeyTokenExclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.PublicKeyTokensInclude, "PublicKeyToken", new[] { "PublicKeyTokenInclude1", "PublicKeyTokenInclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.SourcesExclude, "Source", new[] { "SourceExclude1", "SourceExclude2" }); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.SourcesInclude, "Source", new[] { "SourceInclude1", "SourceInclude2" }); } [TestCase(true, true)] @@ -245,7 +240,6 @@ public void Should_Add_The_Test_Assembly_Regex_Escaped_To_Module_Excludes_When_I CoverageOutputFolder = "", Settings = new TestMsCodeCoverageOptions{ IncludeTestAssembly = includeTestAssembly1, - ModulePathsExclude = new string[]{ "ModulePathExclude"} }, ExcludedReferencedProjects = new List(), IncludedReferencedProjects = new List(), @@ -268,21 +262,25 @@ public void Should_Add_The_Test_Assembly_Regex_Escaped_To_Module_Excludes_When_I var testDlls = userRunSettingsProjectDetailsLookup.Select(kvp => kvp.Value.TestDllFile).ToList(); string GetModulePathExcludeWhenExcludingTestAssembly(bool first) { - var regexed = MsCodeCoverageRegex.RegexEscapePath(testDlls[first ? 0 : 1]); - return ModulePathElement(regexed); + return MsCodeCoverageRegex.RegexEscapePath(testDlls[first ? 0 : 1]); } - - var expectedModulePathExcludes1 = !includeTestAssembly1 ? GetModulePathExcludeWhenExcludingTestAssembly(true) : ""; - var expectedModulePathExcludes2 = !includeTestAssembly2 ? GetModulePathExcludeWhenExcludingTestAssembly(false) : ""; - var expectedModulePathExcludes = expectedModulePathExcludes1 + expectedModulePathExcludes2 + ModulePathElement("ModulePathExclude"); - + var expectedModulePathExcludes = new List(); + if (!includeTestAssembly1) + { + expectedModulePathExcludes.Add(GetModulePathExcludeWhenExcludingTestAssembly(true)); + } + if (!includeTestAssembly2) + { + expectedModulePathExcludes.Add(GetModulePathExcludeWhenExcludingTestAssembly(false)); + } + var replacements = runSettingsTemplateReplacementsFactory.Create(testContainers, userRunSettingsProjectDetailsLookup, null); - Assert.AreEqual(expectedModulePathExcludes, replacements.ModulePathsExclude); + + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.ModulePathsExclude, "ModulePath", expectedModulePathExcludes); } - [TestCase(true)] - [TestCase(false)] - public void Should_Add_Regexed_IncludedExcluded_Referenced_Projects_To_ModulePaths(bool included) + [Test] + public void Should_Add_Regexed_ExcludedReferencedProjects_And_ModulePathsExclude_To_ModulePaths() { var testContainers = new List() { @@ -295,18 +293,72 @@ public void Should_Add_Regexed_IncludedExcluded_Referenced_Projects_To_ModulePat { "Source1", new TestUserRunSettingsProjectDetails - { - CoverageOutputFolder = "", - TestDllFile = "", - Settings = new TestMsCodeCoverageOptions + { + CoverageOutputFolder = "", + TestDllFile = "", + Settings = new TestMsCodeCoverageOptions + { + ModulePathsExclude = new string[] { "ModulePathExclude1" }, + IncludeTestAssembly = true + }, + ExcludedReferencedProjects = new List { new ReferencedProject("", "ExcludedReferenced1", false) }, + IncludedReferencedProjects = Enumerable.Empty().ToList() + } + }, { - IncludeTestAssembly = !included, - ModulePathsExclude = new string[] { "ModulePathExclude" }, - ModulePathsInclude = new string[] { "ModulePathInclude" } + "Source2", + new TestUserRunSettingsProjectDetails + { + CoverageOutputFolder = "", + TestDllFile = "", + Settings = new TestMsCodeCoverageOptions { + ModulePathsExclude = new string[] { "ModulePathExclude2" }, + IncludeTestAssembly = true + }, + ExcludedReferencedProjects = new List { new ReferencedProject("", "ExcludedReferenced2", true) }, + IncludedReferencedProjects = Enumerable.Empty().ToList() + } }, - ExcludedReferencedProjects = new List { new ReferencedProject("", "ExcludedReferenced1", true) }, - IncludedReferencedProjects = new List { new ReferencedProject("", "IncludedReferenced1", true) }, - } + }; + + var projectDetails = userRunSettingsProjectDetailsLookup.Select(kvp => kvp.Value).ToList(); + IEnumerable expectedExcludedReferencedProjects = projectDetails.SelectMany(pd => pd.ExcludedReferencedProjects) + .Select(rp => MsCodeCoverageRegex.RegexModuleName(rp.AssemblyName, rp.IsDll)); + + var replacements = runSettingsTemplateReplacementsFactory.Create(testContainers, userRunSettingsProjectDetailsLookup, null); + + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsExclude, + "ModulePath", + new[] { "ModulePathExclude1", "ModulePathExclude2" }.Concat(expectedExcludedReferencedProjects)); + + } + + [Test] + public void Should_Add_Regexed_InludedReferencedProjects_And_ModulePathsInclude_To_ModulePaths() + { + var testContainers = new List() + { + CreateTestContainer("Source1"), + CreateTestContainer("Source2"), + }; + + Dictionary userRunSettingsProjectDetailsLookup = new Dictionary + { + { + "Source1", + new TestUserRunSettingsProjectDetails + { + CoverageOutputFolder = "", + TestDllFile = "", + Settings = new TestMsCodeCoverageOptions + { + ModulePathsInclude = new string[] { "ModulePathInclude1" }, + IncludeTestAssembly = false + }, + IncludedReferencedProjects = new List { new ReferencedProject("", "InludedReferenced1", false) }, + ExcludedReferencedProjects = Enumerable.Empty().ToList() + } }, { "Source2", @@ -314,24 +366,130 @@ public void Should_Add_Regexed_IncludedExcluded_Referenced_Projects_To_ModulePat { CoverageOutputFolder = "", TestDllFile = "", - Settings = new TestMsCodeCoverageOptions { IncludeTestAssembly = !included }, - ExcludedReferencedProjects = new List { new ReferencedProject("", "ExcludedReferenced2", true) }, + Settings = new TestMsCodeCoverageOptions { + ModulePathsInclude = new string[] { "ModulePathInclude2" }, + IncludeTestAssembly = false + }, IncludedReferencedProjects = new List { new ReferencedProject("", "IncludedReferenced2", true) }, + ExcludedReferencedProjects = Enumerable.Empty().ToList() } }, }; var projectDetails = userRunSettingsProjectDetailsLookup.Select(kvp => kvp.Value).ToList(); - IEnumerable allReferencedProjects = projectDetails.SelectMany(pd => included ? pd.IncludedReferencedProjects : pd.ExcludedReferencedProjects); + IEnumerable expectedIncludedReferencedProjects = projectDetails.SelectMany(pd => pd.IncludedReferencedProjects) + .Select(rp => MsCodeCoverageRegex.RegexModuleName(rp.AssemblyName, rp.IsDll)); + + var replacements = runSettingsTemplateReplacementsFactory.Create(testContainers, userRunSettingsProjectDetailsLookup, null); + + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsInclude, + "ModulePath", + new[] { "ModulePathInclude1", "ModulePathInclude2" }.Concat(expectedIncludedReferencedProjects)); + } - string GetExpectedExcludedOrIncludedEscaped(IEnumerable excludedOrIncludedReferenced) + [Test] + public void Should_Add_TestAssembly_To_ModulePathsInclude_When_Other_Includes_And_IncludeTestAssembly_True() + { + var testContainers = new List() { - return string.Join("", excludedOrIncludedReferenced.Select(referenced => ModulePathElement(MsCodeCoverageRegex.RegexModuleName(referenced.AssemblyName,true)))); - } - var expectedExcludes = GetExpectedExcludedOrIncludedEscaped(allReferencedProjects) + ModulePathElement(included ? "ModulePathInclude" : "ModulePathExclude"); + CreateTestContainer("Source1"), + CreateTestContainer("Source2"), + }; + + Dictionary userRunSettingsProjectDetailsLookup = new Dictionary + { + { + "Source1", + new TestUserRunSettingsProjectDetails + { + CoverageOutputFolder = "", + TestDllFile = @"Some\Path1", + Settings = new TestMsCodeCoverageOptions + { + ModulePathsInclude = new string[] { "ModulePathInclude1" }, + IncludeTestAssembly = true, + + }, + IncludedReferencedProjects = Enumerable.Empty().ToList(), + ExcludedReferencedProjects = Enumerable.Empty().ToList() + } + }, + { + "Source2", + new TestUserRunSettingsProjectDetails + { + CoverageOutputFolder = "", + TestDllFile = @"Some\Path2", + Settings = new TestMsCodeCoverageOptions { + IncludeTestAssembly = true + }, + IncludedReferencedProjects = Enumerable.Empty().ToList(), + ExcludedReferencedProjects = Enumerable.Empty().ToList() + } + }, + }; + + var replacements = runSettingsTemplateReplacementsFactory.Create(testContainers, userRunSettingsProjectDetailsLookup, null); - Assert.AreEqual(expectedExcludes, included ? replacements.ModulePathsInclude : replacements.ModulePathsExclude); + + var expectedTestDlls = userRunSettingsProjectDetailsLookup.Select(kvp => kvp.Value.TestDllFile).Select(testDll => MsCodeCoverageRegex.RegexEscapePath(testDll)); + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsInclude, + "ModulePath", + new[] { "ModulePathInclude1", }.Concat(expectedTestDlls)); + } + + [Test] + public void Should_Not_Add_TestAssembly_To_ModulePathsInclude_When_No_Other_Includes_And_IncludeTestAssembly_True() + { + var testContainers = new List() + { + CreateTestContainer("Source1"), + CreateTestContainer("Source2"), + }; + + Dictionary userRunSettingsProjectDetailsLookup = new Dictionary + { + { + "Source1", + new TestUserRunSettingsProjectDetails + { + CoverageOutputFolder = "", + TestDllFile = @"Some\Path1", + Settings = new TestMsCodeCoverageOptions + { + IncludeTestAssembly = true, + }, + IncludedReferencedProjects = Enumerable.Empty().ToList(), + ExcludedReferencedProjects = Enumerable.Empty().ToList() + } + }, + { + "Source2", + new TestUserRunSettingsProjectDetails + { + CoverageOutputFolder = "", + TestDllFile = @"Some\Path2", + Settings = new TestMsCodeCoverageOptions { + IncludeTestAssembly = true + }, + IncludedReferencedProjects = Enumerable.Empty().ToList(), + ExcludedReferencedProjects = Enumerable.Empty().ToList() + } + }, + }; + + + + var replacements = runSettingsTemplateReplacementsFactory.Create(testContainers, userRunSettingsProjectDetailsLookup, null); + + var expectedTestDlls = userRunSettingsProjectDetailsLookup.Select(kvp => kvp.Value.TestDllFile).Select(testDll => MsCodeCoverageRegex.RegexEscapePath(testDll)); + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsInclude, + "ModulePath", + Enumerable.Empty()); } [Test] @@ -372,10 +530,10 @@ public void Should_Be_Null_TestAdapter_Replacement_When_Null() Assert.That(replacements.TestAdapter, Is.Null); } - [TestCase(true,true,"true")] + [TestCase(true, true, "true")] [TestCase(false, true, "true")] [TestCase(true, false, "true")] - [TestCase(false, false, "false")] + [TestCase(false, false, "false")] public void Should_Be_Disabled_When_All_Projects_Are_Disabled(bool project1Enabled, bool project2Enabled, string expectedEnabled) { var testContainer1 = CreateTestContainer("Source1"); @@ -416,11 +574,6 @@ public void Should_Be_Disabled_When_All_Projects_Are_Disabled(bool project1Enabl Assert.That(runSettingsTemplateReplacements.Enabled, Is.EqualTo(expectedEnabled)); } - private string ModulePathElement(string value) - { - return $"{value}"; - } - private ITestContainer CreateTestContainer(string source) { var mockTestContainer = new Mock(); @@ -432,7 +585,7 @@ private ITestContainer CreateTestContainer(string source) internal class RunSettingsTemplateReplacementsFactory_Template_Tests { private RunSettingsTemplateReplacementsFactory runSettingsTemplateReplacementsFactory; - + [SetUp] public void CreateSut() { @@ -454,7 +607,7 @@ private ICoverageProject CreateCoverageProject(Action> fu mockCoverageProject.Setup(cp => cp.ExcludedReferencedProjects).Returns(new List()); mockCoverageProject.Setup(cp => cp.IncludedReferencedProjects).Returns(new List()); mockCoverageProject.Setup(cp => cp.TestDllFile).Returns(""); - mockCoverageProject.Setup(cp => cp.Settings).Returns(mockSettings.Object); + mockCoverageProject.Setup(cp => cp.Settings).Returns(mockSettings.Object); furtherSetup?.Invoke(mockCoverageProject); return mockCoverageProject.Object; } @@ -464,7 +617,7 @@ private ICoverageProject CreateCoverageProject(Action> fu public void Should_Set_Enabled_From_The_CoverageProject_Settings(bool enabled) { var coverageProject = CreateCoverageProject(mock => mock.Setup(cp => cp.Settings.Enabled).Returns(enabled)); - var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject,null); + var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); Assert.AreEqual(enabled.ToString(), replacements.Enabled); } @@ -472,12 +625,12 @@ public void Should_Set_Enabled_From_The_CoverageProject_Settings(bool enabled) public void Should_Set_The_ResultsDirectory_To_The_Project_CoverageOutputFolder() { var coverageProject = CreateCoverageProject(mock => mock.Setup(cp => cp.CoverageOutputFolder).Returns("CoverageOutputFolder")); - var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject,null); + var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); Assert.AreEqual("CoverageOutputFolder", replacements.ResultsDirectory); } [Test] - public void Should_Create_Element_Replacements() + public void Should_Create_Element_Replacements_From_Settings() { var msCodeCoverageOptions = new TestCoverageProjectOptions { @@ -491,32 +644,22 @@ public void Should_Create_Element_Replacements() PublicKeyTokensInclude = new[] { "PublicKeyTokenInclude1", "PublicKeyTokenInclude2" }, SourcesExclude = new[] { "SourceExclude1", "SourceExclude2" }, SourcesInclude = new[] { "SourceInclude1", "SourceInclude2" }, - ModulePathsExclude = new[] { "ModulePathExclude1","ModulePathExclude2" }, - ModulePathsInclude = new[] { "ModulePathInclude1", "ModulePathInclude2" }, IncludeTestAssembly = true, }; var coverageProject = CreateCoverageProject(mock => mock.Setup(cp => cp.Settings).Returns(msCodeCoverageOptions)); var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); - - void AssertReplacement(string replacement, string replacementProperty, bool isInclude) - { - var ie = isInclude ? "Include" : "Exclude"; - Assert.AreEqual($"<{replacementProperty}>{replacementProperty}{ie}1<{replacementProperty}>{replacementProperty}{ie}2", replacement); - } - - AssertReplacement(replacements.ModulePathsExclude, "ModulePath", false); - AssertReplacement(replacements.FunctionsExclude, "Function", false); - AssertReplacement(replacements.FunctionsInclude, "Function", true); - AssertReplacement(replacements.CompanyNamesExclude, "CompanyName", false); - AssertReplacement(replacements.CompanyNamesInclude, "CompanyName", true); - AssertReplacement(replacements.AttributesExclude, "Attribute", false); - AssertReplacement(replacements.AttributesInclude, "Attribute", true); - AssertReplacement(replacements.PublicKeyTokensExclude, "PublicKeyToken", false); - AssertReplacement(replacements.PublicKeyTokensInclude, "PublicKeyToken", true); - AssertReplacement(replacements.SourcesExclude, "Source", false); - AssertReplacement(replacements.SourcesInclude, "Source", true); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.FunctionsExclude, "Function", msCodeCoverageOptions.FunctionsExclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.FunctionsInclude, "Function", msCodeCoverageOptions.FunctionsInclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.CompanyNamesExclude, "CompanyName", msCodeCoverageOptions.CompanyNamesExclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.CompanyNamesInclude, "CompanyName", msCodeCoverageOptions.CompanyNamesInclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.AttributesExclude, "Attribute", msCodeCoverageOptions.AttributesExclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.AttributesInclude, "Attribute", msCodeCoverageOptions.AttributesInclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.PublicKeyTokensExclude, "PublicKeyToken", msCodeCoverageOptions.PublicKeyTokensExclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.PublicKeyTokensInclude, "PublicKeyToken", msCodeCoverageOptions.PublicKeyTokensInclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.SourcesExclude, "Source", msCodeCoverageOptions.SourcesExclude); + IncludeExcludeXmlELementsStringAssertionHelper.Assert(replacements.SourcesInclude, "Source", msCodeCoverageOptions.SourcesInclude); } [Test] @@ -580,7 +723,7 @@ public void Should_Have_ModulePathsExclude_Replacements_From_ExcludedReferencedP { var msCodeCoverageOptions = new TestCoverageProjectOptions { - ModulePathsExclude = new[] { "FromSettings"}, + ModulePathsExclude = new[] { "FromSettings" }, IncludeTestAssembly = false }; @@ -595,17 +738,19 @@ public void Should_Have_ModulePathsExclude_Replacements_From_ExcludedReferencedP }); var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); - var expectedModulePathsExclude = $"{ModulePathElement(MsCodeCoverageRegex.RegexModuleName("ModuleName", true))}{ModulePathElement(MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll"))}{ModulePathElement("FromSettings")}"; - Assert.AreEqual(expectedModulePathsExclude, replacements.ModulePathsExclude); + + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsExclude, "ModulePath", + new[] { MsCodeCoverageRegex.RegexModuleName("ModuleName", true), MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll"), "FromSettings" }); } [Test] - public void Should_Have_ModulePathsInclude_Replacements_From_IncludedReferencedProjects_Settings_And_Included_Test_Assembly() + public void Should_Have_ModulePathsInclude_Replacements_From_IncludedReferencedProjects_Settings_IncludedTestAssembly_False() { var msCodeCoverageOptions = new TestCoverageProjectOptions { ModulePathsInclude = new[] { "FromSettings" }, - IncludeTestAssembly = true + IncludeTestAssembly = false }; var coverageProject = CreateCoverageProject(mock => @@ -613,19 +758,76 @@ public void Should_Have_ModulePathsInclude_Replacements_From_IncludedReferencedP mock.Setup(cp => cp.Settings).Returns(msCodeCoverageOptions); mock.Setup(cp => cp.IncludedReferencedProjects).Returns(new List { - new ReferencedProject("", "ModuleName", true) + new ReferencedProject("", "ModuleNameDll", true), + new ReferencedProject("", "ModuleNameExe", false) }); mock.Setup(cp => cp.TestDllFile).Returns(@"Path\To\Test.dll"); }); var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); - var expectedModulePathsInclude = $"{ModulePathElement(MsCodeCoverageRegex.RegexModuleName("ModuleName",true))}{ModulePathElement(MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll"))}{ModulePathElement("FromSettings")}"; - Assert.AreEqual(expectedModulePathsInclude, replacements.ModulePathsInclude); + + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsInclude, + "ModulePath", + new[] { + MsCodeCoverageRegex.RegexModuleName("ModuleNameDll", true), + MsCodeCoverageRegex.RegexModuleName("ModuleNameExe", false), + "FromSettings" }); + } + + [Test] + public void Should_Not_Have_ModulePathsInclude_Replacements_When_IncludeTestAssembly_True_And_No_Other_Includes() + { + var msCodeCoverageOptions = new TestCoverageProjectOptions + { + IncludeTestAssembly = true + }; + + var coverageProject = CreateCoverageProject(mock => + { + mock.Setup(cp => cp.Settings).Returns(msCodeCoverageOptions); + mock.Setup(cp => cp.TestDllFile).Returns(@"Path\To\Test.dll"); + }); + + var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); + + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsInclude, + "ModulePath", + Enumerable.Empty()); } - private static string ModulePathElement(string value) + [Test] + public void Should_Have_ModulePathsInclude_Replacements_With_Test_Assembly_When_IncludeTestAssembly_True_And_Other_Includes() + { + var msCodeCoverageOptions = new TestCoverageProjectOptions + { + ModulePathsInclude = new[] { "SettingsInclude" }, + IncludeTestAssembly = true + }; + + var coverageProject = CreateCoverageProject(mock => + { + mock.Setup(cp => cp.Settings).Returns(msCodeCoverageOptions); + mock.Setup(cp => cp.TestDllFile).Returns(@"Path\To\Test.dll"); + }); + + var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, null); + + IncludeExcludeXmlELementsStringAssertionHelper.Assert( + replacements.ModulePathsInclude, + "ModulePath", + new[] { "SettingsInclude", MsCodeCoverageRegex.RegexEscapePath(@"Path\To\Test.dll") }); + } + + } + + internal static class IncludeExcludeXmlELementsStringAssertionHelper { + public static void Assert(string xmlElements, string expectedElementName, IEnumerable expectedContents) { - return $"{value}"; + var elements = XElement.Parse($"{xmlElements}").Elements(); + NUnit.Framework.Assert.That(elements.All(el => el.Name == expectedElementName)); + NUnit.Framework.Assert.That(elements.Select(el => el.Value), Is.EquivalentTo(expectedContents)); } } diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs index 84aecedf..96b4f4b8 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplateReplacementsFactory.cs @@ -151,14 +151,17 @@ public IRunSettingsTemplateReplacements Create( var additionalModulePathsExclude = allProjectDetails.SelectMany(pd => - GetAdditionalModulePaths(pd.ExcludedReferencedProjects, pd.TestDllFile, pd.Settings.IncludeTestAssembly, false)); + GetAdditionalModulePathsExclude(pd.ExcludedReferencedProjects, pd.TestDllFile, pd.Settings.IncludeTestAssembly)); + + var hasIncludes = allProjectDetails.Any(pd => HasIncludes(pd.Settings.ModulePathsInclude, pd.IncludedReferencedProjects)); + var additionalModulePathsInclude = allProjectDetails.SelectMany(pd => - GetAdditionalModulePaths(pd.IncludedReferencedProjects, pd.TestDllFile, pd.Settings.IncludeTestAssembly, true)); + GetAdditionalModulePathsInclude(hasIncludes, pd.IncludedReferencedProjects, pd.TestDllFile, pd.Settings.IncludeTestAssembly)); var settings = new CombinedIncludesExcludesOptions(mergedSettings, additionalModulePathsInclude, additionalModulePathsExclude); return new RunSettingsTemplateReplacements(settings, resultsDirectory, (!allProjectsDisabled).ToString().ToLower(), testAdapter); } - private IEnumerable GetAdditionalModulePaths( + private static IEnumerable GetAdditionalModulePaths( IEnumerable referencedProjects, string testDllFile, bool includeTestAssembly, @@ -175,22 +178,49 @@ bool isInclude } + private static IEnumerable GetAdditionalModulePathsExclude( + IEnumerable referencedProjects, string testDllFile, bool includeTestAssembly) + { + return GetAdditionalModulePaths(referencedProjects, testDllFile, includeTestAssembly, false); + } + + private static bool HasIncludes( + string[] modulePathsInclude, + List includedReferencedProjects) + { + return modulePathsInclude?.Any() == true || includedReferencedProjects.Any(); + } + + private static IEnumerable GetAdditionalModulePathsInclude( + bool hasIncludes, + List includedReferencedProjects, + string testDllFile, + bool includeTestAssembly) + + { + includeTestAssembly = includeTestAssembly && hasIncludes; + return GetAdditionalModulePaths( + includedReferencedProjects, + testDllFile, + includeTestAssembly, + true); + + } + public IRunSettingsTemplateReplacements Create(ICoverageProject coverageProject, string testAdapter) { var projectSettings = coverageProject.Settings; - var additionalModulePathsExclude = GetAdditionalModulePaths( + var additionalModulePathsExclude = GetAdditionalModulePathsExclude( coverageProject.ExcludedReferencedProjects, coverageProject.TestDllFile, - projectSettings.IncludeTestAssembly, - false); + projectSettings.IncludeTestAssembly); - - var additionalModulePathsInclude = GetAdditionalModulePaths( + var additionalModulePathsInclude = GetAdditionalModulePathsInclude( + HasIncludes(coverageProject.Settings.ModulePathsInclude, coverageProject.IncludedReferencedProjects), coverageProject.IncludedReferencedProjects, coverageProject.TestDllFile, - projectSettings.IncludeTestAssembly, - true); - + projectSettings.IncludeTestAssembly); + var settings = new CombinedIncludesExcludesOptions(projectSettings, additionalModulePathsInclude, additionalModulePathsExclude); return new RunSettingsTemplateReplacements(settings, coverageProject.CoverageOutputFolder, projectSettings.Enabled.ToString(), testAdapter); } From 158aedea14224e50a659fc24fc008e38f0357908 Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 11 Mar 2025 13:13:15 +0000 Subject: [PATCH 7/8] update readme --- README.md | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index bff50711..11ec91c6 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ or download from [releases](https://github.com/FortuneN/FineCodeCoverage/release For .Net -FCC supports the new [Microsoft.Testing.Platform]( https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-platform-intro). +FCC supports the new [Microsoft.Testing.Platform](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-platform-intro). FCC does not support the associated testing platform server mode for when using microsoft as a coverage provider. You can disable this feature - "Options / Environment / Preview Features / Use testing platform server mode" -but it is not necessary as FCC adds a global msbuild property, DisableTestingPlatformServerCapability true, that removes +but it is not necessary as FCC adds a global msbuild property, DisableTestingPlatformServerCapability true, that removes the project capability. ([see Microsoft.Testing.Platform.MSBuild.targets](https://github.com/microsoft/testfx/blob/d141931b99fad0617d8435ce321fca0c45c9eb94/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets#L10)). When not using Microsoft.TestingPlatform you have added test adapters through nuget packages. For instance, the NUnit Test Adapter extension is not sufficient. @@ -111,7 +111,7 @@ The old coverage was based upon every test. Ms code coverage is coverage from th Ms code coverage requires a [runsettings](https://docs.microsoft.com/en-us/visualstudio/test/configure-unit-tests-by-using-a-dot-runsettings-file?view=vs-2022) file that is configured appropriately for code coverage. This requires that you have the ms code coverage package and have pointed to it with the TestAdaptersPaths element as well as specifying the ms data collector. [Exclusions and inclusions](https://docs.microsoft.com/en-us/visualstudio/test/customizing-code-coverage-analysis?view=vs-2022#include-or-exclude-assemblies-and-members) -are also specified in the runsettings. I don't think that the documentation is clear enough on how this works so you may want to look at [this issue](https://github.com/microsoft/vstest/issues/3462). +are also specified in the runsettings. FCC does not require you to do this. If you do not provide a runsettings and RunMsCodeCoverage is Yes then FCC will generate one and write the necessary entry in the project file. Note that having a test project file open in visual studio whilst running tests may result in a conflict warning when FCC removes the entry at the end of the test. @@ -140,9 +140,12 @@ It is also possible to use your own runsettings file and have FCC add to it and ## Run settings defaults and merging -Ms code coverage does provide a default Configuration / CodeCoverage element if not provided. It will also add some default exclusions if not present or merge them in unless you add the attribute mergeDefaults='false'. -For instance it Attributes exclude ExcludeFromCodeCoverageAttribute. -If you are interested see ...\AppData\Local\FineCodeCoverage\msCodeCoverage_version\_\build\netstandard1.0\Microsoft.VisualStudio.TraceDataCollector.dll and the DynamicCoverageDataCollector. +Ms code coverage does provide a default Configuration / CodeCoverage element if not provided. It will also add some default exclusions for ModulePaths, Attributes, Sources, Functions, and PublicKeyTokens if not present in inclusions or exclusions unless you add the attribute mergeDefaults='false'. +For instance for Attributes it will exclude ExcludeFromCodeCoverageAttribute. + +If you are interested in using DotPeek or Reflector against the Microsoft source code within +...\AppData\Local\FineCodeCoverage\msCodeCoverage_version\_\build\netstandard2.0\ there is the Microsoft.VisualStudio.TraceDataCollector.dll. +see `DynamicCoverageDataCollector.OnInitialize` for the default configuration logic. The default config is in the resources of Microsoft.CodeCoverage.Core. ## Problems with ms code coverage @@ -347,7 +350,7 @@ If you are using option 1) then project and global options will only be used whe | DisabledNoCoverage | Set to false for VS Option Enabled=false to not disable coverage | | RunWhenTestsFail | By default coverage runs when tests fail. Set to false to prevent this. **Cannot be used in conjunction with RunInParallel** | | RunWhenTestsExceed | Specify a value to only run coverage based upon the number of executing tests. **Cannot be used in conjunction with RunInParallel** | -| RunMsCodeCoverage | Change to IfInRunSettings to only collect with configured runsettings. Yes (default) for runsettings generation. No to use Coverlet or OpenCover. | +| RunMsCodeCoverage | Change to IfInRunSettings to only collect with configured runsettings. Yes (default) for runsettings generation. No to use Coverlet or OpenCover. | | IncludeTestAssembly | Specifies whether to report code coverage of the test assembly | | IncludeReferencedProjects | Set to true to add all directly referenced projects to Include. | | IncludeAssemblies | Provide a list of assemblies to include in coverage. The dll name without extension is used for matching. | @@ -393,28 +396,34 @@ If you are using option 1) then project and global options will only be used whe ## Exclusions and inclusions -You probably want to set IncludeReferencedProjects to true. This will ensure that you do not get coverage for testing frameworks - only your code. - Coverlet and OpenCover use filter expressions. -Filter expressions - -Wildcards - -\* => matches zero or more characters -Examples -[\*]\* => All types in all assemblies. - -[coverlet\.\*]Coverlet.Core.Coverage => The Coverage class in the Coverlet.Core namespace belonging to any assembly that matches coverlet.\* (e.g coverlet.core) +Filter expressions -[\*\]Coverlet.Core.Instrumentation.\* => All types belonging to Coverlet.Core.Instrumentation namespace in any assembly +> Wildcards +> +> \* => matches zero or more characters +> +> Examples +> +> [\*]\* => All types in all assemblies. +> +> [coverlet\.\*]Coverlet.Core.Coverage => The Coverage class in the Coverlet.Core namespace belonging to any assembly that matches coverlet.\* (e.g coverlet.core) +> +> [\*\]Coverlet.Core.Instrumentation.\* => All types belonging to Coverlet.Core.Instrumentation namespace in any assembly +> +> [coverlet\.\*.tests]\* => All types in any assembly starting with coverlet. and ending with .tests +> +> Both 'Exclude' and 'Include' options can be used together but 'Exclude' takes precedence. -[coverlet\.\*.tests]\* => All types in any assembly starting with coverlet. and ending with .tests +Ms code coverage uses [regexes](https://learn.microsoft.com/en-us/visualstudio/test/customizing-code-coverage-analysis?view=vs-2022#regular-expressions). -Both 'Exclude' and 'Include' options can be used together but 'Exclude' takes precedence. +> You can include or exclude assemblies or specific types and members from code coverage analysis. If the Include section is empty or omitted, then all assemblies that are loaded and have associated PDB files are included. If an assembly or member matches a clause in the Exclude section, then it is excluded from code coverage. The Exclude section takes precedence over the Include section: if an assembly is listed in both Include and Exclude, it will not be included in code coverage. -Ms code coverage uses [regexes](https://learn.microsoft.com/en-us/visualstudio/test/customizing-code-coverage-analysis?view=vs-2022#regular-expressions). -You can include or exclude assemblies or specific types and members from code coverage analysis. If the Include section is empty or omitted, then all assemblies that are loaded and have associated PDB files are included. If an assembly or member matches a clause in the Exclude section, then it is excluded from code coverage. The Exclude section takes precedence over the Include section: if an assembly is listed in both Include and Exclude, it will not be included in code coverage. +Just remember that as soon as there is an include for an assembly all other assemblies that you require coverage for will also need to be included. ( [internals](https://github.com/FortuneN/FineCodeCoverage/issues/489) ) +If there are no includes then all assemblies will be included unless explicitly excluded. +Hence when IncludeTestAssembly is true FCC only explicitly includes it when there are other includes specified. +**You may want to set IncludeReferencedProjects to true. This will ensure that you do not get coverage for testing frameworks - only your code.** You can ignore a method or an entire class from code coverage by applying the [ExcludeFromCodeCoverage] attribute present in the System.Diagnostics.CodeAnalysis namespace. @@ -447,19 +456,19 @@ if you want to contribute to this project. For cloning and building this project yourself, make sure to install the [Extensibility Essentials](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.ExtensibilityEssentials2022) extension for Visual Studio which enables some features -used by this project. +used by this project. ## License [Apache 2.0](LICENSE) -## Credits +## Credits [Coverlet](https://github.com/coverlet-coverage/coverlet) [OpenCover](https://github.com/OpenCover/opencover) -[ReportGenerator](https://github.com/danielpalme/ReportGenerator) +[ReportGenerator](https://github.com/danielpalme/ReportGenerator) ## Please support the project From dac99c2e89f843cc4cb0b98c5f09e2f06ab1640a Mon Sep 17 00:00:00 2001 From: Tony Hallett Date: Tue, 11 Mar 2025 13:47:08 +0000 Subject: [PATCH 8/8] refactor --- .../RunSettingsTemplate_Tests.cs | 4 ++-- .../TemplatedRunSettingsService_Tests.cs | 2 +- .../CodeCoverage/IRunSettingsTemplate.cs | 6 +++++- .../IRunSettingsTemplateReplacements.cs | 4 ++++ .../CodeCoverage/RunSettingsTemplate.cs | 5 +---- .../TemplatedRunSettingsService.cs | 18 +++++++++--------- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplate_Tests.cs b/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplate_Tests.cs index ae82ff87..a54a7ffb 100644 --- a/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplate_Tests.cs +++ b/FineCodeCoverageTests/MsCodeCoverage/RunSettingsTemplate_Tests.cs @@ -13,7 +13,7 @@ public class RunSettingsTemplate_Tests public void Should_Be_Replaceable_With_Recommended_You_Do_Not_Change_Elements_When_Not_Provided(bool isDotNetFramework) { var runSettingsTemplate = new RunSettingsTemplate(); - var template = runSettingsTemplate.ToString(); + var template = runSettingsTemplate.Get(); var useVerifiableInstrumentation = isDotNetFramework ? "False" : "True"; var replacements = new RunSettingsTemplateReplacements @@ -118,7 +118,7 @@ public void Should_Be_Replaceable_With_Recommended_You_Do_Not_Change_Elements_Wh public void Should_Be_ReplacedTestAdapter_When_Template_Has_The_FCC_TestAdapter_Placeholder() { var runSettingsTemplate = new RunSettingsTemplate(); - var template = runSettingsTemplate.ToString(); + var template = runSettingsTemplate.Get(); Assert.True(runSettingsTemplate.ReplaceTemplate(template, new RunSettingsTemplateReplacements(), true).ReplacedTestAdapter); } diff --git a/FineCodeCoverageTests/MsCodeCoverage/TemplatedRunSettingsService_Tests.cs b/FineCodeCoverageTests/MsCodeCoverage/TemplatedRunSettingsService_Tests.cs index 70d718c2..5cf4d776 100644 --- a/FineCodeCoverageTests/MsCodeCoverage/TemplatedRunSettingsService_Tests.cs +++ b/FineCodeCoverageTests/MsCodeCoverage/TemplatedRunSettingsService_Tests.cs @@ -32,7 +32,7 @@ public async Task Should_Create_Run_Settings_From_Template_Async(bool isDotNetFr var coverageProjects = new List { coverageProject}; var mockRunSettingsTemplate = autoMocker.GetMock(); - mockRunSettingsTemplate.Setup(runSettingsTemplate => runSettingsTemplate.ToString()).Returns(""); + mockRunSettingsTemplate.Setup(runSettingsTemplate => runSettingsTemplate.Get()).Returns(""); var runSettingsTemplateReplacements = new RunSettingsTemplateReplacements(); var mockRunSettingsTemplateReplacementFactory = autoMocker.GetMock(); diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplate.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplate.cs index 5108110d..9e591748 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplate.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplate.cs @@ -16,7 +16,11 @@ ITemplateReplacementResult ReplaceTemplate( IRunSettingsTemplateReplacements replacements, bool isNetFrameworkProject); string Replace(string templatedXml, IRunSettingsTemplateReplacements replacements); - + + // returns a string representation of the runsettings xml containing markers for string replacement + string Get(); + + // returns a string representation of the runsettings xml containing markers for string replacement string ConfigureCustom(string runSettingsTemplate); string DataCollectionRunSettingsElement { get; } diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplateReplacements.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplateReplacements.cs index 9298e674..94efa0c8 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplateReplacements.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/IRunSettingsTemplateReplacements.cs @@ -1,10 +1,14 @@ namespace FineCodeCoverage.Engine.MsTestPlatform.CodeCoverage { + // string values for string.Replace of templated values - e.g %fcc_modulepaths_exclude% internal interface IRunSettingsTemplateReplacements { string Enabled { get; } string ResultsDirectory { get; } string TestAdapter { get; } + + // the following are xml fragments as strings + // e.g path1path2 string ModulePathsExclude { get; } string ModulePathsInclude { get; } string FunctionsExclude { get; } diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplate.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplate.cs index bcf6f2fb..ecd4317a 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplate.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/RunSettingsTemplate.cs @@ -164,10 +164,7 @@ public RunSettingsTemplate() "; } - public override string ToString() - { - return template; - } + public string Get() => template; public ITemplateReplacementResult ReplaceTemplate( string runSettingsTemplate, diff --git a/SharedProject/Core/MsTestPlatform/CodeCoverage/TemplatedRunSettingsService.cs b/SharedProject/Core/MsTestPlatform/CodeCoverage/TemplatedRunSettingsService.cs index 724b8362..3381ff73 100644 --- a/SharedProject/Core/MsTestPlatform/CodeCoverage/TemplatedRunSettingsService.cs +++ b/SharedProject/Core/MsTestPlatform/CodeCoverage/TemplatedRunSettingsService.cs @@ -129,8 +129,8 @@ string fccMsTestAdapterPath return coverageProjects.Select(coverageProject => { var projectDirectory = Path.GetDirectoryName(coverageProject.ProjectFile); - var (runSettingsTemplate, customTemplatePath) = GetRunSettingsTemplate(projectDirectory, solutionDirectory); - var templateReplaceResult = ReplaceTemplate(coverageProject, runSettingsTemplate, fccMsTestAdapterPath); + var (replaceableTemplate, customTemplatePath) = GetRunSettingsTemplate(projectDirectory, solutionDirectory); + var templateReplaceResult = ReplaceTemplate(coverageProject, replaceableTemplate, fccMsTestAdapterPath); return new TemplatedCoverageProjectRunSettingsResult { @@ -143,28 +143,28 @@ string fccMsTestAdapterPath }).ToList(); } - private (string Template, string CustomPath) GetRunSettingsTemplate(string projectDirectory, string solutionDirectory) + private (string ReplaceableTemplate, string CustomPath) GetRunSettingsTemplate(string projectDirectory, string solutionDirectory) { string customPath = null; - string template; + string replaceableTemplate; var customRunSettingsTemplateDetails = customRunSettingsTemplateProvider.Provide(projectDirectory, solutionDirectory); if (customRunSettingsTemplateDetails != null) { customPath = customRunSettingsTemplateDetails.Path; - template = runSettingsTemplate.ConfigureCustom(customRunSettingsTemplateDetails.Template); + replaceableTemplate = runSettingsTemplate.ConfigureCustom(customRunSettingsTemplateDetails.Template); } else { - template = runSettingsTemplate.ToString(); + replaceableTemplate = runSettingsTemplate.Get(); } - return (template, customPath); + return (replaceableTemplate, customPath); } - private ITemplateReplacementResult ReplaceTemplate(ICoverageProject coverageProject, string runSettingsTemplate, string fccMsTestAdapterPath) + private ITemplateReplacementResult ReplaceTemplate(ICoverageProject coverageProject, string replaceableTemplate, string fccMsTestAdapterPath) { var replacements = runSettingsTemplateReplacementsFactory.Create(coverageProject, fccMsTestAdapterPath); - return this.runSettingsTemplate.ReplaceTemplate(runSettingsTemplate, replacements, coverageProject.IsDotNetFramework); + return this.runSettingsTemplate.ReplaceTemplate(replaceableTemplate, replacements, coverageProject.IsDotNetFramework); } public Task CleanUpAsync(List coverageProjects)