Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.
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
4 changes: 2 additions & 2 deletions plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.jvnet.hudson.plugins</groupId>
<artifactId>analysis-pom</artifactId>
<version>6.12.0</version>
<version>6.14.0</version>
<relativePath />
</parent>

Expand Down Expand Up @@ -33,7 +33,7 @@
<testcontainers.version>1.19.0</testcontainers.version>
<job-dsl.version>1.84</job-dsl.version>

<coverage-model.version>0.24.0</coverage-model.version>
<coverage-model.version>0.25.0</coverage-model.version>
<git-forensics.version>2.0.0</git-forensics.version>
<prism-api.version>1.29.0-8</prism-api.version>
<pull-request-monitoring.version>1.7.8</pull-request-monitoring.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import edu.hm.hafner.coverage.Mutation;
import edu.hm.hafner.coverage.Node;
import edu.hm.hafner.coverage.Value;
import edu.hm.hafner.util.LineRange;
import edu.hm.hafner.util.VisibleForTesting;

import hudson.model.TaskListener;
Expand All @@ -46,7 +47,7 @@
*
* @author Florian Orendi
*/
@SuppressWarnings("PMD.GodClass")
@SuppressWarnings({"PMD.GodClass", "PMD.CyclomaticComplexity"})
class CoverageChecksPublisher {
private static final ElementFormatter FORMATTER = new ElementFormatter();
private static final int TITLE_HEADER_LEVEL = 4;
Expand Down Expand Up @@ -245,10 +246,16 @@ private List<ChecksAnnotation> getAnnotations() {
}

var annotations = new ArrayList<ChecksAnnotation>();
for (var fileNode : filterAnnotations().getAllFileNodes()) {
annotations.addAll(getMissingLines(fileNode));
annotations.addAll(getPartiallyCoveredLines(fileNode));
annotations.addAll(getSurvivedMutations(fileNode));
var filteredByScope = filterAnnotations();
var hasMutationCoverage = filteredByScope.getValue(Metric.MUTATION).isPresent();
for (var fileNode : filteredByScope.getAllFileNodes()) {
if (hasMutationCoverage) {
annotations.addAll(getSurvivedMutations(fileNode));
}
else {
annotations.addAll(getMissingLines(fileNode));
annotations.addAll(getPartiallyCoveredLines(fileNode));
}
}
return annotations;
}
Expand All @@ -263,20 +270,32 @@ private Node filterAnnotations() {
}

private Collection<? extends ChecksAnnotation> getMissingLines(final FileNode fileNode) {
var builder = createAnnotationBuilder(fileNode).withTitle("Not covered line");

return fileNode.getMissedLines().stream()
.map(line -> builder.withMessage("Line " + line + " is not covered by tests")
.withStartLine(line)
.withEndLine(line)
.build())
var builder = createAnnotationBuilder(fileNode);
return fileNode.getMissedLineRanges().stream()
.map(range -> rangeToAnnotation(range, builder))
.collect(Collectors.toList());
}

private ChecksAnnotation rangeToAnnotation(final LineRange range, final ChecksAnnotationBuilder builder) {
if (range.getStart() == range.getEnd()) {
builder.withTitle("Not covered line")
.withMessage(String.format("Line %d is not covered by tests", range.getStart()));
}
else {
builder.withTitle("Not covered lines").withMessage(
String.format("Lines %d-%d are not covered by tests", range.getStart(), range.getEnd()));
}
return builder
.withStartLine(range.getStart())
.withEndLine(range.getEnd())
.build();
}

private Collection<? extends ChecksAnnotation> getSurvivedMutations(final FileNode fileNode) {
var builder = createAnnotationBuilder(fileNode).withTitle("Mutation survived");

return fileNode.getSurvivedMutationsPerLine().entrySet().stream()
.filter(entry -> fileNode.getCoveredOfLine(entry.getKey()) > 0)
.map(entry -> builder.withMessage(createMutationMessage(entry.getKey(), entry.getValue()))
.withStartLine(entry.getKey())
.withEndLine(entry.getKey())
Expand All @@ -293,11 +312,15 @@ private String createMutationDetails(final List<Mutation> mutations) {

private String createMutationMessage(final int line, final List<Mutation> survived) {
if (survived.size() == 1) {
return "One mutation survived in line " + line;
return String.format("One mutation survived in line %d (%s)", line, formatMutator(survived));
}
return String.format("%d mutations survived in line %d", survived.size(), line);
}

private String formatMutator(final List<Mutation> survived) {
return survived.get(0).getMutator().replaceAll(".*\\.", "");
}

private Collection<? extends ChecksAnnotation> getPartiallyCoveredLines(final FileNode fileNode) {
var builder = createAnnotationBuilder(fileNode).withTitle("Partially covered line");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void shouldShowProjectBaselineForPit() {
}

@ParameterizedTest(name = "should create checks (scope = {0}, expected annotations = {1})")
@CsvSource({"SKIP, 0", "ALL_LINES, 18", "MODIFIED_LINES, 1"})
@CsvSource({"SKIP, 0", "ALL_LINES, 5", "MODIFIED_LINES, 1"})
void shouldCreateChecksReportPit(final ChecksAnnotationScope scope, final int expectedAnnotations) {
var result = readResult("mutations.xml", new PitestParser());

Expand All @@ -101,45 +101,40 @@ private void assertMutationAnnotations(final ChecksOutput output, final int expe
assertThat(annotation.getRawDetails()).contains("Survived mutations:\n"
+ "- Replaced integer addition with subtraction (org.pitest.mutationtest.engine.gregor.mutators.MathMutator)");
assertThat(annotation.getPath()).contains("edu/hm/hafner/coverage/parser/CoberturaParser.java");
assertThat(annotation.getMessage()).contains("One mutation survived in line 251");
assertThat(annotation.getStartLine()).isPresent().get().isEqualTo(251);
assertThat(annotation.getEndLine()).isPresent().get().isEqualTo(251);
assertThat(annotation.getMessage()).contains("One mutation survived in line 251 (MathMutator)");
assertThat(annotation.getStartLine()).isPresent().contains(251);
assertThat(annotation.getEndLine()).isPresent().contains(251);
});
}
}

private void assertThatTitleIs(final CoverageChecksPublisher publisher, final String expectedTitle) {
var checkDetails = publisher.extractChecksDetails();
assertThat(checkDetails.getOutput()).isPresent().get().satisfies(output -> {
assertThat(output.getTitle()).isPresent()
.get()
.isEqualTo(expectedTitle);
assertThat(output.getTitle()).isPresent().contains(expectedTitle);
});
}

@ParameterizedTest(name = "should create checks (scope = {0}, expected annotations = {1})")
@CsvSource({"SKIP, 0", "ALL_LINES, 36", "MODIFIED_LINES, 3"})
@CsvSource({"SKIP, 0", "ALL_LINES, 28", "MODIFIED_LINES, 2"})
void shouldCreateChecksReportJaCoCo(final ChecksAnnotationScope scope, final int expectedAnnotations) {
var result = readJacocoResult("jacoco-codingstyle.xml");

var publisher = new CoverageChecksPublisher(createCoverageBuildAction(result), result, REPORT_NAME, scope, createJenkins());

var checkDetails = publisher.extractChecksDetails();

assertThat(checkDetails.getName()).isPresent().get().isEqualTo(REPORT_NAME);
assertThat(checkDetails.getName()).isPresent().contains(REPORT_NAME);
assertThat(checkDetails.getStatus()).isEqualTo(ChecksStatus.COMPLETED);
assertThat(checkDetails.getConclusion()).isEqualTo(ChecksConclusion.SUCCESS);
assertThat(checkDetails.getDetailsURL()).isPresent()
.get()
.isEqualTo("http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage");
.contains("http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage");
assertThatDetailsAreCorrect(checkDetails, expectedAnnotations);
}

private void assertThatDetailsAreCorrect(final ChecksDetails checkDetails, final int expectedAnnotations) {
assertThat(checkDetails.getOutput()).isPresent().get().satisfies(output -> {
assertThat(output.getTitle()).isPresent()
.get()
.isEqualTo("Line Coverage: 50.00% (+50.00%)");
assertThat(output.getTitle()).isPresent().contains("Line Coverage: 50.00% (+50.00%)");
var expectedDetails = toString("coverage-publisher-details.checks-expected-result");
assertThat(output.getText()).isPresent().get().asString().isEqualToNormalizingWhitespace(expectedDetails);
assertChecksAnnotations(output, expectedAnnotations);
Expand All @@ -160,25 +155,17 @@ private void assertChecksAnnotations(final ChecksOutput checksOutput, final int
assertThat(annotation.getTitle()).contains("Not covered line");
assertThat(annotation.getAnnotationLevel()).isEqualTo(ChecksAnnotationLevel.WARNING);
assertThat(annotation.getPath()).contains("edu/hm/hafner/util/TreeStringBuilder.java");
assertThat(annotation.getMessage()).contains("Line 61 is not covered by tests");
assertThat(annotation.getStartLine()).isPresent().get().isEqualTo(61);
assertThat(annotation.getEndLine()).isPresent().get().isEqualTo(61);
},
annotation -> {
assertThat(annotation.getTitle()).contains("Not covered line");
assertThat(annotation.getAnnotationLevel()).isEqualTo(ChecksAnnotationLevel.WARNING);
assertThat(annotation.getPath()).contains("edu/hm/hafner/util/TreeStringBuilder.java");
assertThat(annotation.getMessage()).contains("Line 62 is not covered by tests");
assertThat(annotation.getStartLine()).isPresent().get().isEqualTo(62);
assertThat(annotation.getEndLine()).isPresent().get().isEqualTo(62);
assertThat(annotation.getMessage()).contains("Lines 61-62 are not covered by tests");
assertThat(annotation.getStartLine()).isPresent().contains(61);
assertThat(annotation.getEndLine()).isPresent().contains(62);
},
annotation -> {
assertThat(annotation.getTitle()).contains("Partially covered line");
assertThat(annotation.getAnnotationLevel()).isEqualTo(ChecksAnnotationLevel.WARNING);
assertThat(annotation.getPath()).contains("edu/hm/hafner/util/TreeStringBuilder.java");
assertThat(annotation.getMessage()).contains("Line 113 is only partially covered, one branch is missing");
assertThat(annotation.getStartLine()).isPresent().get().isEqualTo(113);
assertThat(annotation.getEndLine()).isPresent().get().isEqualTo(113);
assertThat(annotation.getStartLine()).isPresent().contains(113);
assertThat(annotation.getEndLine()).isPresent().contains(113);
});
}
else {
Expand Down