Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Supported Formats:
* lcov
* opencover
* cobertura
* teamcity

The `--format` option can be specified multiple times to output multiple formats in a single run:

Expand All @@ -117,6 +118,28 @@ The above command will write the results to the supplied path, if no file extens
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --output "/custom/directory/" -f json -f lcov
```

#### TeamCity Output

Coverlet can output basic code coverage statistics using [TeamCity service messages](https://confluence.jetbrains.com/display/TCD18/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages).

```bash
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --output teamcity
```

The currently supported [TeamCity statistics](https://confluence.jetbrains.com/display/TCD18/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages) are:

| TeamCity Statistic Key | Description |
| :--- | :--- |
| CodeCoverageL | Line-level code coverage |
| CodeCoverageC | Class-level code coverage |
| CodeCoverageM | Method-level code coverage |
| CodeCoverageAbsLTotal | The total number of lines |
| CodeCoverageAbsLCovered | The number of covered lines |
| CodeCoverageAbsCTotal | The total number of classes |
| CodeCoverageAbsCCovered | The number of covered classes |
| CodeCoverageAbsMTotal | The total number of methods |
| CodeCoverageAbsMCovered | The number of covered methods |

#### Merging Results

With Coverlet you can combine the output of multiple coverage runs into a single result.
Expand Down Expand Up @@ -234,6 +257,7 @@ Supported Formats:
* lcov
* opencover
* cobertura
* teamcity

You can specify multiple output formats by separating them with a comma (`,`).

Expand Down
37 changes: 23 additions & 14 deletions src/coverlet.console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Diagnostics;
using System.IO;
using System.Text;

using ConsoleTables;
using Coverlet.Console.Logging;
using Coverlet.Core;
Expand Down Expand Up @@ -73,22 +72,32 @@ static int Main(string[] args)
if (reporter == null)
throw new Exception($"Specified output format '{format}' is not supported");

var filename = Path.GetFileName(dOutput);
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";

var report = Path.Combine(directory, filename);
logger.LogInformation($" Generating report '{report}'");
File.WriteAllText(report, reporter.Report(result));
if (reporter.UseConsoleOutput)
{
// Output to console
logger.LogInformation(" Outputting results to console");
logger.LogInformation(reporter.Report(result));
}
else
{
// Output to file
var filename = Path.GetFileName(dOutput);
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";

var report = Path.Combine(directory, filename);
logger.LogInformation($" Generating report '{report}'");
File.WriteAllText(report, reporter.Report(result));
}
}

var summary = new CoverageSummary();
var exceptionBuilder = new StringBuilder();
var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
var thresholdFailed = false;
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules).Percent * 100;
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules).Percent * 100;
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules).Percent * 100;
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules);
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules);

foreach (var _module in result.Modules)
{
Expand Down Expand Up @@ -122,9 +131,9 @@ static int Main(string[] args)

logger.LogInformation(string.Empty);
logger.LogInformation(coverageTable.ToStringAlternative());
logger.LogInformation($"Total Line: {overallLineCoverage}%");
logger.LogInformation($"Total Branch: {overallBranchCoverage}%");
logger.LogInformation($"Total Method: {overallMethodCoverage}%");
logger.LogInformation($"Total Line: {overallLineCoverage.Percent * 100}%");
logger.LogInformation($"Total Branch: {overallBranchCoverage.Percent * 100}%");
logger.LogInformation($"Total Method: {overallMethodCoverage.Percent * 100}%");

if (thresholdFailed)
throw new Exception(exceptionBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray()));
Expand Down
2 changes: 2 additions & 0 deletions src/coverlet.core/Reporters/CoberturaReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace Coverlet.Core.Reporters
{
public class CoberturaReporter : IReporter
{
public bool UseConsoleOutput => false;

public string Format => "cobertura";

public string Extension => "cobertura.xml";
Expand Down
1 change: 1 addition & 0 deletions src/coverlet.core/Reporters/IReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace Coverlet.Core.Reporters
{
public interface IReporter
{
bool UseConsoleOutput { get; }
string Format { get; }
string Extension { get; }
string Report(CoverageResult result);
Expand Down
2 changes: 2 additions & 0 deletions src/coverlet.core/Reporters/JsonReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace Coverlet.Core.Reporters
{
public class JsonReporter : IReporter
{
public bool UseConsoleOutput => false;

public string Format => "json";

public string Extension => "json";
Expand Down
2 changes: 2 additions & 0 deletions src/coverlet.core/Reporters/LcovReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ namespace Coverlet.Core.Reporters
{
public class LcovReporter : IReporter
{
public bool UseConsoleOutput => false;

public string Format => "lcov";

public string Extension => "info";
Expand Down
2 changes: 2 additions & 0 deletions src/coverlet.core/Reporters/OpenCoverReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Coverlet.Core.Reporters
{
public class OpenCoverReporter : IReporter
{
public bool UseConsoleOutput => false;

public string Format => "opencover";

public string Extension => "opencover.xml";
Expand Down
4 changes: 3 additions & 1 deletion src/coverlet.core/Reporters/ReporterFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Collections.Generic;
using coverlet.core.Reporters;

namespace Coverlet.Core.Reporters
{
Expand All @@ -14,7 +15,8 @@ public ReporterFactory(string format)
_format = format;
_reporters = new IReporter[] {
new JsonReporter(), new LcovReporter(),
new OpenCoverReporter(), new CoberturaReporter()
new OpenCoverReporter(), new CoberturaReporter(),
new TeamCityReporter()
};
}

Expand Down
34 changes: 34 additions & 0 deletions src/coverlet.core/Reporters/TeamCityReporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Coverlet.Core;
using Coverlet.Core.Reporters;
using System.Text;

namespace coverlet.core.Reporters
{
public class TeamCityReporter : IReporter
{
public bool UseConsoleOutput => true;

public string Format => "teamcity";

public string Extension => null;

public string Report(CoverageResult result)
{
// Calculate coverage
var summary = new CoverageSummary();
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules);
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules);

// Report coverage
var stringBuilder = new StringBuilder();
var teamCityServiceMessageWriter = new TeamCityServiceMessageWriter(s => stringBuilder.AppendLine(s));
teamCityServiceMessageWriter.OutputLineCoverage(overallLineCoverage);
teamCityServiceMessageWriter.OutputBranchCoverage(overallBranchCoverage);
teamCityServiceMessageWriter.OutputMethodCoverage(overallMethodCoverage);

// Return a placeholder
return stringBuilder.ToString();
}
}
}
68 changes: 68 additions & 0 deletions src/coverlet.core/TeamCityServiceMessageWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Coverlet.Core;
using System;

namespace coverlet.core
{
public class TeamCityServiceMessageWriter
{
private readonly Action<string> _writer;

public TeamCityServiceMessageWriter(Action<string> writer)
{
_writer = writer;
}

public void OutputLineCoverage(CoverageDetails coverageDetails)
{
// The total number of lines
OutputTeamCityServiceMessage("CodeCoverageL", coverageDetails.Total);

// The number of covered lines
OutputTeamCityServiceMessage("CodeCoverageAbsLCovered", coverageDetails.Covered);

// Line-level code coverage
OutputTeamCityServiceMessage("CodeCoverageAbsLTotal", coverageDetails.Percent * 100);
}

public void OutputBranchCoverage(CoverageDetails coverageDetails)
{
// The total number of branches
OutputTeamCityServiceMessage("CodeCoverageR", coverageDetails.Total);

// The number of covered branches
OutputTeamCityServiceMessage("CodeCoverageAbsRCovered", coverageDetails.Covered);

// Branch-level code coverage
OutputTeamCityServiceMessage("CodeCoverageAbsRTotal", coverageDetails.Percent * 100);
}

public void OutputClassCoverage(CoverageDetails coverageDetails)
{
// The total number of classes
OutputTeamCityServiceMessage("CodeCoverageC", coverageDetails.Total);

// The number of covered classes
OutputTeamCityServiceMessage("CodeCoverageAbsCCovered", coverageDetails.Covered);

// Class-level code coverage
OutputTeamCityServiceMessage("CodeCoverageAbsCTotal", coverageDetails.Percent * 100);
}

public void OutputMethodCoverage(CoverageDetails coverageDetails)
{
// The total number of methods
OutputTeamCityServiceMessage("CodeCoverageM", coverageDetails.Total);

// The number of covered methods
OutputTeamCityServiceMessage("CodeCoverageAbsMCovered", coverageDetails.Covered);

// Method-level code coverage
OutputTeamCityServiceMessage("CodeCoverageAbsMTotal", coverageDetails.Percent * 100);
}

private void OutputTeamCityServiceMessage(string key, object value)
{
_writer?.Invoke($"##teamcity[buildStatisticValue key='{key}' value='{value}']");
}
}
}
36 changes: 23 additions & 13 deletions src/coverlet.msbuild.tasks/CoverageResultTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,33 @@ public override bool Execute()
if (reporter == null)
throw new Exception($"Specified output format '{format}' is not supported");

var filename = Path.GetFileName(_output);
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";

var report = Path.Combine(directory, filename);
Console.WriteLine($" Generating report '{report}'");
File.WriteAllText(report, reporter.Report(result));
if (reporter.UseConsoleOutput)
{
// Output to console
Console.WriteLine(" Outputting results to console");
Console.WriteLine(reporter.Report(result));
}
else
{
// Output to file
var filename = Path.GetFileName(_output);
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";

var report = Path.Combine(directory, filename);
Console.WriteLine($" Generating report '{report}'");
File.WriteAllText(report, reporter.Report(result));
}
}

var thresholdFailed = false;
var thresholdTypes = _thresholdType.Split(',').Select(t => t.Trim());
var summary = new CoverageSummary();
var exceptionBuilder = new StringBuilder();
var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules).Percent * 100;
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules).Percent * 100;
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules).Percent * 100;
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules);
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules);

foreach (var module in result.Modules)
{
Expand Down Expand Up @@ -115,9 +125,9 @@ public override bool Execute()

Console.WriteLine();
Console.WriteLine(coverageTable.ToStringAlternative());
Console.WriteLine($"Total Line: {overallLineCoverage}%");
Console.WriteLine($"Total Branch: {overallBranchCoverage}%");
Console.WriteLine($"Total Method: {overallMethodCoverage}%");
Console.WriteLine($"Total Line: {overallLineCoverage.Percent * 100}%");
Console.WriteLine($"Total Branch: {overallBranchCoverage.Percent * 100}%");
Console.WriteLine($"Total Method: {overallMethodCoverage.Percent * 100}%");

if (thresholdFailed)
throw new Exception(exceptionBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray()));
Expand Down
Loading