Skip to content

Commit

Permalink
Adding ability to support all different code coverage formats with V2 (
Browse files Browse the repository at this point in the history
…#68)

* Adding ability to support all different code coverage formats with V2

* Adding L0 test to ReportGeneratorTool

* Removing the CodeCoverage.IO old package

* Adding nuget restore

* removing nuget restore

* adding nuget config

* adding nuget config and

* adding nuget config and restore

* adding nuget config and restore remove

* Adding dotnet restore

* Adding dotnet restore 2

* Adding dotnet restore 3

* Adding dotnet restore 4

* Adding dotnet restore 5

* Adding dotnet restore 6

* Adding dotnet restore 7

* Adding dotnet restore 8

* Adding dotnet restore 9

* Adding dotnet restore 10

* Adding dotnet restore 11

* Adding dotnet restore 12

* Adding dotnet restore 13

* Adding dotnet restore 14

* Adding dotnet restore 15

* Adding dotnet restore 16

* Updating L0 tests cases

* Fixing pipeline error

* Adding nuget config custom file

* Adding nuget authenticate

* Adding nuget authenticate 1

* Adding nuget authenticate 2

* Final updates

* Final updates 1

* Final updates 2

* Adding .coverage sample file

* Updating nuget config file

* Updating nuget config file2

* Addressing PR comments

* Addressing PR comments and creating object

* Few refactoring code

* Nit change
  • Loading branch information
vinayakmsft authored Dec 14, 2023
1 parent 70cdde3 commit ccf072c
Show file tree
Hide file tree
Showing 14 changed files with 238 additions and 44 deletions.
8 changes: 7 additions & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ steps:
- task: NuGetToolInstaller@1

- task: NuGetAuthenticate@1
displayName: 'NuGet Authenticate'


- task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
restoreSolution: '$(solution)'
feedsToUse: config
nugetConfigPath: nuget.config

- task: UseDotNet@2
displayName: 'Use .NET Core sdk 7.0.x'
Expand Down
5 changes: 5 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@
<config>
<add key="globalPackagesFolder" value="packages" />
</config>
<packageSources>
<clear />
<add key="Nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="test-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/test-tools/nuget/v3/index.json" />
</packageSources>
</configuration>
44 changes: 3 additions & 41 deletions src/CoveragePublisher.Tests/CoverageProcessorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,58 +68,20 @@ public void WillNotPublishFileCoverageIfCountIsZero()
}

[TestMethod]
public void WillCatchAndReportExceptions()
public void WillNotPublishFileCoverageIfCountIsZeroForDotCoverage()
{
var logger = new TestLogger();
TraceLogger.Initialize(logger);

var token = new CancellationToken();
var processor = new CoverageProcessor(_mockPublisher.Object, _mockTelemetryDataCollector.Object);
var coverage = new List<FileCoverageInfo>();

_mockPublisher.Setup(x => x.IsFileCoverageJsonSupported()).Returns(true);
_mockParser.Setup(x => x.GetFileCoverageInfos()).Throws(new ParsingException("message", new Exception("error")));

processor.ParseAndPublishCoverage(_config, token, _mockParser.Object).Wait();

Assert.IsTrue(logger.Log.Contains("error: message System.Exception: error"));

logger.Log = "";

_mockParser.Setup(x => x.GetFileCoverageInfos()).Throws(new Exception("error"));
_mockParser.Setup(x => x.GetFileCoverageInfos(token)).Returns(coverage);

processor.ParseAndPublishCoverage(_config, token, _mockParser.Object).Wait();

Assert.IsTrue(logger.Log.Contains("error: An error occured while publishing coverage files. System.Exception: error"));
}

[TestMethod]
public void ParseAndPublishCoverageWillPublishFileAndCodeCoverageSummary()
{
var token = new CancellationToken();
var processor = new CoverageProcessor(_mockPublisher.Object, _mockTelemetryDataCollector.Object);
var coverage = new List<FileCoverageInfo>();
coverage.Add(new FileCoverageInfo());

var summary = new CoverageSummary();

summary.AddCoverageStatistics("", 3, 3, CoverageSummary.Priority.Class);

_mockPublisher.Setup(x => x.IsFileCoverageJsonSupported()).Returns(false);
_mockParser.Setup(x => x.GetCoverageSummary()).Returns(summary);

_mockPublisher.Setup(x => x.IsFileCoverageJsonSupported()).Returns(true);
_mockParser.Setup(x => x.GetFileCoverageInfos()).Returns(coverage);

processor.ParseAndPublishCoverage(_config, token, _mockParser.Object).Wait();

_mockPublisher.Verify(x => x.PublishCoverageSummary(
It.Is<CoverageSummary>(a => a == summary),
It.Is<CancellationToken>(b => b == token)));

_mockPublisher.Verify(x => x.PublishFileCoverage(
It.Is<List<FileCoverageInfo>>(a => a == coverage),
It.Is<CancellationToken>(b => b == token)));
It.Is<CancellationToken>(b => b == token)), Times.Never);
}

[TestMethod]
Expand Down
6 changes: 6 additions & 0 deletions src/CoveragePublisher.Tests/CoveragePublisher.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
<None Update="SampleCoverage\JaCoCo.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="SampleCoverage\DotCoverage.coverage">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="SampleCoverage\CoverageXFile.covx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.Azure.Pipelines.CoveragePublisher;
using Microsoft.Azure.Pipelines.CoveragePublisher.Model;
using Microsoft.Azure.Pipelines.CoveragePublisher.Parsers;
Expand Down Expand Up @@ -84,6 +85,19 @@ public void WillReturnEmptyCoverageForNoInputFiles()
".Trim());
}

[TestMethod]
public void WillReturnEmptyCoverageForNoInputFilesAllFormats()
{
var parser = new ReportGeneratorTool(new PublisherConfiguration());
var token = new CancellationTokenSource();
var fileCoverage = parser.GetFileCoverageInfos(token.Token);
var summary = parser.GetCoverageSummary();

Assert.AreEqual(fileCoverage.Count, 0);
Assert.AreEqual(summary.CodeCoverageData.CoverageStats.Count, 0);

}

[TestMethod]
public void WillReturnEmptyCoverageForNonExistingFile()
{
Expand All @@ -101,6 +115,44 @@ public void WillReturnEmptyCoverageForNonExistingFile()
".Trim());
}

[TestMethod]
public void WillReturnCoverageStatsForDotCoverageFile()
{
var parser = new ReportGeneratorTool(new PublisherConfiguration() { CoverageFiles = new string[] { "SampleCoverage/DotCoverage.coverage" } });
var token = new CancellationTokenSource();
var fileCoverage = parser.GetFileCoverageInfos(token.Token);
var summary = parser.GetCoverageSummary();

Assert.AreEqual(fileCoverage.Count, 10);
Assert.AreEqual(summary.CodeCoverageData.CoverageStats[0].Total, 922);
Assert.AreEqual(summary.CodeCoverageData.CoverageStats[0].Covered, 786);

}

[TestMethod]
public void WillReturnCoverageStatsForCoverageXFile()
{
var parser = new ReportGeneratorTool(new PublisherConfiguration() { CoverageFiles = new string[] { "SampleCoverage/CoverageXFile.covx" } });
var token = new CancellationTokenSource();
var fileCoverage = parser.GetFileCoverageInfos(token.Token);
var summary = parser.GetCoverageSummary();

Assert.AreEqual(fileCoverage.Count, 400);
Assert.AreEqual(summary.CodeCoverageData.CoverageStats[0].Total, 23070);
Assert.AreEqual(summary.CodeCoverageData.CoverageStats[0].Covered , 8785);
}

[TestMethod]
public void WillReturnCoverageStatsForNotCoverageXFile()
{
var parser = new ReportGeneratorTool(new PublisherConfiguration() { CoverageFiles = new string[] { "SampleCoverage/sampleCoverage.coverage" } });
var token = new CancellationTokenSource();
var fileCoverage = parser.GetFileCoverageInfos();
var summary = parser.GetCoverageSummary();

Assert.AreEqual(fileCoverage.Count, 0);
}

[TestMethod]
[DataRow(new string[] { "SampleCoverage/Clover.xml" })]
[DataRow(new string[] { "SampleCoverage/Cobertura.xml" })]
Expand Down
Binary file not shown.
Binary file not shown.
8 changes: 7 additions & 1 deletion src/CoveragePublisher/CoverageProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Pipelines.CoveragePublisher.Model;
using Microsoft.Azure.Pipelines.CoveragePublisher.Parsers;
using Microsoft.Azure.Pipelines.CoveragePublisher.Publishers.DefaultPublisher;
using Microsoft.Azure.Pipelines.CoveragePublisher.Utils;
using Microsoft.VisualStudio.Services.WebApi;

Expand Down Expand Up @@ -45,7 +47,11 @@ public async Task ParseAndPublishCoverage(PublisherConfiguration config, Cancell

if (supportsFileCoverageJson)
{
var fileCoverage = parser.GetFileCoverageInfos();
bool hasDotCoverageFiles = config.CoverageFiles.Any(file => Path.GetExtension(file) == Constants.CoverageFormats.CoverageDotFileFormat);
bool hasCoverageXFiles = config.CoverageFiles.Any(file => Path.GetExtension(file) == Constants.CoverageFormats.CoverageXFileExtension);
bool hasCoverageBFiles = config.CoverageFiles.Any(file => Path.GetExtension(file) == Constants.CoverageFormats.CoverageBFileExtension);

var fileCoverage = (hasDotCoverageFiles || hasCoverageXFiles || hasCoverageBFiles) ? parser.GetFileCoverageInfos(token) : parser.GetFileCoverageInfos();

var summary = parser.GetCoverageSummary();

Expand Down
2 changes: 2 additions & 0 deletions src/CoveragePublisher/CoveragePublisher.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
<PackageReference Include="Microsoft.CodeCoverage.Core" version="17.9.4-beta.23608.1" />
<PackageReference Include="Serilog" />
</ItemGroup>

<ItemGroup>
Expand Down
9 changes: 8 additions & 1 deletion src/CoveragePublisher/Model/ICoverageParserTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Threading;

namespace Microsoft.Azure.Pipelines.CoveragePublisher.Model
{
Expand All @@ -15,7 +16,13 @@ public interface ICoverageParserTool
/// </summary>
/// <returns>List of <see cref="FileCoverageInfo"/></returns>
List<FileCoverageInfo> GetFileCoverageInfos();


/// <summary>
/// Get coverage information for individual files. Specifically used for .coverage/.covx scenarios
/// </summary>
/// <returns>List of <see cref="FileCoverageInfo"/></returns>
List<FileCoverageInfo> GetFileCoverageInfos(CancellationToken token);

/// <summary>
/// Get coverage summary, contains combined coverage summary data.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.CodeCoverage.Core;
using Microsoft.CodeCoverage.IO.Coverage;

namespace Microsoft.Azure.Pipelines.CoveragePublisher.Parsers.CoverageParserTools
{
internal class PublisherCoverageFileConfiguration : ICoverageFileConfiguration
{
public PublisherCoverageFileConfiguration(
bool ReadModules,
bool ReadSkippedModules,
bool ReadSkippedFunctions,
bool ReadSnapshotsData,
bool FixCoverageBuffersMismatch,
bool GenerateCoverageBufferFiles
) {
this.ReadModules = ReadModules;
this.ReadSkippedModules = ReadSkippedModules;
this.ReadSkippedFunctions = ReadSkippedFunctions;
this.ReadSnapshotsData = ReadSnapshotsData;
this.FixCoverageBuffersMismatch = FixCoverageBuffersMismatch;
this.GenerateCoverageBufferFiles = GenerateCoverageBufferFiles;
}
public bool ReadModules { get; set; }

public bool ReadSkippedModules { get; set; }

public bool ReadSkippedFunctions { get; set; }

public bool ReadSnapshotsData { get; set; }

public bool GenerateCoverageBufferFiles { get; set; }

public bool FixCoverageBuffersMismatch { get; set; }

public int MaxDegreeOfParallelism { get; set; } = 10;

public bool SkipInvalidData { get; set; }

public CoverageMergeOperation MergeOperation { get; set; }

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.Azure.Pipelines.CoveragePublisher.Model;
using Microsoft.TeamFoundation.TestManagement.WebApi;
using Microsoft.CodeCoverage.Core;
using Palmmedia.ReportGenerator.Core;
using Palmmedia.ReportGenerator.Core.CodeAnalysis;
using Palmmedia.ReportGenerator.Core.Parser;
Expand All @@ -12,6 +13,16 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Serilog;
using Microsoft.Azure.Pipelines.CoveragePublisher.Publishers.DefaultPublisher;
using Microsoft.Azure.Pipelines.CoveragePublisher.Parsers.CoverageParserTools;
using CoverageStatus = Microsoft.TeamFoundation.TestManagement.WebApi.CoverageStatus;
using Microsoft.CodeCoverage.IO.Coverage;
using Microsoft.CodeCoverage.IO;

namespace Microsoft.Azure.Pipelines.CoveragePublisher.Parsers
{
Expand Down Expand Up @@ -70,6 +81,81 @@ public List<FileCoverageInfo> GetFileCoverageInfos()
return fileCoverages;
}

public List<FileCoverageInfo> GetFileCoverageInfos(CancellationToken token)
{
TraceLogger.Debug("ReportGeneratorTool.GetFileCoverageInfos: Generating file coverage info from coverage files.");

List<FileCoverageInfo> fileCoverages = new List<FileCoverageInfo>();

if (Configuration.CoverageFiles == null)
{
return fileCoverages;
}

var transformedXml = TransformCoverageFilesToXml(Configuration.CoverageFiles, token);

_parserResult = ParseCoverageFiles(transformedXml.Result);

foreach (var assembly in _parserResult.Assemblies)
{
foreach (var @class in assembly.Classes)
{
foreach (var file in @class.Files)
{
FileCoverageInfo resultFileCoverageInfo = new FileCoverageInfo { FilePath = file.Path, LineCoverageStatus = new Dictionary<uint, CoverageStatus>() };
int lineNumber = 0;

foreach (var line in file.LineCoverage)
{
if (line != -1 && lineNumber != 0)
{
resultFileCoverageInfo.LineCoverageStatus.Add((uint)lineNumber, line == 0 ? CoverageStatus.NotCovered : CoverageStatus.Covered);
}
++lineNumber;
}

fileCoverages.Add(resultFileCoverageInfo);
}
}
}

return fileCoverages;
}


private async Task<List<string>> TransformCoverageFilesToXml(IList<string> inputCoverageFiles, CancellationToken cancellationToken)
{

PublisherCoverageFileConfiguration GenericCoverageFileConfiguration = new PublisherCoverageFileConfiguration(ReadModules: true,
ReadSkippedModules: true,
ReadSkippedFunctions: true,
ReadSnapshotsData:true,
FixCoverageBuffersMismatch: true,
GenerateCoverageBufferFiles: true);


var utility = new CoverageFileUtilityV2(GenericCoverageFileConfiguration);

var transformedXmls = new List<string>();
foreach (var coverageFile in inputCoverageFiles)
{
if ((coverageFile.EndsWith(Constants.CoverageFormats.CoverageDotFileFormat) ||
coverageFile.EndsWith(Constants.CoverageFormats.CoverageXFileExtension) ||
coverageFile.EndsWith(Constants.CoverageFormats.CoverageBFileExtension)
))
{
string transformedXml = Path.ChangeExtension(coverageFile, ".xml");
await utility.ToXmlFileAsync(
path: coverageFile,
outputPath: transformedXml,
cancellationToken: cancellationToken);

transformedXmls.Add(transformedXml);
}
}
return transformedXmls;
}

public CoverageSummary GetCoverageSummary()
{
TraceLogger.Debug("ReportGeneratorTool.GetCoverageSummary: Generating coverage summary for the coverage files.");
Expand Down
Loading

0 comments on commit ccf072c

Please sign in to comment.