diff --git a/src/Nuke/IHaveCodeCoverage.cs b/src/Nuke/IHaveCodeCoverage.cs index 3c753f1c..32cb4266 100644 --- a/src/Nuke/IHaveCodeCoverage.cs +++ b/src/Nuke/IHaveCodeCoverage.cs @@ -1,3 +1,4 @@ +using System.Xml.Linq; using Nuke.Common.IO; namespace Rocket.Surgery.Nuke; @@ -20,11 +21,13 @@ public interface IHaveCodeCoverage : IHaveArtifacts ?? TryGetValue(() => CoverageDirectory) ?? NukeBuild.RootDirectory / "coverage"; - public IEnumerable IncludeModulePaths => []; - public IEnumerable ExcludeModulePaths => []; - public IEnumerable IncludeAttributes => []; + public static IEnumerable DefaultIncludeModulePaths => []; + public static IEnumerable DefaultExcludeModulePaths => []; + public static IEnumerable DefaultIncludeSources => []; + public static IEnumerable DefaultExcludeSources => []; + public static IEnumerable DefaultIncludeAttributes => []; - public IEnumerable ExcludeAttributes => + public static IEnumerable DefaultExcludeAttributes => [ "System.Diagnostics.DebuggerHiddenAttribute", "System.Diagnostics.DebuggerNonUserCodeAttribute", @@ -33,23 +36,37 @@ public interface IHaveCodeCoverage : IHaveArtifacts "System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute", ]; - public IEnumerable IncludeNamespaces => []; + public static IEnumerable DefaultIncludeNamespaces => []; - public IEnumerable ExcludeNamespaces => + public static IEnumerable DefaultExcludeNamespaces => [ - "Bogus.", - "FakeItEasy.", - "Moq.", - "NSubstitute.", - "Verify.", - "XUnit.", - "TUnit.", + "Bogus", + "FakeItEasy", + "Moq", + "NSubstitute", + "Verify", + "XUnit", + "TUnit", "Microsoft.", "System.", "JetBrains.", - "DryIoc.", - "Nuke.", - "FluentAssertions.", - "Serilog.", + "DryIoc", + "Nuke", + "FluentAssertions", + "Serilog", ]; + + public IEnumerable IncludeNamespaces => DefaultIncludeNamespaces; + public IEnumerable ExcludeNamespaces => DefaultExcludeNamespaces; + public IEnumerable IncludeAttributes => DefaultIncludeAttributes; + public IEnumerable ExcludeAttributes => DefaultExcludeAttributes; + public IEnumerable IncludeSources => DefaultIncludeSources; + public IEnumerable ExcludeSources => DefaultExcludeSources; + public IEnumerable IncludeModulePaths => DefaultIncludeModulePaths; + public IEnumerable ExcludeModulePaths => DefaultExcludeModulePaths; + + public XDocument CustomizeCoverageRunSettings(XDocument document) + { + return document; + } } diff --git a/src/Nuke/ITriggerCodeCoverageReports.cs b/src/Nuke/ITriggerCodeCoverageReports.cs index 0e172847..d5b05ca0 100644 --- a/src/Nuke/ITriggerCodeCoverageReports.cs +++ b/src/Nuke/ITriggerCodeCoverageReports.cs @@ -1,6 +1,7 @@ using Nuke.Common.IO; using Nuke.Common.Tooling; using Nuke.Common.Tools.ReportGenerator; +using Rocket.Surgery.Nuke.ProjectModel; // ReSharper disable SuspiciousTypeConversion.Global @@ -13,7 +14,7 @@ namespace Rocket.Surgery.Nuke; /// This causes code coverage to trigger /// [PublicAPI] -public interface ITriggerCodeCoverageReports : IHaveCodeCoverage, IHaveTestTarget, IHaveTestArtifacts, ITrigger +public interface ITriggerCodeCoverageReports : IHaveCodeCoverage, IHaveTestTarget, IHaveTestArtifacts, ITrigger, IHaveSolution { /// /// The input reports @@ -89,17 +90,38 @@ public interface ITriggerCodeCoverageReports : IHaveCodeCoverage, IHaveTestTarge /// /// /// - protected ReportGeneratorSettings Defaults(ReportGeneratorSettings settings) => ( this switch - { - IHaveGitVersion gitVersion => settings.SetTag( - gitVersion.GitVersion.InformationalVersion - ), - IHaveGitRepository { GitRepository: { } } gitRepository => settings - .SetTag(gitRepository.GitRepository.Head), - _ => settings, - } - ) - .SetReports(InputReports) - .SetSourceDirectories(NukeBuild.RootDirectory) - .SetFramework(Constants.ReportGeneratorFramework); + protected ReportGeneratorSettings Defaults(ReportGeneratorSettings settings) + => ( this switch + { + IHaveGitVersion gitVersion => settings.SetTag( + gitVersion.GitVersion.InformationalVersion + ), + IHaveGitRepository { GitRepository: { } } gitRepository => settings + .SetTag(gitRepository.GitRepository.Head), + _ => settings, + } + ) + .SetReports(InputReports) + .SetSourceDirectories(NukeBuild.RootDirectory) + .SetFramework(Constants.ReportGeneratorFramework) + // this is more or less a hack / compromise because + // I was unable to coverage to exclude everything in a given assembly by default. + .AddAssemblyFilters( + Solution + .AnalyzeAllProjects() + .Select(z => z.GetProperty("AssemblyName") ?? "") + .Where(z => !string.IsNullOrWhiteSpace(z)) + .Distinct() + .Select(z => "+" + z) + ) + .AddAssemblyFilters( + Solution + .AnalyzeAllProjects() + .SelectMany(z => z.PackageReferences) + .Select(z => z.Name) + .Where(z => !string.IsNullOrWhiteSpace(z)) + .Distinct() + .Select(z => "-" + z) + ) + ; } diff --git a/src/Nuke/TestMethodExtensions.cs b/src/Nuke/TestMethodExtensions.cs index 8282a80e..ba5699d8 100644 --- a/src/Nuke/TestMethodExtensions.cs +++ b/src/Nuke/TestMethodExtensions.cs @@ -1,6 +1,9 @@ +using System.Collections.Immutable; using System.Xml.Linq; using Nuke.Common.IO; +using Nuke.Common.ProjectModel; using Rocket.Surgery.Nuke.DotNetCore; +using Rocket.Surgery.Nuke.ProjectModel; namespace Rocket.Surgery.Nuke; @@ -19,7 +22,7 @@ public static class TestMethodExtensions // ReSharper disable once IdentifierTypo // ReSharper disable once StringLiteralTypo public static ITargetDefinition EnsureRunSettingsExists(this ITargetDefinition target, T build) - where T : IHaveCodeCoverage, IComprehendTests + where T : IHaveCodeCoverage, IComprehendTests, IHaveSolution { return target.Executes( async () => @@ -38,28 +41,46 @@ await typeof(ICanTestWithDotNetCore) .GetManifestResourceStream("Rocket.Surgery.Nuke.default.runsettings")!.CopyToAsync(tempFile); } + var projects = build + .Solution.AnalyzeAllProjects() + .ToImmutableArray(); + var includeNames = projects + .Select(z => z.GetProperty("AssemblyName") ?? "") + .Where(z => !string.IsNullOrWhiteSpace(z)) + .Distinct() + .Select(z => ( z + ".dll" ).Replace(".", "\\.")); + var excludePackages = projects + .SelectMany(z => z.PackageReferences) + .Select(z => z.Name) + .Where(z => !string.IsNullOrWhiteSpace(z)) + .Distinct() + .Select(z => ( z + ".dll" ).Replace(".", "\\.")); + + ManageRunSettings( + build, runsettings, - build.IncludeModulePaths, - build.ExcludeModulePaths, - build.IncludeAttributes, - build.ExcludeAttributes, - build.IncludeNamespaces, - build.ExcludeNamespaces + ( + build.IncludeModulePaths.Union(includeNames), + build.ExcludeModulePaths .Union(excludePackages) + ), + ( build.IncludeAttributes, build.ExcludeAttributes ), + ( build.IncludeNamespaces, build.ExcludeNamespaces ), + ( build.IncludeSources, build.ExcludeSources ) ); } ); } - private static void ManageRunSettings( + private static void ManageRunSettings( + T build, AbsolutePath runsettingsPath, - IEnumerable includeModulePaths, - IEnumerable excludeModulePaths, - IEnumerable includeAttributes, - IEnumerable excludeAttributes, - IEnumerable includeNamespaces, - IEnumerable excludeNamespaces + (IEnumerable include, IEnumerable exclude) modulePaths, + (IEnumerable include, IEnumerable exclude) attributes, + (IEnumerable include, IEnumerable exclude) namespaces, + (IEnumerable include, IEnumerable exclude) sources ) + where T : IHaveCodeCoverage, IComprehendTests { var doc = XDocument.Load(runsettingsPath); @@ -81,28 +102,29 @@ IEnumerable excludeNamespaces dataCollector.Element("Configuration")?.Add(codeCoverage); } - AddIncludeItems(codeCoverage, "ModulePaths", "ModulePath", includeModulePaths); - AddExcludeItems(codeCoverage, "ModulePaths", "ModulePath", excludeModulePaths); - AddIncludeItems(codeCoverage, "Attributes", "Attribute", includeAttributes.Select(TransformAttribute)); - AddExcludeItems(codeCoverage, "Attributes", "Attribute", excludeAttributes.Select(TransformAttribute)); - AddIncludeItems(codeCoverage, "Functions", "Function", includeNamespaces.Select(TransformNamespace)); - AddExcludeItems(codeCoverage, "Functions", "Function", excludeNamespaces.Select(TransformNamespace)); + AddItems(codeCoverage, "Attributes", "Attribute", transform(attributes, transformAttribute)); + AddItems(codeCoverage, "Functions", "Function", transform(namespaces, transformNamespace)); + AddItems(codeCoverage, "ModulePaths", "ModulePath", transform(modulePaths, transformModulePath)); + AddItems(codeCoverage, "Sources", "Source", sources); + + build.CustomizeCoverageRunSettings(doc); - DistinctAndOrganize(codeCoverage, "ModulePaths"); DistinctAndOrganize(codeCoverage, "Attributes"); DistinctAndOrganize(codeCoverage, "Functions"); + DistinctAndOrganize(codeCoverage, "ModulePaths"); + DistinctAndOrganize(codeCoverage, "Sources"); doc.Save(runsettingsPath); - static string TransformAttribute(string ns) - { - return $"^{ns.Replace(".", "\\.")}$"; - } + static (IEnumerable include, IEnumerable exclude) transform( + (IEnumerable include, IEnumerable exclude) attributes, + Func> transformer + ) => ( attributes.include.SelectMany(transformer), attributes.exclude.SelectMany(transformer) ); - static string TransformNamespace(string ns) - { - return $"^{ns.Replace(".", "\\.")}.*"; - } + + static IEnumerable transformAttribute(string attr) => [$"^{attr.Replace(".", "\\.")}$"]; + static IEnumerable transformModulePath(string ns) => [$".*{ns}"]; + static IEnumerable transformNamespace(string ns) => [$"^{ns.Replace(".", "\\.")}.*"]; } private static XElement EnsureElement(XElement parent, string name) @@ -117,34 +139,30 @@ private static XElement EnsureElement(XElement parent, string name) return element; } - private static void AddIncludeItems(XElement parent, string parentName, string childName, IEnumerable values) + private static void AddItems(XElement parent, string parentName, string childName, (IEnumerable include, IEnumerable exclude) values) { var parentElement = EnsureElement(parent, parentName); - var element = EnsureElement(parentElement, "Include"); + var include = EnsureElement(parentElement, "Include"); + var exclude = EnsureElement(parentElement, "Exclude"); - element.RemoveAll(); - foreach (var value in values) + include.RemoveAll(); + exclude.RemoveAll(); + + foreach (var value in values.include) { - element.Add(new XElement(childName, value)); + include.Add(new XElement(childName, value)); } - } - private static void AddExcludeItems(XElement parent, string parentName, string childName, IEnumerable values) - { - var parentElement = EnsureElement(parent, parentName); - var element = EnsureElement(parentElement, "Exclude"); - - element.RemoveAll(); - foreach (var value in values) + foreach (var value in values.exclude) { - element.Add(new XElement(childName, value)); + exclude.Add(new XElement(childName, value)); } } private static void DistinctAndOrganize(XElement parent, string parentName) { - var element = EnsureElement(parent, parentName); - if (element.Element("Include") is { } include) + if (parent.Element(parentName) is not { } item) return; + if (item.Element("Include") is { } include) { var values = include.Elements().DistinctBy(z => z.Value).OrderBy(x => x.Value).ToArray(); include.RemoveAll(); @@ -152,12 +170,14 @@ private static void DistinctAndOrganize(XElement parent, string parentName) if (!include.Elements().Any()) include.Remove(); } - if (element.Element("Exclude") is { } exclude) + if (item.Element("Exclude") is { } exclude) { var values = exclude.Elements().DistinctBy(z => z.Value).OrderBy(x => x.Value).ToArray(); exclude.RemoveAll(); exclude.Add([..values]); if (!exclude.Elements().Any()) exclude.Remove(); } + + if (!item.Elements().Any()) item.Remove(); } } diff --git a/src/Nuke/Xamarin/ICanTestXamarin.cs b/src/Nuke/Xamarin/ICanTestXamarin.cs index 116a4f7f..1e9b134e 100644 --- a/src/Nuke/Xamarin/ICanTestXamarin.cs +++ b/src/Nuke/Xamarin/ICanTestXamarin.cs @@ -37,7 +37,7 @@ public interface ICanTestXamarin : IHaveTestTarget, .SetLoggers("trx") .SetProperty("CollectCoverage", "true") .SetProperty( - "DeterministicSourcePaths", + "DeterministicSourcePaths", "false" ) // DeterministicSourcePaths being true breaks coverlet! .SetProperty("CoverageDirectory", CoverageDirectory) diff --git a/src/Nuke/default.runsettings b/src/Nuke/default.runsettings index 8f797263..6ba7cd98 100644 --- a/src/Nuke/default.runsettings +++ b/src/Nuke/default.runsettings @@ -1,47 +1,17 @@ - + + - + Cobertura True True - - - .*DryIoc.dll - .*FluentAssertions.dll - - - - - ^System\.Diagnostics\.DebuggerHiddenAttribute$ - ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$ - ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ - ^System\.Runtime\.CompilerServices\.CompilerGeneratedAttribute$ - ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$ - - - - - ^Bogus\..* - ^FakeItEasy\..* - ^Moq\..* - ^NSubstitute\..* - ^Verify\..* - ^XUnit\..* - ^TUnit\..* - ^Microsoft\..* - ^System\..* - ^JetBrains\..* - ^DryIoc\..* - ^Nuke\..* - ^FluentAssertions\..* - ^Serilog\..* - - + + + + diff --git a/test/coverage.runsettings b/test/coverage.runsettings index 99e36fa8..1dada981 100644 --- a/test/coverage.runsettings +++ b/test/coverage.runsettings @@ -2,13 +2,12 @@ - + Cobertura True True - ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ @@ -20,22 +19,75 @@ - ^Bogus\..* - ^DryIoc\..* - ^FakeItEasy\..* - ^FluentAssertions\..* + ^Bogus.* + ^DryIoc.* + ^FakeItEasy.* + ^FluentAssertions.* ^JetBrains\..* ^Microsoft\..* - ^Moq\..* - ^NSubstitute\..* - ^Nuke\..* - ^Serilog\..* + ^Moq.* + ^NSubstitute.* + ^Nuke.* + ^Serilog.* ^System\..* - ^TUnit\..* - ^Verify\..* - ^XUnit\..* + ^TUnit.* + ^Verify.* + ^XUnit.* + + + .*\.build\.dll + .*Rocket\.Surgery\.Nuke\.dll + .*Rocket\.Surgery\.Nuke\.Tests\.dll + + + .*Bogus\.dll + .*Buildalyzer\.dll + .*FakeItEasy\.Analyzer\.CSharp\.dll + .*FakeItEasy\.dll + .*FluentAssertions\.Analyzers\.dll + .*FluentAssertions\.dll + .*GitVersion\.Tool\.dll + .*Humanizer\.Core\.dll + .*JetBrains\.Annotations\.dll + .*JetBrains\.dotCover\.CommandLineTools\.dll + .*JetBrains\.ExternalAnnotations\.dll + .*JetBrains\.ReSharper\.GlobalTools\.dll + .*LibGit2Sharp\.dll + .*Microsoft\.Build\.dll + .*Microsoft\.Build\.Framework\.dll + .*Microsoft\.Build\.Locator\.dll + .*Microsoft\.Build\.Tasks\.Core\.dll + .*Microsoft\.Build\.Utilities\.Core\.dll + .*Microsoft\.CodeAnalysis\.Analyzers\.dll + .*Microsoft\.CodeAnalysis\.BannedApiAnalyzers\.dll + .*Microsoft\.CodeAnalysis\.PublicApiAnalyzers\.dll + .*Microsoft\.CodeCoverage\.dll + .*Microsoft\.Extensions\.FileSystemGlobbing\.dll + .*Microsoft\.NET\.Test\.Sdk\.dll + .*Microsoft\.SourceLink\.GitHub\.dll + .*Nuke\.Common\.dll + .*ReportGenerator\.dll + .*Rocket\.Surgery\.Extensions\.Testing\.FakeItEasy\.dll + .*Rocket\.Surgery\.Extensions\.Testing\.XUnit\.dll + .*Rocket\.Surgery\.MSBuild\.CI\.dll + .*Rocket\.Surgery\.MSBuild\.GitVersion\.dll + .*Rocket\.Surgery\.MSBuild\.SourceLink\.dll + .*Roslynator\.Analyzers\.dll + .*Roslynator\.CodeAnalysis\.Analyzers\.dll + .*Roslynator\.CodeFixes\.dll + .*Roslynator\.Formatting\.Analyzers\.dll + .*Roslynator\.Refactorings\.dll + .*Serilog\.Extensions\.Logging\.dll + .*System\.Collections\.Immutable\.dll + .*System\.Interactive\.Async\.dll + .*xunit\.analyzers\.dll + .*xunit\.dll + .*xunit\.runner\.visualstudio\.dll + .*YamlDotNet\.dll + +