Skip to content

Commit

Permalink
Merge pull request #227 from Reptarsrage/226_teamcity_support
Browse files Browse the repository at this point in the history
226 teamcity support
  • Loading branch information
tonerdo committed Nov 13, 2018
2 parents 4c3471d + fbd01ad commit 7ede9d7
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 29 deletions.
24 changes: 24 additions & 0 deletions README.md
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
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.OutputType == ReporterOutputType.Console)
{
// 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
Expand Up @@ -10,6 +10,8 @@ namespace Coverlet.Core.Reporters
{
public class CoberturaReporter : IReporter
{
public ReporterOutputType OutputType => ReporterOutputType.File;

public string Format => "cobertura";

public string Extension => "cobertura.xml";
Expand Down
7 changes: 7 additions & 0 deletions src/coverlet.core/Reporters/IReporter.cs
Expand Up @@ -2,8 +2,15 @@ namespace Coverlet.Core.Reporters
{
public interface IReporter
{
ReporterOutputType OutputType { get; }
string Format { get; }
string Extension { get; }
string Report(CoverageResult result);
}

public enum ReporterOutputType
{
File,
Console,
}
}
2 changes: 2 additions & 0 deletions src/coverlet.core/Reporters/JsonReporter.cs
Expand Up @@ -4,6 +4,8 @@ namespace Coverlet.Core.Reporters
{
public class JsonReporter : IReporter
{
public ReporterOutputType OutputType => ReporterOutputType.File;

public string Format => "json";

public string Extension => "json";
Expand Down
2 changes: 2 additions & 0 deletions src/coverlet.core/Reporters/LcovReporter.cs
Expand Up @@ -6,6 +6,8 @@ namespace Coverlet.Core.Reporters
{
public class LcovReporter : IReporter
{
public ReporterOutputType OutputType => ReporterOutputType.File;

public string Format => "lcov";

public string Extension => "info";
Expand Down
2 changes: 2 additions & 0 deletions src/coverlet.core/Reporters/OpenCoverReporter.cs
Expand Up @@ -9,6 +9,8 @@ namespace Coverlet.Core.Reporters
{
public class OpenCoverReporter : IReporter
{
public ReporterOutputType OutputType => ReporterOutputType.File;

public string Format => "opencover";

public string Extension => "opencover.xml";
Expand Down
4 changes: 3 additions & 1 deletion src/coverlet.core/Reporters/ReporterFactory.cs
@@ -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
74 changes: 74 additions & 0 deletions src/coverlet.core/Reporters/TeamCityReporter.cs
@@ -0,0 +1,74 @@
using Coverlet.Core;
using Coverlet.Core.Reporters;
using System.Text;

namespace coverlet.core.Reporters
{
public class TeamCityReporter : IReporter
{
public ReporterOutputType OutputType => ReporterOutputType.Console;

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();
OutputLineCoverage(overallLineCoverage, stringBuilder);
OutputBranchCoverage(overallBranchCoverage, stringBuilder);
OutputMethodCoverage(overallMethodCoverage, stringBuilder);

// Return a placeholder
return stringBuilder.ToString();
}

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

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

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

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

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

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

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

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

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

private void OutputTeamCityServiceMessage(string key, object value, StringBuilder builder)
{
builder.AppendLine($"##teamcity[buildStatisticValue key='{key}' value='{value}']");
}
}
}
36 changes: 23 additions & 13 deletions src/coverlet.msbuild.tasks/CoverageResultTask.cs
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.OutputType == ReporterOutputType.Console)
{
// 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
3 changes: 2 additions & 1 deletion test/coverlet.core.tests/Reporters/ReporterFactoryTests.cs
@@ -1,4 +1,4 @@
using System;
using coverlet.core.Reporters;
using Xunit;

namespace Coverlet.Core.Reporters.Tests
Expand All @@ -12,6 +12,7 @@ public void TestCreateReporter()
Assert.Equal(typeof(LcovReporter), new ReporterFactory("lcov").CreateReporter().GetType());
Assert.Equal(typeof(OpenCoverReporter), new ReporterFactory("opencover").CreateReporter().GetType());
Assert.Equal(typeof(CoberturaReporter), new ReporterFactory("cobertura").CreateReporter().GetType());
Assert.Equal(typeof(TeamCityReporter), new ReporterFactory("teamcity").CreateReporter().GetType());
Assert.Null(new ReporterFactory("").CreateReporter());
}
}
Expand Down

0 comments on commit 7ede9d7

Please sign in to comment.