Skip to content

Commit 7ede9d7

Browse files
authored
Merge pull request #227 from Reptarsrage/226_teamcity_support
226 teamcity support
2 parents 4c3471d + fbd01ad commit 7ede9d7

File tree

12 files changed

+286
-29
lines changed

12 files changed

+286
-29
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Supported Formats:
9898
* lcov
9999
* opencover
100100
* cobertura
101+
* teamcity
101102
102103
The `--format` option can be specified multiple times to output multiple formats in a single run:
103104
@@ -117,6 +118,28 @@ The above command will write the results to the supplied path, if no file extens
117118
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --output "/custom/directory/" -f json -f lcov
118119
```
119120
121+
#### TeamCity Output
122+
123+
Coverlet can output basic code coverage statistics using [TeamCity service messages](https://confluence.jetbrains.com/display/TCD18/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages).
124+
125+
```bash
126+
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --output teamcity
127+
```
128+
129+
The currently supported [TeamCity statistics](https://confluence.jetbrains.com/display/TCD18/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages) are:
130+
131+
| TeamCity Statistic Key | Description |
132+
| :--- | :--- |
133+
| CodeCoverageL | Line-level code coverage |
134+
| CodeCoverageC | Class-level code coverage |
135+
| CodeCoverageM | Method-level code coverage |
136+
| CodeCoverageAbsLTotal | The total number of lines |
137+
| CodeCoverageAbsLCovered | The number of covered lines |
138+
| CodeCoverageAbsCTotal | The total number of classes |
139+
| CodeCoverageAbsCCovered | The number of covered classes |
140+
| CodeCoverageAbsMTotal | The total number of methods |
141+
| CodeCoverageAbsMCovered | The number of covered methods |
142+
120143
#### Merging Results
121144
122145
With Coverlet you can combine the output of multiple coverage runs into a single result.
@@ -234,6 +257,7 @@ Supported Formats:
234257
* lcov
235258
* opencover
236259
* cobertura
260+
* teamcity
237261
238262
You can specify multiple output formats by separating them with a comma (`,`).
239263

src/coverlet.console/Program.cs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Diagnostics;
44
using System.IO;
55
using System.Text;
6-
76
using ConsoleTables;
87
using Coverlet.Console.Logging;
98
using Coverlet.Core;
@@ -73,22 +72,32 @@ static int Main(string[] args)
7372
if (reporter == null)
7473
throw new Exception($"Specified output format '{format}' is not supported");
7574

76-
var filename = Path.GetFileName(dOutput);
77-
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
78-
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";
79-
80-
var report = Path.Combine(directory, filename);
81-
logger.LogInformation($" Generating report '{report}'");
82-
File.WriteAllText(report, reporter.Report(result));
75+
if (reporter.OutputType == ReporterOutputType.Console)
76+
{
77+
// Output to console
78+
logger.LogInformation(" Outputting results to console");
79+
logger.LogInformation(reporter.Report(result));
80+
}
81+
else
82+
{
83+
// Output to file
84+
var filename = Path.GetFileName(dOutput);
85+
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
86+
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";
87+
88+
var report = Path.Combine(directory, filename);
89+
logger.LogInformation($" Generating report '{report}'");
90+
File.WriteAllText(report, reporter.Report(result));
91+
}
8392
}
8493

8594
var summary = new CoverageSummary();
8695
var exceptionBuilder = new StringBuilder();
8796
var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
8897
var thresholdFailed = false;
89-
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules).Percent * 100;
90-
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules).Percent * 100;
91-
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules).Percent * 100;
98+
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules);
99+
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
100+
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules);
92101

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

123132
logger.LogInformation(string.Empty);
124133
logger.LogInformation(coverageTable.ToStringAlternative());
125-
logger.LogInformation($"Total Line: {overallLineCoverage}%");
126-
logger.LogInformation($"Total Branch: {overallBranchCoverage}%");
127-
logger.LogInformation($"Total Method: {overallMethodCoverage}%");
134+
logger.LogInformation($"Total Line: {overallLineCoverage.Percent * 100}%");
135+
logger.LogInformation($"Total Branch: {overallBranchCoverage.Percent * 100}%");
136+
logger.LogInformation($"Total Method: {overallMethodCoverage.Percent * 100}%");
128137

129138
if (thresholdFailed)
130139
throw new Exception(exceptionBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray()));

src/coverlet.core/Reporters/CoberturaReporter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ namespace Coverlet.Core.Reporters
1010
{
1111
public class CoberturaReporter : IReporter
1212
{
13+
public ReporterOutputType OutputType => ReporterOutputType.File;
14+
1315
public string Format => "cobertura";
1416

1517
public string Extension => "cobertura.xml";

src/coverlet.core/Reporters/IReporter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ namespace Coverlet.Core.Reporters
22
{
33
public interface IReporter
44
{
5+
ReporterOutputType OutputType { get; }
56
string Format { get; }
67
string Extension { get; }
78
string Report(CoverageResult result);
89
}
10+
11+
public enum ReporterOutputType
12+
{
13+
File,
14+
Console,
15+
}
916
}

src/coverlet.core/Reporters/JsonReporter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ namespace Coverlet.Core.Reporters
44
{
55
public class JsonReporter : IReporter
66
{
7+
public ReporterOutputType OutputType => ReporterOutputType.File;
8+
79
public string Format => "json";
810

911
public string Extension => "json";

src/coverlet.core/Reporters/LcovReporter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ namespace Coverlet.Core.Reporters
66
{
77
public class LcovReporter : IReporter
88
{
9+
public ReporterOutputType OutputType => ReporterOutputType.File;
10+
911
public string Format => "lcov";
1012

1113
public string Extension => "info";

src/coverlet.core/Reporters/OpenCoverReporter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace Coverlet.Core.Reporters
99
{
1010
public class OpenCoverReporter : IReporter
1111
{
12+
public ReporterOutputType OutputType => ReporterOutputType.File;
13+
1214
public string Format => "opencover";
1315

1416
public string Extension => "opencover.xml";

src/coverlet.core/Reporters/ReporterFactory.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using System.Collections.Generic;
4+
using coverlet.core.Reporters;
45

56
namespace Coverlet.Core.Reporters
67
{
@@ -14,7 +15,8 @@ public ReporterFactory(string format)
1415
_format = format;
1516
_reporters = new IReporter[] {
1617
new JsonReporter(), new LcovReporter(),
17-
new OpenCoverReporter(), new CoberturaReporter()
18+
new OpenCoverReporter(), new CoberturaReporter(),
19+
new TeamCityReporter()
1820
};
1921
}
2022

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using Coverlet.Core;
2+
using Coverlet.Core.Reporters;
3+
using System.Text;
4+
5+
namespace coverlet.core.Reporters
6+
{
7+
public class TeamCityReporter : IReporter
8+
{
9+
public ReporterOutputType OutputType => ReporterOutputType.Console;
10+
11+
public string Format => "teamcity";
12+
13+
public string Extension => null;
14+
15+
public string Report(CoverageResult result)
16+
{
17+
// Calculate coverage
18+
var summary = new CoverageSummary();
19+
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules);
20+
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
21+
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules);
22+
23+
// Report coverage
24+
var stringBuilder = new StringBuilder();
25+
OutputLineCoverage(overallLineCoverage, stringBuilder);
26+
OutputBranchCoverage(overallBranchCoverage, stringBuilder);
27+
OutputMethodCoverage(overallMethodCoverage, stringBuilder);
28+
29+
// Return a placeholder
30+
return stringBuilder.ToString();
31+
}
32+
33+
private void OutputLineCoverage(CoverageDetails coverageDetails, StringBuilder builder)
34+
{
35+
// The total number of lines
36+
OutputTeamCityServiceMessage("CodeCoverageL", coverageDetails.Total, builder);
37+
38+
// The number of covered lines
39+
OutputTeamCityServiceMessage("CodeCoverageAbsLCovered", coverageDetails.Covered, builder);
40+
41+
// Line-level code coverage
42+
OutputTeamCityServiceMessage("CodeCoverageAbsLTotal", coverageDetails.Percent * 100, builder);
43+
}
44+
45+
private void OutputBranchCoverage(CoverageDetails coverageDetails, StringBuilder builder)
46+
{
47+
// The total number of branches
48+
OutputTeamCityServiceMessage("CodeCoverageR", coverageDetails.Total, builder);
49+
50+
// The number of covered branches
51+
OutputTeamCityServiceMessage("CodeCoverageAbsRCovered", coverageDetails.Covered, builder);
52+
53+
// Branch-level code coverage
54+
OutputTeamCityServiceMessage("CodeCoverageAbsRTotal", coverageDetails.Percent * 100, builder);
55+
}
56+
57+
private void OutputMethodCoverage(CoverageDetails coverageDetails, StringBuilder builder)
58+
{
59+
// The total number of methods
60+
OutputTeamCityServiceMessage("CodeCoverageM", coverageDetails.Total, builder);
61+
62+
// The number of covered methods
63+
OutputTeamCityServiceMessage("CodeCoverageAbsMCovered", coverageDetails.Covered, builder);
64+
65+
// Method-level code coverage
66+
OutputTeamCityServiceMessage("CodeCoverageAbsMTotal", coverageDetails.Percent * 100, builder);
67+
}
68+
69+
private void OutputTeamCityServiceMessage(string key, object value, StringBuilder builder)
70+
{
71+
builder.AppendLine($"##teamcity[buildStatisticValue key='{key}' value='{value}']");
72+
}
73+
}
74+
}

src/coverlet.msbuild.tasks/CoverageResultTask.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,33 @@ public override bool Execute()
6565
if (reporter == null)
6666
throw new Exception($"Specified output format '{format}' is not supported");
6767

68-
var filename = Path.GetFileName(_output);
69-
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
70-
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";
71-
72-
var report = Path.Combine(directory, filename);
73-
Console.WriteLine($" Generating report '{report}'");
74-
File.WriteAllText(report, reporter.Report(result));
68+
if (reporter.OutputType == ReporterOutputType.Console)
69+
{
70+
// Output to console
71+
Console.WriteLine(" Outputting results to console");
72+
Console.WriteLine(reporter.Report(result));
73+
}
74+
else
75+
{
76+
// Output to file
77+
var filename = Path.GetFileName(_output);
78+
filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename;
79+
filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}";
80+
81+
var report = Path.Combine(directory, filename);
82+
Console.WriteLine($" Generating report '{report}'");
83+
File.WriteAllText(report, reporter.Report(result));
84+
}
7585
}
7686

7787
var thresholdFailed = false;
7888
var thresholdTypes = _thresholdType.Split(',').Select(t => t.Trim());
7989
var summary = new CoverageSummary();
8090
var exceptionBuilder = new StringBuilder();
8191
var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
82-
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules).Percent * 100;
83-
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules).Percent * 100;
84-
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules).Percent * 100;
92+
var overallLineCoverage = summary.CalculateLineCoverage(result.Modules);
93+
var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
94+
var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules);
8595

8696
foreach (var module in result.Modules)
8797
{
@@ -115,9 +125,9 @@ public override bool Execute()
115125

116126
Console.WriteLine();
117127
Console.WriteLine(coverageTable.ToStringAlternative());
118-
Console.WriteLine($"Total Line: {overallLineCoverage}%");
119-
Console.WriteLine($"Total Branch: {overallBranchCoverage}%");
120-
Console.WriteLine($"Total Method: {overallMethodCoverage}%");
128+
Console.WriteLine($"Total Line: {overallLineCoverage.Percent * 100}%");
129+
Console.WriteLine($"Total Branch: {overallBranchCoverage.Percent * 100}%");
130+
Console.WriteLine($"Total Method: {overallMethodCoverage.Percent * 100}%");
121131

122132
if (thresholdFailed)
123133
throw new Exception(exceptionBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray()));

test/coverlet.core.tests/Reporters/ReporterFactoryTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using coverlet.core.Reporters;
22
using Xunit;
33

44
namespace Coverlet.Core.Reporters.Tests
@@ -12,6 +12,7 @@ public void TestCreateReporter()
1212
Assert.Equal(typeof(LcovReporter), new ReporterFactory("lcov").CreateReporter().GetType());
1313
Assert.Equal(typeof(OpenCoverReporter), new ReporterFactory("opencover").CreateReporter().GetType());
1414
Assert.Equal(typeof(CoberturaReporter), new ReporterFactory("cobertura").CreateReporter().GetType());
15+
Assert.Equal(typeof(TeamCityReporter), new ReporterFactory("teamcity").CreateReporter().GetType());
1516
Assert.Null(new ReporterFactory("").CreateReporter());
1617
}
1718
}

0 commit comments

Comments
 (0)