From 16eda38beeab52a92da52bcd0d9044f73f91dc51 Mon Sep 17 00:00:00 2001 From: Bert Date: Wed, 14 Feb 2024 08:00:14 +0100 Subject: [PATCH] fix MergeWith option (#1601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix MergeWith (net8.0 and net7.0) * use range operator * add JsonSerializerOptions * consolidate nuget package versions * incorporate review comments * sanitize report path for all output variations --------- Co-authored-by: David Müller --- .config/dotnet-tools.json | 2 +- Directory.Build.props | 19 +++ Directory.Packages.props | 12 +- .../XUnitTestProject1.csproj | 2 +- .../XUnitTestProject1.csproj | 4 +- .../XUnitTestProject2.csproj | 2 +- .../XUnitTestProject3.csproj | 2 +- .../XUnitTestProject1.csproj | 2 +- .../XUnitTestProject1.csproj | 2 +- Documentation/MSBuildIntegration.md | 9 ++ eng/publish-coverage-results.yml | 2 +- .../DataCollection/AttachmentManager.cs | 7 +- .../DataCollection/CoverageManager.cs | 10 +- .../DataCollection/CoverletSettingsParser.cs | 4 +- .../CoverletInProcDataCollector.cs | 4 +- .../Resources/Resources.Designer.cs | 126 ---------------- .../Resources/Resources.resx | 141 ------------------ .../coverlet.collector.csproj | 27 ---- src/coverlet.core/Coverage.cs | 21 ++- src/coverlet.core/CoverageResult.cs | 2 +- src/coverlet.core/coverlet.core.csproj | 14 +- src/coverlet.msbuild.tasks/ReportWriter.cs | 7 +- .../Coverage/CoverageTests.cs | 140 ++++++++++++++++- .../TestAssets/MergeWith.coverage.json | 43 ++++++ .../coverlet.core.tests.csproj | 3 + ...verlet.integration.determisticbuild.csproj | 2 +- .../coverlet.integration.template.csproj | 2 +- .../coverlet.integration.tests.csproj | 3 +- .../coverlet.msbuild.tasks.tests/Reporters.cs | 7 +- .../Properties/AssemblyInfo.cs | 1 - .../coverlet.tests.xunit.extensions.csproj | 2 +- 31 files changed, 273 insertions(+), 351 deletions(-) delete mode 100644 src/coverlet.collector/Resources/Resources.Designer.cs delete mode 100644 src/coverlet.collector/Resources/Resources.resx create mode 100644 test/coverlet.core.tests/TestAssets/MergeWith.coverage.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index acf83a0be..924174dc1 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-reportgenerator-globaltool": { - "version": "5.2.0", + "version": "5.2.1", "commands": [ "reportgenerator" ] diff --git a/Directory.Build.props b/Directory.Build.props index 36526253d..91ee686a1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -53,6 +53,25 @@ 15.9.20 1.6.0 1.5.0 + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 6976c130a..015e0ee98 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,13 +29,13 @@ vstest 17.8 version NuGetFrameworksVersion is defined here https://github.com/microsoft/vstest/blob/9a0c41811637edf4afe0e265e08fdd1cb18109ed/eng/Versions.props#L94C1-L94C1 --> + - - - + + @@ -44,11 +44,11 @@ - + - + - + diff --git a/Documentation/Examples/MSBuild/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj b/Documentation/Examples/MSBuild/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj index a5048b133..19586970c 100644 --- a/Documentation/Examples/MSBuild/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj +++ b/Documentation/Examples/MSBuild/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject1/XUnitTestProject1.csproj b/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject1/XUnitTestProject1.csproj index e59657fc7..e78fcdd22 100644 --- a/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject1/XUnitTestProject1.csproj +++ b/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject1/XUnitTestProject1.csproj @@ -12,8 +12,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers diff --git a/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject2/XUnitTestProject2.csproj b/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject2/XUnitTestProject2.csproj index 6c791bc5d..4346c22c3 100644 --- a/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject2/XUnitTestProject2.csproj +++ b/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject2/XUnitTestProject2.csproj @@ -12,7 +12,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers diff --git a/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject3/XUnitTestProject3.csproj b/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject3/XUnitTestProject3.csproj index e85527020..4cdea2dbd 100644 --- a/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject3/XUnitTestProject3.csproj +++ b/Documentation/Examples/MSBuild/MergeWith/XUnitTestProject3/XUnitTestProject3.csproj @@ -12,7 +12,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers diff --git a/Documentation/Examples/VSTest/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj b/Documentation/Examples/VSTest/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj index 2f200b8cd..d63628b1e 100644 --- a/Documentation/Examples/VSTest/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj +++ b/Documentation/Examples/VSTest/DeterministicBuild/XUnitTestProject1/XUnitTestProject1.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Documentation/Examples/VSTest/HelloWorld/XUnitTestProject1/XUnitTestProject1.csproj b/Documentation/Examples/VSTest/HelloWorld/XUnitTestProject1/XUnitTestProject1.csproj index e77db159d..3ab4b931e 100644 --- a/Documentation/Examples/VSTest/HelloWorld/XUnitTestProject1/XUnitTestProject1.csproj +++ b/Documentation/Examples/VSTest/HelloWorld/XUnitTestProject1/XUnitTestProject1.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/Documentation/MSBuildIntegration.md b/Documentation/MSBuildIntegration.md index a097462e9..f3bd95251 100644 --- a/Documentation/MSBuildIntegration.md +++ b/Documentation/MSBuildIntegration.md @@ -50,6 +50,15 @@ To specify a directory where all results will be written to (especially if using dotnet test /p:CollectCoverage=true /p:CoverletOutput='./results/' ``` +_Note: escape characters might produce some unexpected results._ + +|status| msbuild parameter | +|---|---| +|:heavy_check_mark:|`/p:CoverletOutput="C:/GitHub/coverlet/artifacts/Reports/coverlet.core/"`| +|:heavy_check_mark:|`/p:CoverletOutput="C:\\GitHub\\coverlet\\artifacts\\Reports\\coverlet.core\\"`| +|:heavy_check_mark:|`/p:CoverletOutput="C:\GitHub\coverlet\artifacts\Reports\coverlet.core\\"`| +|:x:|`/p:CoverletOutput="C:\GitHub\coverlet\artifacts\Reports\coverlet.core\"`| + The Coverlet MSBuild task sets the `CoverletReport` MSBuild item so that you can easily use the produced coverage reports. For example, using [ReportGenerator](https://github.com/danielpalme/ReportGenerator#usage--command-line-parameters) to generate an html coverage report. ```xml diff --git a/eng/publish-coverage-results.yml b/eng/publish-coverage-results.yml index a74bac9ec..58dda6870 100644 --- a/eng/publish-coverage-results.yml +++ b/eng/publish-coverage-results.yml @@ -19,7 +19,7 @@ steps: script: | dotnet tool restore --add-source https://api.nuget.org/v3/index.json dotnet tool list - dotnet reportgenerator -reports:"${{parameters.reports}}" -targetdir:"$(Build.SourcesDirectory)\artifacts\CoverageReport" -reporttypes:"Html;HtmlInline_AzurePipelines_Dark;Cobertura" -assemblyfilters:"${{parameters.assemblyfilters}}" -classfilters:"${{parameters.classfilters}}" -verbosity:Verbose --minimumCoverageThresholds:lineCoverage=${{parameters.minimumLineCoverage}} + dotnet reportgenerator -reports:"${{parameters.reports}}" -targetdir:"$(Build.SourcesDirectory)\artifacts\CoverageReport" -reporttypes:"HtmlInline_AzurePipelines_Dark;Cobertura" -assemblyfilters:"${{parameters.assemblyfilters}}" -classfilters:"${{parameters.classfilters}}" -verbosity:Verbose --minimumCoverageThresholds:lineCoverage=${{parameters.minimumLineCoverage}} - publish: '$(Build.SourcesDirectory)/artifacts/CoverageReport' displayName: 'Publish CoverageReport Artifact' diff --git a/src/coverlet.collector/DataCollection/AttachmentManager.cs b/src/coverlet.collector/DataCollection/AttachmentManager.cs index 9a4a68786..a062a40bc 100644 --- a/src/coverlet.collector/DataCollection/AttachmentManager.cs +++ b/src/coverlet.collector/DataCollection/AttachmentManager.cs @@ -4,7 +4,6 @@ using System; using System.ComponentModel; using System.IO; -using coverlet.collector.Resources; using Coverlet.Collector.Utilities; using Coverlet.Collector.Utilities.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; @@ -109,8 +108,7 @@ private string SaveCoverageReport(string report, string reportFileName) } catch (Exception ex) { - string errorMessage = string.Format(Resources.FailedToSaveCoverageReport, CoverletConstants.DataCollectorName, reportFileName, _reportDirectory); - throw new CoverletDataCollectorException(errorMessage, ex); + throw new CoverletDataCollectorException($"{CoverletConstants.DataCollectorName}: Failed to save coverage report '{reportFileName}' in directory '{_reportDirectory}'", ex); } } @@ -168,8 +166,7 @@ private void CleanupReportDirectory() } catch (Exception ex) { - string errorMessage = string.Format(Resources.FailedToCleanupReportDirectory, CoverletConstants.DataCollectorName, _reportDirectory); - throw new CoverletDataCollectorException(errorMessage, ex); + throw new CoverletDataCollectorException($"{CoverletConstants.DataCollectorName}: Failed to cleanup report directory: '{_reportDirectory}'", ex); } } } diff --git a/src/coverlet.collector/DataCollection/CoverageManager.cs b/src/coverlet.collector/DataCollection/CoverageManager.cs index d6f6052c8..f0453501c 100644 --- a/src/coverlet.collector/DataCollection/CoverageManager.cs +++ b/src/coverlet.collector/DataCollection/CoverageManager.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using coverlet.collector.Resources; using Coverlet.Collector.Utilities; using Coverlet.Collector.Utilities.Interfaces; using Coverlet.Core; @@ -68,8 +67,7 @@ public void InstrumentModules() } catch (Exception ex) { - string errorMessage = string.Format(Resources.InstrumentationException, CoverletConstants.DataCollectorName); - throw new CoverletDataCollectorException(errorMessage, ex); + throw new CoverletDataCollectorException($"{CoverletConstants.DataCollectorName}: Failed to instrument modules", ex); } } @@ -96,8 +94,7 @@ private CoverageResult GetCoverageResult() } catch (Exception ex) { - string errorMessage = string.Format(Resources.CoverageResultException, CoverletConstants.DataCollectorName); - throw new CoverletDataCollectorException(errorMessage, ex); + throw new CoverletDataCollectorException($"{CoverletConstants.DataCollectorName}: Failed to get coverage result", ex); } } @@ -114,8 +111,7 @@ private CoverageResult GetCoverageResult() } catch (Exception ex) { - string errorMessage = string.Format(Resources.CoverageReportException, CoverletConstants.DataCollectorName); - throw new CoverletDataCollectorException(errorMessage, ex); + throw new CoverletDataCollectorException($"{CoverletConstants.DataCollectorName}: Failed to get coverage report", ex); } } } diff --git a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs index 29584281e..733dacfcc 100644 --- a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs +++ b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Xml; -using coverlet.collector.Resources; using Coverlet.Collector.Utilities; namespace Coverlet.Collector.DataCollection @@ -72,8 +71,7 @@ private static string ParseTestModule(IEnumerable testModules) // Validate if at least one source present. if (testModules == null || !testModules.Any()) { - string errorMessage = string.Format(Resources.NoTestModulesFound, CoverletConstants.DataCollectorName); - throw new CoverletDataCollectorException(errorMessage); + throw new CoverletDataCollectorException($"{CoverletConstants.DataCollectorName}: No test modules found"); } // Note: diff --git a/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs b/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs index b61d75f55..b1e6c5be2 100644 --- a/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs +++ b/src/coverlet.collector/InProcDataCollection/CoverletInProcDataCollector.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Reflection; using System.Text; -using coverlet.collector.Resources; using Coverlet.Collector.Utilities; using Coverlet.Core.Instrumentation; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; @@ -76,8 +75,7 @@ public void TestSessionEnd(TestSessionEndArgs testSessionEndArgs) if (_enableExceptionLog) { _eqtTrace.Error("{0}: Failed to unload module with error: {1}", CoverletConstants.InProcDataCollectorName, ex); - string errorMessage = string.Format(Resources.FailedToUnloadModule, CoverletConstants.InProcDataCollectorName); - throw new CoverletDataCollectorException(errorMessage, ex); + throw new CoverletDataCollectorException($"{CoverletConstants.InProcDataCollectorName}: Failed to unload module", ex); } } } diff --git a/src/coverlet.collector/Resources/Resources.Designer.cs b/src/coverlet.collector/Resources/Resources.Designer.cs deleted file mode 100644 index b8dab181e..000000000 --- a/src/coverlet.collector/Resources/Resources.Designer.cs +++ /dev/null @@ -1,126 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace coverlet.collector.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("coverlet.collector.Resources.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to {0}: Failed to get coverage report. - /// - internal static string CoverageReportException { - get { - return ResourceManager.GetString("CoverageReportException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}: Failed to get coverage result. - /// - internal static string CoverageResultException { - get { - return ResourceManager.GetString("CoverageResultException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}: Failed to cleanup report directory: '{1}'. - /// - internal static string FailedToCleanupReportDirectory { - get { - return ResourceManager.GetString("FailedToCleanupReportDirectory", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}: Failed to save coverage report '{1}' in directory '{2}'. - /// - internal static string FailedToSaveCoverageReport { - get { - return ResourceManager.GetString("FailedToSaveCoverageReport", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}: Failed to unload module. - /// - internal static string FailedToUnloadModule { - get { - return ResourceManager.GetString("FailedToUnloadModule", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}: Failed to instrument modules. - /// - internal static string InstrumentationException { - get { - return ResourceManager.GetString("InstrumentationException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}: No test modules found. - /// - internal static string NoTestModulesFound { - get { - return ResourceManager.GetString("NoTestModulesFound", resourceCulture); - } - } - } -} diff --git a/src/coverlet.collector/Resources/Resources.resx b/src/coverlet.collector/Resources/Resources.resx deleted file mode 100644 index af111547e..000000000 --- a/src/coverlet.collector/Resources/Resources.resx +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - {0}: Failed to get coverage report - - - {0}: Failed to get coverage result - - - {0}: Failed to cleanup report directory: '{1}' - - - {0}: Failed to save coverage report '{1}' in directory '{2}' - - - {0}: Failed to unload module - - - {0}: Failed to instrument modules - - - {0}: No test modules found - - \ No newline at end of file diff --git a/src/coverlet.collector/coverlet.collector.csproj b/src/coverlet.collector/coverlet.collector.csproj index 7b48b9a37..fc6145d80 100644 --- a/src/coverlet.collector/coverlet.collector.csproj +++ b/src/coverlet.collector/coverlet.collector.csproj @@ -49,33 +49,6 @@ - - - True - True - Resources.resx - - - True - True - Resources.resx - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - ResXFileCodeGenerator - Resource.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - - - diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 3c7778db3..3e0f46216 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -60,6 +60,14 @@ internal class Coverage public string Identifier { get; } + readonly JsonSerializerOptions _options = new() + { + PropertyNameCaseInsensitive = true, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + IncludeFields = true, + WriteIndented = true + }; + public Coverage(string moduleOrDirectory, CoverageParameters parameters, ILogger logger, @@ -311,10 +319,17 @@ public CoverageResult GetCoverageResult() var coverageResult = new CoverageResult { Identifier = Identifier, Modules = modules, InstrumentedResults = _results, Parameters = _parameters }; - if (!string.IsNullOrEmpty(_parameters.MergeWith) && !string.IsNullOrWhiteSpace(_parameters.MergeWith) && _fileSystem.Exists(_parameters.MergeWith)) + if (!string.IsNullOrEmpty(_parameters.MergeWith) && !string.IsNullOrWhiteSpace(_parameters.MergeWith)) { - string json = _fileSystem.ReadAllText(_parameters.MergeWith); - coverageResult.Merge(JsonSerializer.Deserialize(json)); + if (_fileSystem.Exists(_parameters.MergeWith)) + { + _logger.LogInformation($"MergeWith: '{_parameters.MergeWith}'."); + string json = _fileSystem.ReadAllText(_parameters.MergeWith); + coverageResult.Merge(JsonSerializer.Deserialize(json, _options)); + } else + { + _logger.LogInformation($"MergeWith: file '{_parameters.MergeWith}' does not exist."); + } } return coverageResult; diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index c547f6a40..3cc9ce084 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -24,7 +24,7 @@ internal class Branches : List { } internal class Method { [JsonConstructor] - public Method() + internal Method() { Lines = []; Branches = []; diff --git a/src/coverlet.core/coverlet.core.csproj b/src/coverlet.core/coverlet.core.csproj index 77ef61484..0492a403b 100644 --- a/src/coverlet.core/coverlet.core.csproj +++ b/src/coverlet.core/coverlet.core.csproj @@ -7,18 +7,18 @@ - - - - + + + + - + - - + + diff --git a/src/coverlet.msbuild.tasks/ReportWriter.cs b/src/coverlet.msbuild.tasks/ReportWriter.cs index 65225f098..2f5404295 100644 --- a/src/coverlet.msbuild.tasks/ReportWriter.cs +++ b/src/coverlet.msbuild.tasks/ReportWriter.cs @@ -30,8 +30,8 @@ public string WriteReport() if (filename == string.Empty) { - // empty filename for instance only directory is passed to CoverletOutput c:\reportpath - // c:\reportpath\coverage.reportedextension + // empty filename for instance only directory is passed to CoverletOutput 'c:/reportpath/' + // note: use always '/' and not backslash => 'c:\reportpath\coverage.cobertura.xml' filename = $"coverage.{_coverletMultiTargetFrameworksCurrentTFM}{separatorPoint}{_reporter.Extension}"; } else if (Path.HasExtension(filename)) @@ -47,8 +47,9 @@ public string WriteReport() filename = $"{filename}{separatorPoint}{_coverletMultiTargetFrameworksCurrentTFM}.{_reporter.Extension}"; } - string report = Path.Combine(_directory, filename); + string report = Path.Combine(Path.GetFullPath(_directory), filename); _fileSystem.WriteAllText(report, _reporter.Report(_result, _sourceRootTranslator)); + return report; } } diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs index ccd293760..25761f83d 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs @@ -2,9 +2,15 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; using Coverlet.Core.Abstractions; using Coverlet.Core.Helpers; +using Coverlet.Core.Instrumentation; using Coverlet.Core.Symbols; using Moq; using Xunit; @@ -14,6 +20,16 @@ namespace Coverlet.Core.Tests public partial class CoverageTests { private readonly Mock _mockLogger = new(); + readonly JsonSerializerOptions _options = new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + IncludeFields = true, + WriteIndented = true, + Converters = + { + new BranchDictionaryConverterFactory() + } + }; [Fact] public void TestCoverage() @@ -86,11 +102,131 @@ public void TestCoverageWithTestAssembly() new SourceRootTranslator(module, _mockLogger.Object, new FileSystem(), new AssemblyAdapter()), new CecilSymbolHelper()); coverage.PrepareModules(); - CoverageResult result = coverage.GetCoverageResult(); + string result = JsonSerializer.Serialize(coverage.GetCoverageResult(), _options); + + Assert.Contains("coverlet.core.tests.dll", result); + + directory.Delete(true); + } + + [Fact] + public void TestCoverageMergeWithParameter() + { + string module = GetType().Assembly.Location; + string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); + + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + + File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); + File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); + + // TODO: Find a way to mimick hits + var instrumentationHelper = + new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, + new SourceRootTranslator(module, new Mock().Object, new FileSystem(), new AssemblyAdapter())); + + var parameters = new CoverageParameters + { + IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, + IncludeDirectories = Array.Empty(), + ExcludeFilters = Array.Empty(), + ExcludedSourceFiles = Array.Empty(), + ExcludeAttributes = Array.Empty(), + IncludeTestAssembly = false, + SingleHit = false, + MergeWith = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "MergeWith.coverage.json").First(), + UseSourceLink = false + }; + + var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + coverage.PrepareModules(); + + string result = JsonSerializer.Serialize(coverage.GetCoverageResult(), _options); + + Assert.Contains("DeepThought.cs", result); + + _mockLogger.Verify(l => l.LogInformation(It.Is(v => v.StartsWith("MergeWith: '") && v.EndsWith("MergeWith.coverage.json'.")), It.IsAny()), Times.Once); + + directory.Delete(true); + } + + [Fact] + public void TestCoverageMergeWithWrongParameter() + { + string module = GetType().Assembly.Location; + string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); - Assert.NotEmpty(result.Modules); + DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); + + File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); + File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); + + // TODO: Find a way to mimick hits + var instrumentationHelper = + new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, + new SourceRootTranslator(module, new Mock().Object, new FileSystem(), new AssemblyAdapter())); + + var parameters = new CoverageParameters + { + IncludeFilters = new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, + IncludeDirectories = Array.Empty(), + ExcludeFilters = Array.Empty(), + ExcludedSourceFiles = Array.Empty(), + ExcludeAttributes = Array.Empty(), + IncludeTestAssembly = false, + SingleHit = false, + MergeWith = "FileDoesNotExist.json", + UseSourceLink = false + }; + + var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + coverage.PrepareModules(); + + string result = JsonSerializer.Serialize(coverage.GetCoverageResult(), _options); + + _mockLogger.Verify(l => l.LogInformation(It.Is(v => v.Equals("MergeWith: file 'FileDoesNotExist.json' does not exist.")), It.IsAny()), Times.Once); directory.Delete(true); } } } +public class BranchDictionaryConverterFactory : JsonConverterFactory +{ + public override bool CanConvert(Type typeToConvert) + { + return typeof(Dictionary).IsAssignableFrom(typeToConvert); + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + Type[] genericArgs = typeToConvert.GetGenericArguments(); + Type keyType = genericArgs[0]; + Type valueType = genericArgs[1]; + + JsonConverter converter = (JsonConverter)Activator.CreateInstance( + typeof(BranchDictionaryConverter<,>).MakeGenericType(new Type[] { keyType, valueType })); + + return converter; + } +} + +public class BranchDictionaryConverter : JsonConverter> +{ + public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (KeyValuePair pair in value) + { + writer.WritePropertyName(pair.Key.ToString()); + JsonSerializer.Serialize(writer, pair.Value, options); + } + + writer.WriteEndObject(); + } +} diff --git a/test/coverlet.core.tests/TestAssets/MergeWith.coverage.json b/test/coverlet.core.tests/TestAssets/MergeWith.coverage.json new file mode 100644 index 000000000..ab9608617 --- /dev/null +++ b/test/coverlet.core.tests/TestAssets/MergeWith.coverage.json @@ -0,0 +1,43 @@ +{ + "coverletsamplelib.integration.template.dll": { + "C:\\GitHub\\coverlet\\artifacts\\bin\\coverlet.integration.tests\\debug_net7.0\\01021711\\Program.cs": { + "HelloWorld.Program": { + "System.Void HelloWorld.Program::Main(System.String[])": { + "Lines": { + "10": 1, + "11": 1, + "12": 1, + "13": 1, + "14": 1 + }, + "Branches": [] + } + } + }, + "C:\\GitHub\\coverlet\\artifacts\\bin\\coverlet.integration.tests\\debug_net7.0\\01021711\\DeepThought.cs": { + "Coverlet.Integration.Template.DeepThought": { + "System.Int32 Coverlet.Integration.Template.DeepThought::AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything()": { + "Lines": { + "6": 1, + "7": 1, + "8": 1 + }, + "Branches": [] + } + } + }, + "C:\\GitHub\\coverlet\\artifacts\\bin\\coverlet.integration.tests\\debug_net7.0\\01021711\\TemplateTest.cs": { + "Coverlet.Integration.Template.TemplateTest": { + "System.Void Coverlet.Integration.Template.TemplateTest::Answer()": { + "Lines": { + "9": 0, + "10": 0, + "11": 0, + "12": 0 + }, + "Branches": [] + } + } + } + } +} diff --git a/test/coverlet.core.tests/coverlet.core.tests.csproj b/test/coverlet.core.tests/coverlet.core.tests.csproj index bf8b35f17..314575a09 100644 --- a/test/coverlet.core.tests/coverlet.core.tests.csproj +++ b/test/coverlet.core.tests/coverlet.core.tests.csproj @@ -55,6 +55,9 @@ Always + + Always + Always diff --git a/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj b/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj index 0f1e39197..584a6fef6 100644 --- a/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj +++ b/test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj @@ -25,7 +25,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/coverlet.integration.template/coverlet.integration.template.csproj b/test/coverlet.integration.template/coverlet.integration.template.csproj index 759a71f66..0ac2c4764 100644 --- a/test/coverlet.integration.template/coverlet.integration.template.csproj +++ b/test/coverlet.integration.template/coverlet.integration.template.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/coverlet.integration.tests/coverlet.integration.tests.csproj b/test/coverlet.integration.tests/coverlet.integration.tests.csproj index ae5e1804d..e685ce525 100644 --- a/test/coverlet.integration.tests/coverlet.integration.tests.csproj +++ b/test/coverlet.integration.tests/coverlet.integration.tests.csproj @@ -10,9 +10,8 @@ - - + diff --git a/test/coverlet.msbuild.tasks.tests/Reporters.cs b/test/coverlet.msbuild.tasks.tests/Reporters.cs index a38563a41..16450f1f7 100644 --- a/test/coverlet.msbuild.tasks.tests/Reporters.cs +++ b/test/coverlet.msbuild.tasks.tests/Reporters.cs @@ -21,14 +21,12 @@ public class Reporters [InlineData(null, "/folder/reportFolder/file.ext", "cobertura", "/folder/reportFolder/file.ext")] [InlineData(null, "/folder/reportFolder/file.ext1.ext2", "cobertura", "/folder/reportFolder/file.ext1.ext2")] [InlineData(null, "/folder/reportFolder/file", "cobertura", "/folder/reportFolder/file.cobertura.xml")] - [InlineData(null, "file", "cobertura", "file.cobertura.xml")] // multiple tfm [InlineData("netcoreapp2.2", "/folder/reportFolder/", "lcov", "/folder/reportFolder/coverage.netcoreapp2.2.info")] [InlineData("netcoreapp2.2", "/folder/reportFolder/", "cobertura", "/folder/reportFolder/coverage.netcoreapp2.2.cobertura.xml")] [InlineData("net472", "/folder/reportFolder/file.ext", "cobertura", "/folder/reportFolder/file.net472.ext")] [InlineData("net472", "/folder/reportFolder/file.ext1.ext2", "cobertura", "/folder/reportFolder/file.ext1.net472.ext2")] [InlineData("netcoreapp2.2", "/folder/reportFolder/file", "cobertura", "/folder/reportFolder/file.netcoreapp2.2.cobertura.xml")] - [InlineData("netcoreapp2.2", "file", "cobertura", "file.netcoreapp2.2.cobertura.xml")] public void Msbuild_ReportWriter(string? coverletMultiTargetFrameworksCurrentTFM, string coverletOutput, string reportFormat, string expectedFileName) { var fileSystem = new Mock(); @@ -43,6 +41,11 @@ public void Msbuild_ReportWriter(string? coverletMultiTargetFrameworksCurrentTFM new CoverageResult() { Modules = new Modules(), Parameters = new CoverageParameters() }, null); string path = reportWriter.WriteReport(); + //remove drive for windows paths + if (path.Contains(':')) + { + path = path[2..]; + } // Path.Combine depends on OS so we can change only win side to avoid duplication Assert.Equal(path.Replace('/', Path.DirectorySeparatorChar), expectedFileName.Replace('/', Path.DirectorySeparatorChar)); } diff --git a/test/coverlet.tests.utils/Properties/AssemblyInfo.cs b/test/coverlet.tests.utils/Properties/AssemblyInfo.cs index 081699970..44bd6fece 100644 --- a/test/coverlet.tests.utils/Properties/AssemblyInfo.cs +++ b/test/coverlet.tests.utils/Properties/AssemblyInfo.cs @@ -8,4 +8,3 @@ [assembly: InternalsVisibleTo("coverlet.core.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100757cf9291d78a82e5bb58a827a3c46c2f959318327ad30d1b52e918321ffbd847fb21565b8576d2a3a24562a93e86c77a298b564a0f1b98f63d7a1441a3a8bcc206da3ed09d5dacc76e122a109a9d3ac608e21a054d667a2bae98510a1f0f653c0e6f58f42b4b3934f6012f5ec4a09b3dfd3e14d437ede1424bdb722aead64ad")] [assembly: InternalsVisibleTo("coverlet.integration.tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010001d24efbe9cbc2dc49b7a3d2ae34ca37cfb69b4f450acd768a22ce5cd021c8a38ae7dc68b2809a1ac606ad531b578f192a5690b2986990cbda4dd84ec65a3a4c1c36f6d7bb18f08592b93091535eaee2f0c8e48763ed7f190db2008e1f9e0facd5c0df5aaab74febd3430e09a428a72e5e6b88357f92d78e47512d46ebdc3cbb")] - diff --git a/test/coverlet.tests.xunit.extensions/coverlet.tests.xunit.extensions.csproj b/test/coverlet.tests.xunit.extensions/coverlet.tests.xunit.extensions.csproj index 6a67a0006..f3b1a3aac 100644 --- a/test/coverlet.tests.xunit.extensions/coverlet.tests.xunit.extensions.csproj +++ b/test/coverlet.tests.xunit.extensions/coverlet.tests.xunit.extensions.csproj @@ -8,6 +8,6 @@ - +