Skip to content
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

Updating merge api, #3053

Merged
10 commits merged into from
Sep 22, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,19 @@ public class CodeCoverageDataAttachmentsHandler : IDataCollectorAttachmentProces
{
private const string CoverageUri = "datacollector://microsoft/CodeCoverage/2.0";
private const string CoverageFileExtension = ".coverage";
private const string XmlFileExtension = ".xml";
private const string CoverageFriendlyName = "Code Coverage";

private const string CodeCoverageIOAssemblyName = "Microsoft.VisualStudio.Coverage.IO";
private const string CoverageFileUtilityTypeName = "CoverageFileUtility";
private const string MergeMethodName = "MergeCoverageFilesAsync";
private const string WriteMethodName = "WriteCoverageFile";
private const string MergeMethodName = "MergeCoverageReportsAsync";
private const string CoverageMergeOperationName = "CoverageMergeOperation";

private static readonly Uri CodeCoverageDataCollectorUri = new Uri(CoverageUri);
private static Assembly CodeCoverageAssembly;
private static object ClassInstance;
private static MethodInfo MergeMethodInfo;
private static Array MergeOperationEnumValues;

public bool SupportsIncrementalProcessing => true;

Expand All @@ -39,50 +44,50 @@ public IEnumerable<Uri> GetExtensionUris()

public async Task<ICollection<AttachmentSet>> ProcessAttachmentSetsAsync(ICollection<AttachmentSet> attachments, IProgress<int> progressReporter, IMessageLogger logger, CancellationToken cancellationToken)
{
if (attachments != null && attachments.Any())
{
var coverageReportFilePaths = new List<string>();
var coverageOtherFilePaths = new List<string>();
if ((attachments?.Any()) != true)
return new Collection<AttachmentSet>();

var coverageReportFilePaths = new List<string>();
var coverageOtherFilePaths = new List<string>();

foreach (var attachmentSet in attachments)
foreach (var attachmentSet in attachments)
{
foreach (var attachment in attachmentSet.Attachments)
{
foreach (var attachment in attachmentSet.Attachments)
if (attachment.Uri.LocalPath.EndsWith(CoverageFileExtension, StringComparison.OrdinalIgnoreCase) ||
attachment.Uri.LocalPath.EndsWith(XmlFileExtension, StringComparison.OrdinalIgnoreCase))
{
if (attachment.Uri.LocalPath.EndsWith(CoverageFileExtension, StringComparison.OrdinalIgnoreCase))
{
coverageReportFilePaths.Add(attachment.Uri.LocalPath);
}
else
{
coverageOtherFilePaths.Add(attachment.Uri.LocalPath);
}
coverageReportFilePaths.Add(attachment.Uri.LocalPath);
}
else
{
coverageOtherFilePaths.Add(attachment.Uri.LocalPath);
}
}
}

if (coverageReportFilePaths.Count > 1)
{
var mergedCoverageReportFilePath = await this.MergeCodeCoverageFilesAsync(coverageReportFilePaths, progressReporter, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(mergedCoverageReportFilePath))
{
var resultAttachmentSet = new AttachmentSet(CodeCoverageDataCollectorUri, CoverageFriendlyName);
resultAttachmentSet.Attachments.Add(UriDataAttachment.CreateFrom(mergedCoverageReportFilePath, CoverageFriendlyName));
if (coverageReportFilePaths.Count > 1)
{
var mergedCoverageReports = await this.MergeCodeCoverageFilesAsync(coverageReportFilePaths, progressReporter, cancellationToken).ConfigureAwait(false);
var resultAttachmentSet = new AttachmentSet(CodeCoverageDataCollectorUri, CoverageFriendlyName);

foreach (var coverageOtherFilePath in coverageOtherFilePaths)
{
resultAttachmentSet.Attachments.Add(UriDataAttachment.CreateFrom(coverageOtherFilePath, string.Empty));
}
foreach (var coverageReport in mergedCoverageReports)
{
resultAttachmentSet.Attachments.Add(UriDataAttachment.CreateFrom(coverageReport, CoverageFriendlyName));
}

return new Collection<AttachmentSet> { resultAttachmentSet };
}
foreach (var coverageOtherFilePath in coverageOtherFilePaths)
{
resultAttachmentSet.Attachments.Add(UriDataAttachment.CreateFrom(coverageOtherFilePath, string.Empty));
}

return attachments;
return new Collection<AttachmentSet> { resultAttachmentSet };
}

return new Collection<AttachmentSet>();
return attachments;
}

private async Task<string> MergeCodeCoverageFilesAsync(IList<string> files, IProgress<int> progressReporter, CancellationToken cancellationToken)
private async Task<IList<string>> MergeCodeCoverageFilesAsync(IList<string> files, IProgress<int> progressReporter, CancellationToken cancellationToken)
{
try
{
Expand Down Expand Up @@ -110,28 +115,23 @@ private async Task<string> MergeCodeCoverageFilesAsync(IList<string> files, IPro
return null;
}

private async Task<string> MergeCodeCoverageFilesAsync(IList<string> files, CancellationToken cancellationToken)
private async Task<IList<string>> MergeCodeCoverageFilesAsync(IList<string> files, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

var assemblyPath = Path.Combine(Path.GetDirectoryName(typeof(CodeCoverageDataAttachmentsHandler).GetTypeInfo().Assembly.GetAssemblyLocation()), CodeCoverageIOAssemblyName + ".dll");

// Get assembly, type and methods
Assembly assembly = new PlatformAssemblyLoadContext().LoadAssemblyFromPath(assemblyPath);
var classType = assembly.GetType($"{CodeCoverageIOAssemblyName}.{CoverageFileUtilityTypeName}");
var classInstance = Activator.CreateInstance(classType);
var mergeMethodInfo = classType?.GetMethod(MergeMethodName, new[] { typeof(IList<string>), typeof(CancellationToken) });
var writeMethodInfo = classType?.GetMethod(WriteMethodName);

// Invoke methods
var task = (Task)mergeMethodInfo.Invoke(classInstance, new object[] { files, cancellationToken });
LoadCodeCoverageAssembly();
var task = (Task)MergeMethodInfo.Invoke(ClassInstance, new object[] { files[0], files, MergeOperationEnumValues.GetValue(0), true, cancellationToken });
await task.ConfigureAwait(false);
var coverageData = task.GetType().GetProperty("Result").GetValue(task, null);
writeMethodInfo.Invoke(classInstance, new object[] { files[0], coverageData });
var mergedResults = coverageData as IList<string>;

// Delete original files and keep merged file only
foreach (var file in files.Skip(1))
foreach (var file in files)
{
if (mergedResults.Contains(file))
continue;

try
{
File.Delete(file);
Expand All @@ -142,7 +142,24 @@ private async Task<string> MergeCodeCoverageFilesAsync(IList<string> files, Canc
}
}

return files[0];
return mergedResults;
}

private void LoadCodeCoverageAssembly()
{
if (CodeCoverageAssembly != null)
return;

var assemblyPath = Path.Combine(Path.GetDirectoryName(typeof(CodeCoverageDataAttachmentsHandler).GetTypeInfo().Assembly.GetAssemblyLocation()), CodeCoverageIOAssemblyName + ".dll");
CodeCoverageAssembly = new PlatformAssemblyLoadContext().LoadAssemblyFromPath(assemblyPath);

var classType = CodeCoverageAssembly.GetType($"{CodeCoverageIOAssemblyName}.{CoverageFileUtilityTypeName}");
ClassInstance = Activator.CreateInstance(classType);

var types = CodeCoverageAssembly.GetTypes();
var mergeOperationEnum = Array.Find(types, d => d.Name == CoverageMergeOperationName);
MergeOperationEnumValues = Enum.GetValues(mergeOperationEnum);
MergeMethodInfo = classType?.GetMethod(MergeMethodName, new[] { typeof(string), typeof(IList<string>), mergeOperationEnum, typeof(bool), typeof(CancellationToken) });
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ protected string GetCodeCoverageExePath()

protected XmlNode GetModuleNode(XmlNode node, string name)
{
return this.GetNode(node, "module", name);
var moduleNode = this.GetNode(node, "module", name);

if (moduleNode == null)
{
moduleNode = this.GetNode(node, "package", name);
}

return moduleNode;
}

protected XmlNode GetNode(XmlNode node, string type, string name)
Expand All @@ -46,6 +53,14 @@ protected XmlNode GetNode(XmlNode node, string type, string name)

protected XmlDocument GetXmlCoverage(string coverageResult)
{
XmlDocument coverage = new XmlDocument();

if (coverageResult.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{
coverage.Load(coverageResult);
return coverage;
}

var codeCoverageExe = this.GetCodeCoverageExePath();
var output = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".xml");

Expand All @@ -69,14 +84,15 @@ protected XmlDocument GetXmlCoverage(string coverageResult)

Assert.IsTrue(0 == analyze.ExitCode, $"Code Coverage analyze failed: {analysisOutput}");

XmlDocument coverage = new XmlDocument();
coverage.Load(output);
return coverage;
}

protected void AssertCoverage(XmlNode node, double expectedCoverage)
{
var coverage = double.Parse(node.Attributes["block_coverage"].Value);
var coverage = node.Attributes["block_coverage"] != null
? double.Parse(node.Attributes["block_coverage"].Value)
: double.Parse(node.Attributes["line-rate"].Value) * 100;
Console.WriteLine($"Checking coverage for {node.Name} {node.Attributes["name"].Value}. Expected at least: {expectedCoverage}. Result: {coverage}");
Assert.IsTrue(coverage > expectedCoverage, $"Coverage check failed for {node.Name} {node.Attributes["name"].Value}. Expected at least: {expectedCoverage}. Found: {coverage}");
}
Expand Down
Loading