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

generate HTML report implementation #4

Merged
merged 7 commits into from
Jun 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
<packageSources>
<add key="Nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<config>
<add key="globalPackagesFolder" value="packages" />
acesiddhu marked this conversation as resolved.
Show resolved Hide resolved
</config>
</configuration>
12 changes: 8 additions & 4 deletions src/CoveragePublisher.L0.Tests/ArgumentsProcessorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ namespace CoveragePublishe.L0.Tests
public class ArgumentsProcessorTests
{
private static StringWriter ConsoleWriter = new StringWriter();
private static string UsageText = @" --reportDirectory (Default: ) Path to report directory.
private static string UsageText = @"
--reportDirectory (Default: ) Path to report directory.

--help Display this help screen.
--sourceDirectory (Default: ) List of source directories separated by ';'.

--version Display version information.
--help Display this help screen.

value pos. 0 Required. Set of coverage files to be published.";
--version Display version information.

value pos. 0 Required. Set of coverage files to be published.
";

[ClassInitialize]
public static void ClassInitialize(TestContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Azure.Pipelines.CoveragePublisher.Model;
using Microsoft.Azure.Pipelines.CoveragePublisher.Parsers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
Expand All @@ -19,7 +20,7 @@ public class ReportGeneratorParserTests
public void WillGenerateCorrectFileCoverage(string[] coverageFiles, string result)
{
var parser = new ReportGeneratorParser();
var fileCoverages = parser.GetFileCoverageInfos(coverageFiles.ToList());
var fileCoverages = parser.GetFileCoverageInfos(new PublisherConfiguration() { CoverageFiles = coverageFiles });
var json = JsonConvert.SerializeObject(fileCoverages);

Assert.AreEqual(json, result);
Expand All @@ -33,7 +34,7 @@ public void WillGenerateCorrectFileCoverage(string[] coverageFiles, string resul
public void WillGenerateCorrectCoverageSummary(string[] coverageFiles, string result)
{
var parser = new ReportGeneratorParser();
var summary = parser.GetCoverageSummary(coverageFiles.ToList());
var summary = parser.GetCoverageSummary(new PublisherConfiguration() { CoverageFiles = coverageFiles });
var json = JsonConvert.SerializeObject(summary.CodeCoverageData);

Assert.AreEqual(json, result);
Expand All @@ -43,22 +44,57 @@ public void WillGenerateCorrectCoverageSummary(string[] coverageFiles, string re
public void WillReturnEmptyCoverageForNoInputFiles()
{
var parser = new ReportGeneratorParser();
var fileCoverage = parser.GetFileCoverageInfos(new List<string>());
var summary = parser.GetCoverageSummary(new List<string>());
var fileCoverage = parser.GetFileCoverageInfos(new PublisherConfiguration());
var summary = parser.GetCoverageSummary(new PublisherConfiguration());

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

[TestMethod]
public void WillReturnEmptyCoverageForNonExistingFile()
{
var parser = new ReportGeneratorParser();
var fileCoverage = parser.GetFileCoverageInfos(new List<string>() { "SampleCoverage/blabla.xml" });
var summary = parser.GetCoverageSummary(new List<string>() { "SampleCoverage/blabla.xml" });
var fileCoverage = parser.GetFileCoverageInfos(new PublisherConfiguration() { CoverageFiles = new string[] { "SampleCoverage/blabla.xml" } });
var summary = parser.GetCoverageSummary(new PublisherConfiguration() { CoverageFiles = new string[] { "SampleCoverage/blabla.xml" } });

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

[TestMethod]
[DataRow(new string[] { "SampleCoverage/Clover.xml" })]
[DataRow(new string[] { "SampleCoverage/Cobertura.xml" })]
[DataRow(new string[] { "SampleCoverage/Jacoco.xml" })]
[DataRow(new string[] { "SampleCoverage/Clover.xml", "SampleCoverage/Cobertura.xml", "SampleCoverage/Jacoco.xml" })]
public void WillGenerateHTMLReport(string[] xmlFiles)
{
var tempDir1 = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
var tempDir2 = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

Directory.CreateDirectory(tempDir1);
Directory.CreateDirectory(tempDir2);

var parser = new ReportGeneratorParser();

parser.GetFileCoverageInfos(new PublisherConfiguration()
{
CoverageFiles = xmlFiles,
ReportDirectory = tempDir1
});

parser.GetCoverageSummary(new PublisherConfiguration()
{
CoverageFiles = xmlFiles,
ReportDirectory = tempDir2
});

Assert.IsTrue(Directory.EnumerateFiles(tempDir1).Count() > 0);
Assert.IsTrue(Directory.EnumerateFiles(tempDir2).Count() > 0);

//cleanup
Directory.Delete(tempDir1, true);
Directory.Delete(tempDir2, true);
}
}
}
13 changes: 8 additions & 5 deletions src/CoveragePublisher/ArgumentsProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ namespace Microsoft.Azure.Pipelines.CoveragePublisher
{
internal class ArgumentsProcessor
{
public class Options : ICLIArgs
public class Options : PublisherConfiguration
{
[Value(0, Required = true, HelpText = "Set of coverage files to be published.")]
public IEnumerable<string> CoverageFiles { get; set; }
override public IEnumerable<string> CoverageFiles { get; set; }
karanjitsingh marked this conversation as resolved.
Show resolved Hide resolved

[Option("reportDirectory", Default = "", HelpText = "Path to report directory.")]
public string ReportDirectory { get; set; }
override public string ReportDirectory { get; set; }

[Option("sourceDirectory", Default = "", HelpText = "List of source directories separated by ';'.")]
override public string SourceDirectories { get; set; }
}


public ICLIArgs ProcessCommandLineArgs(string[] args)
public PublisherConfiguration ProcessCommandLineArgs(string[] args)
{
ICLIArgs cliArgs = null;
PublisherConfiguration cliArgs = null;

Parser.Default.ParseArguments<Options>(args)
.WithParsed<Options>(opts => {
Expand Down
3 changes: 2 additions & 1 deletion src/CoveragePublisher/CoveragePublisher.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Microsoft.Azure.Pipelines.CoveragePublisher</RootNamespace>
<AssemblyName>Microsoft.Azure.Pipelines.CoveragePublisher</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.5.0" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="16.151.0-preview" />
<PackageReference Include="Microsoft.TeamFoundation.PublishTestResults" Version="16.151.0-preview" />
<PackageReference Include="ReportGenerator.Core" Version="4.1.6" />
<PackageReference Include="ReportGenerator.Core" Version="4.1.10.2" />
</ItemGroup>

</Project>
23 changes: 0 additions & 23 deletions src/CoveragePublisher/Model/ICLIArgs.cs

This file was deleted.

16 changes: 8 additions & 8 deletions src/CoveragePublisher/Model/ICoverageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ namespace Microsoft.Azure.Pipelines.CoveragePublisher.Model
internal interface ICoverageParser
{
/// <summary>
/// Get coverage report, contains coverage information for individual files along with path to report directory.
/// Get coverage information for individual files.
/// </summary>
/// <param name="coverageFiles">List of xml coverage files.</param>
/// <returns></returns>
List<FileCoverageInfo> GetFileCoverageInfos(List<string> coverageFiles);
/// <param name="configuration"><see cref="PublisherConfiguration"/></param>
/// <returns>List of <see cref="FileCoverageInfo"/></returns>
List<FileCoverageInfo> GetFileCoverageInfos(PublisherConfiguration configuration);

/// <summary>
/// Get coverage summary, contains combined coverage summary data along with path to report directory.
/// Get coverage summary, contains combined coverage summary data.
/// </summary>
/// <param name="coverageFiles">List of xml coverage files.</param>
/// <returns></returns>
CoverageSummary GetCoverageSummary(List<string> coverageFiles);
/// <param name="configuration"><see cref="PublisherConfiguration"/></param>
/// <returns><see cref="CoverageSummary"/></returns>
CoverageSummary GetCoverageSummary(PublisherConfiguration configuration);
}
}
38 changes: 38 additions & 0 deletions src/CoveragePublisher/Model/PublisherConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;

namespace Microsoft.Azure.Pipelines.CoveragePublisher.Model
{
/// <summary>
/// Publisher configuration
/// </summary>
public class PublisherConfiguration
{
/// <summary>
/// List of coverage files.
/// </summary>
virtual public IEnumerable<string> CoverageFiles { get; set; }
karanjitsingh marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Path to coverage report directory. If set to null or empty, publisher will not create/publish an html report
/// </summary>
virtual public string ReportDirectory { get; set; }

/// <summary>
/// Semi-colon separated list of source directories. Required for creating html reports for jacoco.
/// </summary>
virtual public string SourceDirectories { get; set; }

/// <summary>
/// Gets the configuration for whether HTML reports should be generated or not.
/// </summary>
public bool GenerateHTMLReport {
get
{
return !string.IsNullOrEmpty(ReportDirectory);
}
}
}
}
67 changes: 52 additions & 15 deletions src/CoveragePublisher/Parsers/ReportGeneratorParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,33 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Azure.Pipelines.CoveragePublisher.Model;
using Palmmedia.ReportGenerator.Core;
using Palmmedia.ReportGenerator.Core.CodeAnalysis;
using Palmmedia.ReportGenerator.Core.Parser;
using Palmmedia.ReportGenerator.Core.Parser.Filtering;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;

namespace Microsoft.Azure.Pipelines.CoveragePublisher.Parsers
{
// Will use ReportGenerator to parse xml files and generate IList<FileCoverageInfo>
internal class ReportGeneratorParser: ICoverageParser
{
public List<FileCoverageInfo> GetFileCoverageInfos(List<string> coverageFiles)
public List<FileCoverageInfo> GetFileCoverageInfos(PublisherConfiguration config)
{
var parseResult = ParseCoverageFiles(coverageFiles);

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

foreach (var assembly in parseResult.Assemblies)
if (config.CoverageFiles == null)
{
return fileCoverages;
}

var parserResult = ParseCoverageFiles(new List<string>(config.CoverageFiles));


foreach (var assembly in parserResult.Assemblies)
{
foreach (var @class in assembly.Classes)
{
Expand All @@ -41,18 +51,26 @@ public List<FileCoverageInfo> GetFileCoverageInfos(List<string> coverageFiles)
}
}

this.CreateHTMLReportFromParserResult(parserResult, config, config.SourceDirectories);

return fileCoverages;
}

public CoverageSummary GetCoverageSummary(List<string> coverageFiles)
public CoverageSummary GetCoverageSummary(PublisherConfiguration config)
{
var parseResult = ParseCoverageFiles(coverageFiles);
var summary = new CoverageSummary();

if(config.CoverageFiles == null)
{
return summary;
}

var parserResult = ParseCoverageFiles(new List<string>(config.CoverageFiles));

int totalLines = 0;
int coveredLines = 0;

foreach (var assembly in parseResult.Assemblies)
foreach (var assembly in parserResult.Assemblies)
{
foreach (var @class in assembly.Classes)
{
Expand All @@ -66,6 +84,8 @@ public CoverageSummary GetCoverageSummary(List<string> coverageFiles)

summary.AddCoverageStatistics("line", totalLines, coveredLines, CoverageSummary.Priority.Line);

this.CreateHTMLReportFromParserResult(parserResult, config, config.SourceDirectories);

return summary;
}

Expand All @@ -79,17 +99,34 @@ private ParserResult ParseCoverageFiles(List<string> coverageFiles)
return parser.ParseFiles(collection);
}

private string CreateHTMLReport(ParserResult result)
private bool CreateHTMLReportFromParserResult(ParserResult parserResult, PublisherConfiguration config, string sourceDirectories)
{
/* HTML renderer and SummaryResult constructor isn't exposed yet in ReportGenerator.Core
var builder = new HtmlReportBuilder();
if (config.GenerateHTMLReport && Directory.Exists(config.ReportDirectory))
{
try
{
var reportGeneratorConfig = new ReportConfigurationBuilder().Create(new Dictionary<string, string>() {
{ "targetdir", config.ReportDirectory },
{ "sourcedirs", string.IsNullOrEmpty(sourceDirectories) ? "" : sourceDirectories },
{ "reporttypes", "HtmlInline_AzurePipelines" }
});
karanjitsingh marked this conversation as resolved.
Show resolved Hide resolved

var summaryResult = new SummaryResult();
builder.CreateSummaryReport(summaryResult);

var rendered = new HTMLRenderer()
*/
return "";
var generator = new Generator();

generator.GenerateReport(reportGeneratorConfig, new Settings(), new RiskHotspotsAnalysisThresholds(), parserResult);
}
catch(Exception e)
{
//TODO: log exception
karanjitsingh marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

return true;

}

return false;
}
}
}