-
Notifications
You must be signed in to change notification settings - Fork 389
Solution based merging for data collector #1676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
daveMueller
wants to merge
22
commits into
coverlet-coverage:master
Choose a base branch
from
daveMueller:1307_solution-based-merging-collector
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
9627e72
initial implementation
a6966ba
prototyping
daveMueller 453d589
nit
00d38bb
added some todos
daveMueller 7a3a3ce
Merge branch '1307_solution-based-merging-collector' of https://githu…
daveMueller 62e4f58
further development for json format
daveMueller 44785d9
refactorings
daveMueller 92b0fea
nit
daveMueller 1b912e4
nit
daveMueller f67b976
adaptions
daveMueller c6f5f0e
added new parameter, removed requirement for json format setting
daveMueller 3e40ef5
refactoring (untested)
daveMueller ada8b1e
Merge branch 'coverlet-coverage:master' into 1307_solution-based-merg…
daveMueller ea1cb6d
debug
daveMueller 5b29253
adaptions
249ed87
some changes
daveMueller 988cb9e
changes
1b7d207
tests + cleanup
daveMueller 8281be7
solution for deterministic cobertura reports
daveMueller d67bb46
added some documentation
90768d6
docs
daveMueller 69777e9
added troubleshoot documentation
daveMueller File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
src/coverlet.collector/ArtifactPostProcessor/CoverletCoveragePostProcessor.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// Copyright (c) Toni Solarin-Sodara | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using System.Xml; | ||
using Coverlet.Collector.Utilities; | ||
using Coverlet.Core; | ||
using Coverlet.Core.Abstractions; | ||
using Coverlet.Core.Reporters; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; | ||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; | ||
using Newtonsoft.Json; | ||
using System.Diagnostics; | ||
using Coverlet.Core.Helpers; | ||
|
||
namespace coverlet.collector.ArtifactPostProcessor | ||
{ | ||
public class CoverletCoveragePostProcessor : IDataCollectorAttachmentProcessor | ||
{ | ||
private CoverageResult _coverageResult; | ||
private ReportFormatParser _reportFormatParser; | ||
private IMessageLogger _logger; | ||
|
||
public bool SupportsIncrementalProcessing => true; | ||
|
||
public IEnumerable<Uri> GetExtensionUris() => new[] { new Uri(CoverletConstants.DefaultUri) }; | ||
|
||
public Task<ICollection<AttachmentSet>> ProcessAttachmentSetsAsync(XmlElement configurationElement, | ||
ICollection<AttachmentSet> attachments, IProgress<int> progressReporter, | ||
IMessageLogger logger, CancellationToken cancellationToken) | ||
{ | ||
_reportFormatParser ??= new ReportFormatParser(); | ||
_coverageResult ??= new CoverageResult(); | ||
_coverageResult.Modules ??= new Modules(); | ||
_logger = logger; | ||
|
||
string[] formats = _reportFormatParser.ParseReportFormats(configurationElement); | ||
bool deterministic = _reportFormatParser.ParseDeterministicReport(configurationElement); | ||
bool useSourceLink = _reportFormatParser.ParseUseSourceLink(configurationElement); | ||
bool reportMerging = _reportFormatParser.ParseReportMerging(configurationElement); | ||
|
||
AttachDebugger(); | ||
|
||
if (!reportMerging) return Task.FromResult(attachments); | ||
|
||
IList<IReporter> reporters = CreateReporters(formats).ToList(); | ||
|
||
if (attachments.Count > 1) | ||
{ | ||
_coverageResult.Parameters = new CoverageParameters() {DeterministicReport = deterministic, UseSourceLink = useSourceLink }; | ||
|
||
var fileAttachments = attachments.SelectMany(x => x.Attachments.Where(IsFileAttachment)).ToList(); | ||
string mergeFilePath = Path.GetDirectoryName(fileAttachments.First().Uri.LocalPath); | ||
|
||
MergeExistingJsonReports(attachments); | ||
|
||
RemoveObsoleteReports(fileAttachments); | ||
|
||
AttachmentSet mergedFileAttachment = WriteCoverageReports(reporters, mergeFilePath, _coverageResult); | ||
|
||
attachments = new List<AttachmentSet> { mergedFileAttachment }; | ||
} | ||
|
||
return Task.FromResult(attachments); | ||
} | ||
|
||
private static void RemoveObsoleteReports(List<UriDataAttachment> fileAttachments) | ||
{ | ||
fileAttachments.ForEach(x => | ||
{ | ||
string directory = Path.GetDirectoryName(x.Uri.LocalPath); | ||
if (! string.IsNullOrEmpty(directory) && Directory.Exists(directory)) | ||
Directory.Delete(directory, true); | ||
}); | ||
} | ||
|
||
private void MergeExistingJsonReports(IEnumerable<AttachmentSet> attachments) | ||
{ | ||
foreach (AttachmentSet attachmentSet in attachments) | ||
{ | ||
attachmentSet.Attachments.Where(IsFileWithJsonExt).ToList().ForEach(x => | ||
MergeWithCoverageResult(x.Uri.LocalPath, _coverageResult) | ||
); | ||
} | ||
} | ||
|
||
private AttachmentSet WriteCoverageReports(IEnumerable<IReporter> reporters, string directory, CoverageResult coverageResult) | ||
{ | ||
var attachment = new AttachmentSet(new Uri(CoverletConstants.DefaultUri), string.Empty); | ||
foreach (IReporter reporter in reporters) | ||
{ | ||
string report = GetCoverageReport(coverageResult, reporter); | ||
var file = new FileInfo(Path.Combine(directory, Path.ChangeExtension(CoverletConstants.DefaultFileName, reporter.Extension))); | ||
file.Directory?.Create(); | ||
File.WriteAllText(file.FullName, report); | ||
attachment.Attachments.Add(new UriDataAttachment(new Uri(file.FullName),string.Empty)); | ||
} | ||
return attachment; | ||
} | ||
|
||
private static bool IsFileWithJsonExt(UriDataAttachment x) | ||
{ | ||
return IsFileAttachment(x) && Path.GetExtension(x.Uri.AbsolutePath).Equals(".json"); | ||
} | ||
|
||
private static bool IsFileAttachment(UriDataAttachment x) | ||
{ | ||
return x.Uri.IsFile; | ||
} | ||
|
||
private void MergeWithCoverageResult(string filePath, CoverageResult coverageResult) | ||
{ | ||
string json = File.ReadAllText(filePath); | ||
coverageResult.Merge(JsonConvert.DeserializeObject<Modules>(json)); | ||
} | ||
|
||
private string GetCoverageReport(CoverageResult coverageResult, IReporter reporter) | ||
{ | ||
try | ||
{ | ||
// empty source root translator returns the original path for deterministic report | ||
return reporter.Report(coverageResult, new SourceRootTranslator()); | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw new CoverletDataCollectorException( | ||
$"{CoverletConstants.DataCollectorName}: Failed to get coverage report", ex); | ||
} | ||
} | ||
|
||
private void AttachDebugger() | ||
{ | ||
if (int.TryParse(Environment.GetEnvironmentVariable("COVERLET_DATACOLLECTOR_POSTPROCESSOR_DEBUG"), out int result) && result == 1) | ||
{ | ||
Debugger.Launch(); | ||
Debugger.Break(); | ||
} | ||
} | ||
|
||
private IEnumerable<IReporter> CreateReporters(IEnumerable<string> formats) | ||
{ | ||
IEnumerable<IReporter> reporters = formats.Select(format => | ||
{ | ||
var reporterFactory = new ReporterFactory(format); | ||
if (!reporterFactory.IsValidFormat()) | ||
{ | ||
_logger.SendMessage(TestMessageLevel.Warning, $"Invalid report format '{format}'"); | ||
return null; | ||
} | ||
return reporterFactory.CreateReporter(); | ||
}).Where(r => r != null); | ||
|
||
return reporters; | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -26,24 +26,32 @@ internal class CoverageManager | |||||||||||||||||||
public CoverageManager(CoverletSettings settings, TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, ICoverageWrapper coverageWrapper, | ||||||||||||||||||||
IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator, ICecilSymbolHelper cecilSymbolHelper) | ||||||||||||||||||||
: this(settings, | ||||||||||||||||||||
settings.ReportFormats.Select(format => | ||||||||||||||||||||
{ | ||||||||||||||||||||
var reporterFactory = new ReporterFactory(format); | ||||||||||||||||||||
if (!reporterFactory.IsValidFormat()) | ||||||||||||||||||||
{ | ||||||||||||||||||||
eqtTrace.Warning($"Invalid report format '{format}'"); | ||||||||||||||||||||
return null; | ||||||||||||||||||||
} | ||||||||||||||||||||
else | ||||||||||||||||||||
{ | ||||||||||||||||||||
return reporterFactory.CreateReporter(); | ||||||||||||||||||||
} | ||||||||||||||||||||
}).Where(r => r != null).ToArray(), | ||||||||||||||||||||
CreateReporters(settings, eqtTrace), | ||||||||||||||||||||
new CoverletLogger(eqtTrace, logger), | ||||||||||||||||||||
coverageWrapper, instrumentationHelper, fileSystem, sourceRootTranslator, cecilSymbolHelper) | ||||||||||||||||||||
{ | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
private static IReporter[] CreateReporters(CoverletSettings settings, TestPlatformEqtTrace eqtTrace) | ||||||||||||||||||||
{ | ||||||||||||||||||||
if (settings.ReportMerging && ! settings.ReportFormats.Contains("json")) | ||||||||||||||||||||
settings.ReportFormats = settings.ReportFormats.Append("json").ToArray(); | ||||||||||||||||||||
|
||||||||||||||||||||
return settings.ReportFormats.Select(format => | ||||||||||||||||||||
Comment on lines
+37
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mutating settings.ReportFormats in place may introduce unintended side effects; consider assigning the updated report formats to a new local variable to preserve immutability.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||
{ | ||||||||||||||||||||
var reporterFactory = new ReporterFactory(format); | ||||||||||||||||||||
if (!reporterFactory.IsValidFormat()) | ||||||||||||||||||||
{ | ||||||||||||||||||||
eqtTrace.Warning($"Invalid report format '{format}'"); | ||||||||||||||||||||
return null; | ||||||||||||||||||||
} | ||||||||||||||||||||
else | ||||||||||||||||||||
{ | ||||||||||||||||||||
return reporterFactory.CreateReporter(); | ||||||||||||||||||||
} | ||||||||||||||||||||
}).Where(r => r != null).ToArray(); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public CoverageManager(CoverletSettings settings, IReporter[] reporters, ILogger logger, ICoverageWrapper coverageWrapper, | ||||||||||||||||||||
IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator, ICecilSymbolHelper cecilSymbolHelper) | ||||||||||||||||||||
{ | ||||||||||||||||||||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Copyright (c) Toni Solarin-Sodara | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Xml; | ||
using System.Linq; | ||
|
||
namespace Coverlet.Collector.Utilities | ||
{ | ||
internal class ReportFormatParser | ||
{ | ||
internal string[] ParseReportFormats(XmlElement configurationElement) | ||
{ | ||
string[] formats = Array.Empty<string>(); | ||
if (configurationElement != null) | ||
{ | ||
XmlElement reportFormatElement = configurationElement[CoverletConstants.ReportFormatElementName]; | ||
formats = SplitElement(reportFormatElement); | ||
} | ||
|
||
return formats is null || formats.Length == 0 ? new[] { CoverletConstants.DefaultReportFormat } : formats; | ||
} | ||
|
||
private static string[] SplitElement(XmlElement element) | ||
{ | ||
return element?.InnerText?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(value => !string.IsNullOrWhiteSpace(value)).Select(value => value.Trim()).ToArray(); | ||
} | ||
|
||
internal bool ParseUseSourceLink(XmlElement configurationElement) | ||
{ | ||
XmlElement useSourceLinkElement = configurationElement[CoverletConstants.UseSourceLinkElementName]; | ||
bool.TryParse(useSourceLinkElement?.InnerText, out bool useSourceLink); | ||
return useSourceLink; | ||
} | ||
|
||
internal bool ParseDeterministicReport(XmlElement configurationElement) | ||
{ | ||
XmlElement deterministicReportElement = configurationElement[CoverletConstants.DeterministicReport]; | ||
bool.TryParse(deterministicReportElement?.InnerText, out bool deterministicReport); | ||
return deterministicReport; | ||
} | ||
|
||
internal bool ParseReportMerging(XmlElement configurationElement) | ||
{ | ||
XmlElement mergeWithElement = configurationElement[CoverletConstants.ReportMerging]; | ||
bool.TryParse(mergeWithElement?.InnerText, out bool mergeWith); | ||
return mergeWith; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding checks or logging to ensure that deleting the directory does not inadvertently remove non-report files; additional filtering criteria may improve safety.
Copilot uses AI. Check for mistakes.