Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit 47e45e9

Browse files
committed
Rewrite rendering using dedicated classes for each coverage type.
Replace all if/else blocks with proper subclassing. This provides the possibility to enhance the source code tooltips for mutation results with additional details for the mutations.
1 parent 3f9224c commit 47e45e9

File tree

9 files changed

+169
-205
lines changed

9 files changed

+169
-205
lines changed

plugin/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<testcontainers.version>1.18.0</testcontainers.version>
3333
<job-dsl.version>1.83</job-dsl.version>
3434

35-
<coverage-model.version>0.24.0-rc428.9a_7f3f8f7729</coverage-model.version>
35+
<coverage-model.version>0.24.0</coverage-model.version>
3636
<git-forensics.version>2.0.0</git-forensics.version>
3737
<prism-api.version>1.29.0-4</prism-api.version>
3838
<pull-request-monitoring.version>1.7.8</pull-request-monitoring.version>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package io.jenkins.plugins.coverage.metrics.source;
2+
3+
import java.io.Serializable;
4+
import java.util.Arrays;
5+
6+
import org.apache.commons.lang3.StringUtils;
7+
8+
import edu.hm.hafner.coverage.FileNode;
9+
10+
import io.jenkins.plugins.prism.Sanitizer;
11+
12+
import static j2html.TagCreator.*;
13+
14+
/**
15+
* Provides all required information for a {@link FileNode} so that its source code can be rendered together with the
16+
* line and branch coverage in HTML.
17+
*/
18+
class CoverageSourcePrinter implements Serializable {
19+
private static final long serialVersionUID = -6044649044983631852L;
20+
private static final Sanitizer SANITIZER = new Sanitizer();
21+
22+
static final String UNDEFINED = "noCover";
23+
static final String NO_COVERAGE = "coverNone";
24+
static final String FULL_COVERAGE = "coverFull";
25+
static final String PARTIAL_COVERAGE = "coverPart";
26+
private static final String NBSP = "&nbsp;";
27+
28+
private final String path;
29+
private final int[] linesToPaint;
30+
private final int[] coveredPerLine;
31+
32+
private final int[] missedPerLine;
33+
34+
CoverageSourcePrinter(final FileNode file) {
35+
path = file.getRelativePath();
36+
37+
linesToPaint = file.getLinesWithCoverage().stream().mapToInt(i -> i).toArray();
38+
coveredPerLine = file.getCoveredCounters();
39+
missedPerLine = file.getMissedCounters();
40+
}
41+
42+
public String renderLine(final int line, final String sourceCode) {
43+
var isPainted = isPainted(line);
44+
return tr()
45+
.withClass(isPainted ? getColorClass(line) : CoverageSourcePrinter.UNDEFINED)
46+
.condAttr(isPainted, "data-html-tooltip", isPainted ? getTooltip(line) : StringUtils.EMPTY)
47+
.with(
48+
td().withClass("line")
49+
.with(a().withName(String.valueOf(line)).withText(String.valueOf(line))),
50+
td().withClass("hits")
51+
.with(isPainted ? text(getSummaryColumn(line)) : text(StringUtils.EMPTY)),
52+
td().withClass("code")
53+
.with(rawHtml(SANITIZER.render(cleanupCode(sourceCode)))))
54+
.render();
55+
}
56+
57+
private String cleanupCode(final String content) {
58+
return content.replace("\n", StringUtils.EMPTY)
59+
.replace("\r", StringUtils.EMPTY)
60+
.replace(" ", NBSP)
61+
.replace("\t", NBSP.repeat(8));
62+
}
63+
64+
int size() {
65+
return linesToPaint.length;
66+
}
67+
68+
public String getColorClass(final int line) {
69+
if (getCovered(line) == 0) {
70+
return NO_COVERAGE;
71+
}
72+
else if (getMissed(line) == 0) {
73+
return FULL_COVERAGE;
74+
}
75+
else {
76+
return PARTIAL_COVERAGE;
77+
}
78+
}
79+
80+
public String getTooltip(final int line) {
81+
var covered = getCovered(line);
82+
var missed = getMissed(line);
83+
if (covered + missed > 1) {
84+
if (missed == 0) {
85+
return "All branches covered";
86+
}
87+
return String.format("Partially covered, branch coverage: %d/%d", covered, covered + missed);
88+
}
89+
else if (covered == 1) {
90+
return "Covered at least once";
91+
}
92+
else {
93+
return "Not covered";
94+
}
95+
}
96+
97+
public String getSummaryColumn(final int line) {
98+
var covered = getCovered(line);
99+
var missed = getMissed(line);
100+
if (covered + missed > 1) {
101+
return String.format("%d/%d", covered, covered + missed);
102+
}
103+
return String.valueOf(covered);
104+
}
105+
106+
public String getPath() {
107+
return path;
108+
}
109+
110+
public boolean isPainted(final int line) {
111+
return findIndexOfLine(line) >= 0;
112+
}
113+
114+
int findIndexOfLine(final int line) {
115+
return Arrays.binarySearch(linesToPaint, line);
116+
}
117+
118+
public int getCovered(final int line) {
119+
return getCounter(line, coveredPerLine);
120+
}
121+
122+
public int getMissed(final int line) {
123+
return getCounter(line, missedPerLine);
124+
}
125+
126+
int getCounter(final int line, final int... counters) {
127+
var index = findIndexOfLine(line);
128+
if (index >= 0) {
129+
return counters[index];
130+
}
131+
return 0;
132+
}
133+
}

plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/FileWithBranchCoverage.java

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@
1818
import static j2html.TagCreator.*;
1919

2020
/**
21-
* A file with line and mutation coverage information.
22-
*
23-
* @author Ullrich Hafner
21+
* Provides all required information for a {@link FileNode} so that its source code can be rendered together with the
22+
* line and mutation coverage in HTML.
2423
*/
25-
class FileWithMutationCoverage extends FileWithBranchCoverage {
24+
class MutationSourcePrinter extends CoverageSourcePrinter {
2625
private static final long serialVersionUID = -2215657894423024907L;
2726

2827
private final int[] survivedPerLine;
2928
private final int[] killedPerLine;
3029
private final String[] tooltipPerLine;
3130

32-
FileWithMutationCoverage(final FileNode file) {
31+
MutationSourcePrinter(final FileNode file) {
3332
super(file);
3433

3534
survivedPerLine = new int[size()];
@@ -62,7 +61,9 @@ private String createInfo(final List<Mutation> allMutations) {
6261
Mutation::isKilled, "Killed Mutations:");
6362
ContainerTag survivedContainer = listMutations(allMutations,
6463
Mutation::hasSurvived, "Survived Mutations:");
65-
64+
if (killedContainer.getNumChildren() == 0 && survivedContainer.getNumChildren() == 0) {
65+
return "Not covered";
66+
}
6667
return div().with(killedContainer, survivedContainer).render();
6768
}
6869

plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/PaintedNode.java

Lines changed: 0 additions & 69 deletions
This file was deleted.

plugin/src/main/java/io/jenkins/plugins/coverage/metrics/source/SourceCodePainter.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,16 @@ public void processSourceCodePainting(final Node rootNode, final List<FileNode>
8888
sourceCodeRetention.cleanup(build, sourceCodeFacade.getCoverageSourcesDirectory(), log);
8989
}
9090

91-
private FileWithBranchCoverage createFileModel(final Node rootNode, final FileNode fileNode) {
91+
private CoverageSourcePrinter createFileModel(final Node rootNode, final FileNode fileNode) {
9292
if (rootNode.getValue(Metric.MUTATION).isPresent()) {
93-
return new FileWithMutationCoverage(fileNode);
93+
return new MutationSourcePrinter(fileNode);
9494
}
9595
else {
96-
return new FileWithBranchCoverage(fileNode);
96+
return new CoverageSourcePrinter(fileNode);
9797
}
9898
}
9999

100-
private void paintFilesOnAgent(final List<? extends PaintedNode> paintedFiles,
100+
private void paintFilesOnAgent(final List<? extends CoverageSourcePrinter> paintedFiles,
101101
final String sourceCodeEncoding, final FilteredLog log) throws InterruptedException {
102102
try {
103103
var painter = new AgentCoveragePainter(paintedFiles, sourceCodeEncoding, id);
@@ -117,25 +117,25 @@ private void paintFilesOnAgent(final List<? extends PaintedNode> paintedFiles,
117117
static class AgentCoveragePainter extends MasterToSlaveFileCallable<FilteredLog> {
118118
private static final long serialVersionUID = 3966282357309568323L;
119119

120-
private final List<? extends PaintedNode> paintedFiles;
120+
private final List<? extends CoverageSourcePrinter> paintedFiles;
121121
private final String sourceCodeEncoding;
122122
private final String directory;
123123

124124
/**
125125
* Creates a new instance of {@link AgentCoveragePainter}.
126126
*
127-
* @param paintedFiles
128-
* the model for the file painting for each coverage node
127+
* @param files
128+
* the pretty printers for the files to create the HTML reports for
129129
* @param sourceCodeEncoding
130130
* the encoding of the source code files
131131
* @param directory
132132
* the subdirectory where the source files will be stored in
133133
*/
134-
AgentCoveragePainter(final List<? extends PaintedNode> paintedFiles, final String sourceCodeEncoding,
134+
AgentCoveragePainter(final List<? extends CoverageSourcePrinter> files, final String sourceCodeEncoding,
135135
final String directory) {
136136
super();
137137

138-
this.paintedFiles = paintedFiles;
138+
this.paintedFiles = files;
139139
this.sourceCodeEncoding = sourceCodeEncoding;
140140
this.directory = directory;
141141
}
@@ -185,7 +185,7 @@ private Charset getCharset() {
185185
return new ValidationUtilities().getCharset(sourceCodeEncoding);
186186
}
187187

188-
private int paintSource(final PaintedNode fileNode, final FilePath workspace,
188+
private int paintSource(final CoverageSourcePrinter fileNode, final FilePath workspace,
189189
final Path temporaryFolder, final FilteredLog log) {
190190
String relativePathIdentifier = fileNode.getPath();
191191
FilePath paintedFilesDirectory = workspace.child(directory);
@@ -195,7 +195,7 @@ paintedFilesDirectory, temporaryFolder, getCharset(), log))
195195
.orElse(0);
196196
}
197197

198-
private int paint(final PaintedNode paint, final String relativePathIdentifier,
198+
private int paint(final CoverageSourcePrinter paint, final String relativePathIdentifier,
199199
final FilePath resolvedPath, final FilePath paintedFilesDirectory,
200200
final Path temporaryFolder, final Charset charset, final FilteredLog log) {
201201
String sanitizedFileName = SourceCodeFacade.sanitizeFilename(relativePathIdentifier);
@@ -206,7 +206,9 @@ private int paint(final PaintedNode paint, final String relativePathIdentifier,
206206
Path fullSourcePath = paintedFilesFolder.resolve(sanitizedFileName);
207207
try (BufferedWriter output = Files.newBufferedWriter(fullSourcePath)) {
208208
List<String> lines = Files.readAllLines(Paths.get(resolvedPath.getRemote()), charset);
209-
new SourceToHtml().paintSourceCodeWithCoverageInformation(paint, output, lines);
209+
for (int line = 0; line < lines.size(); line++) {
210+
output.write(paint.renderLine(line + 1, lines.get(line)));
211+
}
210212
}
211213
new FilePath(fullSourcePath.toFile()).zip(zipOutputPath);
212214
FileUtils.deleteDirectory(paintedFilesFolder.toFile());

0 commit comments

Comments
 (0)