From 487df6b778deeb4eda4e6c00a669fbf676edea48 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Thu, 30 Mar 2023 23:00:57 +0200 Subject: [PATCH 01/14] Bump version of coverage-model to 0.20.0. --- plugin/pom.xml | 2 +- .../plugins/coverage/metrics/steps/CoverageRecorder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/pom.xml b/plugin/pom.xml index f6dea3dbe..a714b9fd9 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -32,7 +32,7 @@ 1.17.6 1.83 - 0.19.1 + 0.20.0 2.0.0 1.29.0-4 1.7.8 diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java index 6fc7ab39c..878e57796 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java @@ -399,7 +399,7 @@ private void perform(final Run run, final FilePath workspace, final TaskLi .stream() .filter(ModuleNode.class::isInstance) .map(ModuleNode.class::cast) - .map(ModuleNode::getSources) + .map(ModuleNode::getSourceFolders) .flatMap(Collection::stream) .collect(Collectors.toSet()); sources.addAll(getSourceDirectoriesPaths()); From b2eedd410fa0e69c66c6bd24efdaaa7fe7716774 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 31 Mar 2023 12:15:49 +0200 Subject: [PATCH 02/14] Remove exception from signature. --- plugin/pom.xml | 2 +- .../coverage/metrics/model/ElementFormatter.java | 10 +++++++++- .../coverage/metrics/model/Messages.properties | 2 ++ .../steps/CoverageMetricColumn/help-metric.html | 4 +++- .../steps/CoverageQualityGate/help-metric.html | 2 ++ .../coverage/metrics/steps/CoverageApiITest.java | 2 ++ .../coverage/metrics/steps/CoveragePluginITest.java | 1 + .../metrics/steps/CoverageXmlStreamTest.java | 13 ++++++++----- 8 files changed, 28 insertions(+), 8 deletions(-) diff --git a/plugin/pom.xml b/plugin/pom.xml index a714b9fd9..e75c5ab2a 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -32,7 +32,7 @@ 1.17.6 1.83 - 0.20.0 + 0.20.1-SNAPSHOT 2.0.0 1.29.0-4 1.7.8 diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java index db50e1f85..4fdf5633d 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java @@ -31,6 +31,7 @@ * * @author Florian Orendi */ +// TODO: create instances for the different types @SuppressWarnings({"PMD.GodClass", "PMD.CyclomaticComplexity"}) public final class ElementFormatter { private static final Fraction HUNDRED = Fraction.getFraction("100.0"); @@ -344,7 +345,9 @@ public String formatPercentage(final int covered, final int total, final Locale * @return the formatted delta percentage as plain text with a leading sign */ public String formatDelta(final Fraction fraction, final Metric metric, final Locale locale) { - if (metric.equals(Metric.COMPLEXITY) || metric.equals(Metric.LOC)) { // TODO: move to metric? + if (metric.equals(Metric.COMPLEXITY) + || metric.equals(Metric.COMPLEXITY_MAXIMUM) + || metric.equals(Metric.LOC)) { // TODO: move to metric? return String.format(locale, "%+d", fraction.intValue()); } return String.format(locale, "%+.2f%%", fraction.multiplyBy(HUNDRED).doubleValue()); @@ -385,6 +388,8 @@ public String getDisplayName(final Metric metric) { return Messages.Metric_COMPLEXITY(); case COMPLEXITY_DENSITY: return Messages.Metric_COMPLEXITY_DENSITY(); + case COMPLEXITY_MAXIMUM: + return Messages.Metric_COMPLEXITY_MAXIMUM(); case LOC: return Messages.Metric_LOC(); default: @@ -469,6 +474,8 @@ public String getLabel(final Metric metric) { return Messages.Metric_Short_COMPLEXITY(); case COMPLEXITY_DENSITY: return Messages.Metric_Short_COMPLEXITY_DENSITY(); + case COMPLEXITY_MAXIMUM: + return Messages.Metric_Short_COMPLEXITY_MAXIMUM(); case LOC: return Messages.Metric_Short_LOC(); default: @@ -520,6 +527,7 @@ public ListBoxModel getMetricItems() { add(options, Metric.INSTRUCTION); add(options, Metric.MUTATION); add(options, Metric.COMPLEXITY); + add(options, Metric.COMPLEXITY_MAXIMUM); add(options, Metric.LOC); return options; } diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties index 374d8bc85..8dc16b9d3 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/model/Messages.properties @@ -10,6 +10,7 @@ Metric.MUTATION=Mutation Coverage Metric.BRANCH=Branch Coverage Metric.COMPLEXITY=Cyclomatic Complexity Metric.COMPLEXITY_DENSITY=Complexity Density +Metric.COMPLEXITY_MAXIMUM=Maximum Cyclomatic Complexity Metric.LOC=Lines of Code Metric.Short.CONTAINER=Container @@ -24,6 +25,7 @@ Metric.Short.MUTATION=Mutation Metric.Short.BRANCH=Branch Metric.Short.COMPLEXITY=Complexity Metric.Short.COMPLEXITY_DENSITY=Complexity Density +Metric.Short.COMPLEXITY_MAXIMUM=Max. Complexity Metric.Short.LOC=LOC Metric.MUTATION.Killed=Killed diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html index 6e5cd1375..8b48d8a92 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageMetricColumn/help-metric.html @@ -1,5 +1,5 @@
- Select the metric for the coverage results that are shown in this column. + Select the metric that should be evaluated for this quality gate. The following different metrics are supported: @@ -26,6 +26,8 @@
Cyclomatic complexity (given as absolute number)
COMPLEXITY_DENSITY
Cyclomatic complexity density (given as relation between cyclomatic complexity and lines of code)
+
COMPLEXITY_MAXIMUM
+
Maximum cyclomatic complexity of all methods (given as absolute number)
LOC
Lines of code (given as absolute number)
diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html index 6987cf777..8b48d8a92 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGate/help-metric.html @@ -26,6 +26,8 @@
Cyclomatic complexity (given as absolute number)
COMPLEXITY_DENSITY
Cyclomatic complexity density (given as relation between cyclomatic complexity and lines of code)
+
COMPLEXITY_MAXIMUM
+
Maximum cyclomatic complexity of all methods (given as absolute number)
LOC
Lines of code (given as absolute number)
diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageApiITest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageApiITest.java index ea3ef812a..9de7d7fca 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageApiITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageApiITest.java @@ -38,6 +38,7 @@ void shouldProvideRemoteApi() { + " \"branch\": \"88.28%\",\n" + " \"complexity\": \"2558\",\n" + " \"complexity-density\": \"+44.12%\",\n" + + " \"complexity-maximum\": \"21\",\n" + " \"file\": \"99.67%\",\n" + " \"instruction\": \"96.11%\",\n" + " \"line\": \"95.39%\",\n" @@ -86,6 +87,7 @@ void shouldShowDeltaInRemoteApi() { + " \"branch\": \"+5.33%\",\n" + " \"complexity\": \"-2558\",\n" + " \"complexity-density\": \"+5.13%\",\n" + + " \"complexity-maximum\": \"-15\",\n" + " \"file\": \"-28.74%\",\n" + " \"instruction\": \"-2.63%\",\n" + " \"line\": \"-4.14%\",\n" diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java index dcbb1947b..1c2d3b276 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoveragePluginITest.java @@ -152,6 +152,7 @@ private static void verifyJaCoCoAction(final CoverageBuildAction coverageResult) Metric.INSTRUCTION, Metric.COMPLEXITY, Metric.COMPLEXITY_DENSITY, + Metric.COMPLEXITY_MAXIMUM, Metric.LOC); assertThat(coverageResult.getMetricsForSummary()) .containsExactly(Metric.LINE, Metric.BRANCH, Metric.MUTATION, Metric.COMPLEXITY_DENSITY, Metric.LOC); diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageXmlStreamTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageXmlStreamTest.java index f4cd8b9b2..23d4e14e5 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageXmlStreamTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageXmlStreamTest.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.List; import java.util.NavigableMap; @@ -50,11 +51,12 @@ class CoverageXmlStreamTest extends SerializableTest { @Override protected Node createSerializable() { - return new JacocoParser().parse(new InputStreamReader(asInputStream("jacoco-codingstyle.xml")), new FilteredLog("Errors")); + return new JacocoParser().parse(new InputStreamReader(asInputStream("jacoco-codingstyle.xml"), + StandardCharsets.UTF_8), new FilteredLog("Errors")); } @Test - void shouldSaveAndRestoreTree() throws IOException { + void shouldSaveAndRestoreTree() { Path saved = createTempFile(); Node convertedNode = createSerializable(); @@ -94,13 +96,13 @@ void shouldSaveAndRestoreTree() throws IOException { void shouldStoreActionCompactly() throws IOException { Path saved = createTempFile(); var xmlStream = new TestXmlStream(); - xmlStream.read(saved); + xmlStream.read(saved); // create the stream var file = new XmlFile(xmlStream.getStream(), saved.toFile()); file.write(createAction()); assertThat(Input.from(saved)).nodesByXPath("//" + ACTION_QUALIFIED_NAME + "/projectValues/*") - .hasSize(11).extractingText() + .hasSize(12).extractingText() .containsExactly("MODULE: 1/1", "PACKAGE: 1/1", "FILE: 7/10", @@ -111,6 +113,7 @@ void shouldStoreActionCompactly() throws IOException { "INSTRUCTION: 1260/1350", "COMPLEXITY: 160", "COMPLEXITY_DENSITY: 160/323", + "COMPLEXITY_MAXIMUM: 6", "LOC: 323"); assertThat(Input.from(saved)).nodesByXPath("//" + ACTION_QUALIFIED_NAME + "/projectValues/coverage") @@ -131,7 +134,7 @@ void shouldStoreActionCompactly() throws IOException { "PACKAGE: 1/1", "FILE: 7/10", "CLASS: 15/18", "METHOD: 97/102", "LINE: 294/323", "BRANCH: 109/116", "INSTRUCTION: 1260/1350", "COMPLEXITY: 160", - "COMPLEXITY_DENSITY: 160/323", "LOC: 323" + "COMPLEXITY_DENSITY: 160/323", "COMPLEXITY_MAXIMUM: 6", "LOC: 323" )); } From 429d6bfbf2e62297a9a254b0eb2bfc101e2b514d Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 31 Mar 2023 18:33:52 +0200 Subject: [PATCH 03/14] Fix quality gates evaluation for different metric tendencies. Some metrics are better when getting larger, some when getting smaller. --- plugin/pom.xml | 2 +- .../steps/CoverageQualityGateEvaluator.java | 2 +- .../metrics/AbstractCoverageTest.java | 13 +++- .../CoverageQualityGateEvaluatorTest.java | 70 +++++++++++++++---- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/plugin/pom.xml b/plugin/pom.xml index e75c5ab2a..bba37f45d 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -32,7 +32,7 @@ 1.17.6 1.83 - 0.20.1-SNAPSHOT + 0.21.0 2.0.0 1.29.0-4 1.7.8 diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluator.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluator.java index 4f4d958b5..146213f99 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluator.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluator.java @@ -31,7 +31,7 @@ protected void evaluate(final CoverageQualityGate qualityGate, final QualityGate if (possibleValue.isPresent()) { var actualValue = possibleValue.get(); - var status = actualValue.isBelowThreshold( + var status = actualValue.isOutOfValidRange( qualityGate.getThreshold()) ? qualityGate.getStatus() : QualityGateStatus.PASSED; result.add(qualityGate, status, FORMATTER.format(actualValue, Locale.ENGLISH)); } diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java index 358473be0..ea31fe7b9 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/AbstractCoverageTest.java @@ -11,6 +11,8 @@ import edu.hm.hafner.coverage.Coverage.CoverageBuilder; import edu.hm.hafner.coverage.CoverageParser; +import edu.hm.hafner.coverage.CyclomaticComplexity; +import edu.hm.hafner.coverage.LinesOfCode; import edu.hm.hafner.coverage.Metric; import edu.hm.hafner.coverage.Node; import edu.hm.hafner.coverage.Value; @@ -102,15 +104,22 @@ public static CoverageStatistics createOnlyProjectStatistics() { private static List fillValues() { var builder = new CoverageBuilder(); - return List.of(builder.setMetric(Metric.FILE).setCovered(3).setMissed(1).build(), + return List.of( + builder.setMetric(Metric.FILE).setCovered(3).setMissed(1).build(), builder.setMetric(Metric.LINE).setCovered(2).setMissed(2).build(), - builder.setMetric(Metric.BRANCH).setCovered(9).setMissed(1).build()); + builder.setMetric(Metric.BRANCH).setCovered(9).setMissed(1).build(), + new CyclomaticComplexity(150), + new CyclomaticComplexity(15, Metric.COMPLEXITY_MAXIMUM), + new LinesOfCode(1000) + ); } private static NavigableMap fillDeltas() { final NavigableMap deltaMapping = new TreeMap<>(); deltaMapping.put(Metric.FILE, Fraction.getFraction(-10, 100)); deltaMapping.put(Metric.LINE, Fraction.getFraction(5, 100)); + deltaMapping.put(Metric.COMPLEXITY, Fraction.getFraction(-10, 1)); + deltaMapping.put(Metric.LOC, Fraction.getFraction(5, 1)); return deltaMapping; } } diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java index 1af65e2fc..73dc7fa54 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java @@ -101,7 +101,23 @@ void shouldReportUnstableIfBelowThreshold() { qualityGates.add(new CoverageQualityGate(76.0, Metric.FILE, Baseline.MODIFIED_FILES, QualityGateCriticality.UNSTABLE)); qualityGates.add(new CoverageQualityGate(51.0, Metric.LINE, Baseline.MODIFIED_FILES, QualityGateCriticality.UNSTABLE)); - var minimum = 10; + CoverageQualityGateEvaluator evaluator = new CoverageQualityGateEvaluator(qualityGates, createStatistics()); + QualityGateResult result = evaluator.evaluate(); + + assertThat(result).hasOverallStatus(QualityGateStatus.WARNING).isNotSuccessful().isNotInactive().hasMessages( + "-> [Overall project - File Coverage]: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00)", + "-> [Overall project - Line Coverage]: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00)", + "-> [Modified code lines - File Coverage]: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00)", + "-> [Modified code lines - Line Coverage]: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00)", + "-> [Modified files - File Coverage]: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00)", + "-> [Modified files - Line Coverage]: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00)"); + } + + @Test + void shouldReportUnstableIfWorseAndSuccessIfBetter() { + Collection qualityGates = new ArrayList<>(); + + var minimum = 0; qualityGates.add(new CoverageQualityGate(minimum, Metric.FILE, Baseline.PROJECT_DELTA, QualityGateCriticality.UNSTABLE)); qualityGates.add(new CoverageQualityGate(minimum, Metric.LINE, Baseline.PROJECT_DELTA, QualityGateCriticality.UNSTABLE)); qualityGates.add(new CoverageQualityGate(minimum, Metric.FILE, Baseline.MODIFIED_LINES_DELTA, QualityGateCriticality.UNSTABLE)); @@ -113,18 +129,45 @@ void shouldReportUnstableIfBelowThreshold() { QualityGateResult result = evaluator.evaluate(); assertThat(result).hasOverallStatus(QualityGateStatus.WARNING).isNotSuccessful().isNotInactive().hasMessages( - "-> [Overall project - File Coverage]: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00)", - "-> [Overall project - Line Coverage]: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00)", - "-> [Modified code lines - File Coverage]: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00)", - "-> [Modified code lines - Line Coverage]: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00)", - "-> [Modified files - File Coverage]: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00)", - "-> [Modified files - Line Coverage]: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00)", - "-> [Overall project (difference to reference job) - File Coverage]: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 10.00)", - "-> [Overall project (difference to reference job) - Line Coverage]: ≪Unstable≫ - (Actual value: +5.00%, Quality gate: 10.00)", - "-> [Modified code lines (difference to modified files) - File Coverage]: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 10.00)", - "-> [Modified code lines (difference to modified files) - Line Coverage]: ≪Unstable≫ - (Actual value: +5.00%, Quality gate: 10.00)", - "-> [Modified files (difference to overall project) - File Coverage]: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 10.00)", - "-> [Modified files (difference to overall project) - Line Coverage]: ≪Unstable≫ - (Actual value: +5.00%, Quality gate: 10.00)"); + "-> [Overall project (difference to reference job) - File Coverage]: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 0.00)", + "-> [Overall project (difference to reference job) - Line Coverage]: ≪Success≫ - (Actual value: +5.00%, Quality gate: 0.00)", + "-> [Modified code lines (difference to modified files) - File Coverage]: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 0.00)", + "-> [Modified code lines (difference to modified files) - Line Coverage]: ≪Success≫ - (Actual value: +5.00%, Quality gate: 0.00)", + "-> [Modified files (difference to overall project) - File Coverage]: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 0.00)", + "-> [Modified files (difference to overall project) - Line Coverage]: ≪Success≫ - (Actual value: +5.00%, Quality gate: 0.00)"); + } + + @Test + void shouldReportUnstableIfLargerThanThreshold() { + Collection qualityGates = new ArrayList<>(); + + qualityGates.add(new CoverageQualityGate(149.0, Metric.COMPLEXITY, Baseline.PROJECT, QualityGateCriticality.UNSTABLE)); + qualityGates.add(new CoverageQualityGate(14, Metric.COMPLEXITY_MAXIMUM, Baseline.PROJECT, QualityGateCriticality.UNSTABLE)); + qualityGates.add(new CoverageQualityGate(999, Metric.LOC, Baseline.MODIFIED_LINES, QualityGateCriticality.UNSTABLE)); + + CoverageQualityGateEvaluator evaluator = new CoverageQualityGateEvaluator(qualityGates, createStatistics()); + QualityGateResult result = evaluator.evaluate(); + + assertThat(result).hasOverallStatus(QualityGateStatus.WARNING).isNotSuccessful().isNotInactive().hasMessages( + "-> [Overall project - Cyclomatic Complexity]: ≪Unstable≫ - (Actual value: 150, Quality gate: 149.00)", + "-> [Overall project - Maximum Cyclomatic Complexity]: ≪Unstable≫ - (Actual value: 15, Quality gate: 14.00)", + "-> [Modified code lines - Lines of Code]: ≪Unstable≫ - (Actual value: 1000, Quality gate: 999.00)"); + } + + @Test + void shouldReportUnstableIfWorseAndSuccessIfBetter2() { + Collection qualityGates = new ArrayList<>(); + + var minimum = 0; + qualityGates.add(new CoverageQualityGate(minimum, Metric.COMPLEXITY, Baseline.PROJECT_DELTA, QualityGateCriticality.UNSTABLE)); + qualityGates.add(new CoverageQualityGate(minimum, Metric.LOC, Baseline.PROJECT_DELTA, QualityGateCriticality.UNSTABLE)); + + CoverageQualityGateEvaluator evaluator = new CoverageQualityGateEvaluator(qualityGates, createStatistics()); + QualityGateResult result = evaluator.evaluate(); + + assertThat(result).hasOverallStatus(QualityGateStatus.WARNING).isNotSuccessful().isNotInactive().hasMessages( + "-> [Overall project (difference to reference job) - Cyclomatic Complexity]: ≪Success≫ - (Actual value: -10, Quality gate: 0.00)", + "-> [Overall project (difference to reference job) - Lines of Code]: ≪Unstable≫ - (Actual value: +5, Quality gate: 0.00)"); } @Test @@ -178,7 +221,6 @@ void shouldOverwriteStatus() { @Test void shouldAddAllQualityGates() { - Collection qualityGates = List.of( new CoverageQualityGate(76.0, Metric.FILE, Baseline.PROJECT, QualityGateCriticality.UNSTABLE), new CoverageQualityGate(51.0, Metric.LINE, Baseline.PROJECT, QualityGateCriticality.FAILURE)); From 6f76d6f2ded15e42ba0e1a0021e623723f0999d4 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 31 Mar 2023 22:37:54 +0200 Subject: [PATCH 04/14] Fix test case. --- .../CoverageQualityGateEvaluatorTest.java | 34 +++++++++++-------- ...lisher-quality-gate.checks-expected-result | 26 +++++++------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java index 73dc7fa54..535616616 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageQualityGateEvaluatorTest.java @@ -172,6 +172,24 @@ void shouldReportUnstableIfWorseAndSuccessIfBetter2() { @Test void shouldReportFailureIfBelowThreshold() { + QualityGateResult result = createQualityGateResult(); + + assertThat(result).hasOverallStatus(QualityGateStatus.FAILED).isNotSuccessful().isNotInactive().hasMessages( + "-> [Overall project - File Coverage]: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)", + "-> [Overall project - Line Coverage]: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)", + "-> [Modified code lines - File Coverage]: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)", + "-> [Modified code lines - Line Coverage]: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)", + "-> [Modified files - File Coverage]: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)", + "-> [Modified files - Line Coverage]: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)", + "-> [Overall project (difference to reference job) - File Coverage]: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)", + "-> [Overall project (difference to reference job) - Line Coverage]: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)", + "-> [Modified code lines (difference to modified files) - File Coverage]: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)", + "-> [Modified code lines (difference to modified files) - Line Coverage]: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)", + "-> [Modified files (difference to overall project) - File Coverage]: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)", + "-> [Modified files (difference to overall project) - Line Coverage]: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)"); + } + + static QualityGateResult createQualityGateResult() { Collection qualityGates = new ArrayList<>(); qualityGates.add(new CoverageQualityGate(76.0, Metric.FILE, Baseline.PROJECT, QualityGateCriticality.FAILURE)); @@ -191,21 +209,7 @@ void shouldReportFailureIfBelowThreshold() { CoverageQualityGateEvaluator evaluator = new CoverageQualityGateEvaluator(qualityGates, createStatistics()); - QualityGateResult result = evaluator.evaluate(); - - assertThat(result).hasOverallStatus(QualityGateStatus.FAILED).isNotSuccessful().isNotInactive().hasMessages( - "-> [Overall project - File Coverage]: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)", - "-> [Overall project - Line Coverage]: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)", - "-> [Modified code lines - File Coverage]: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)", - "-> [Modified code lines - Line Coverage]: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)", - "-> [Modified files - File Coverage]: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00)", - "-> [Modified files - Line Coverage]: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00)", - "-> [Overall project (difference to reference job) - File Coverage]: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)", - "-> [Overall project (difference to reference job) - Line Coverage]: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)", - "-> [Modified code lines (difference to modified files) - File Coverage]: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)", - "-> [Modified code lines (difference to modified files) - Line Coverage]: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)", - "-> [Modified files (difference to overall project) - File Coverage]: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00)", - "-> [Modified files (difference to overall project) - Line Coverage]: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00)"); + return evaluator.evaluate(); } @Test diff --git a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-quality-gate.checks-expected-result b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-quality-gate.checks-expected-result index 6c8c5c963..fd708510b 100644 --- a/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-quality-gate.checks-expected-result +++ b/plugin/src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-quality-gate.checks-expected-result @@ -1,15 +1,15 @@ #### Quality Gates Summary -Overall result: Unstable -- Overall project - File Coverage: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00) -- Overall project - Line Coverage: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00) -- Modified code lines - File Coverage: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00) -- Modified code lines - Line Coverage: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00) -- Modified files - File Coverage: ≪Unstable≫ - (Actual value: 75.00%, Quality gate: 76.00) -- Modified files - Line Coverage: ≪Unstable≫ - (Actual value: 50.00%, Quality gate: 51.00) -- Overall project (difference to reference job) - File Coverage: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 10.00) -- Overall project (difference to reference job) - Line Coverage: ≪Unstable≫ - (Actual value: +5.00%, Quality gate: 10.00) -- Modified code lines (difference to modified files) - File Coverage: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 10.00) -- Modified code lines (difference to modified files) - Line Coverage: ≪Unstable≫ - (Actual value: +5.00%, Quality gate: 10.00) -- Modified files (difference to overall project) - File Coverage: ≪Unstable≫ - (Actual value: -10.00%, Quality gate: 10.00) -- Modified files (difference to overall project) - Line Coverage: ≪Unstable≫ - (Actual value: +5.00%, Quality gate: 10.00) +Overall result: Failed +- Overall project - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00) +- Overall project - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00) +- Modified code lines - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00) +- Modified code lines - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00) +- Modified files - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00) +- Modified files - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00) +- Overall project (difference to reference job) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00) +- Overall project (difference to reference job) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00) +- Modified code lines (difference to modified files) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00) +- Modified code lines (difference to modified files) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00) +- Modified files (difference to overall project) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00) +- Modified files (difference to overall project) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00) From 31d3cfe8e3b0cf5f4f33f4aace08020dee3336f5 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 31 Mar 2023 23:28:12 +0200 Subject: [PATCH 05/14] Use tendency of metric to correctly color the delta values. --- .../metrics/steps/CoverageBuildAction.java | 45 +++++++++++++++++++ .../resources/coverage/coverage-summary.jelly | 9 ++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java index a2e920d54..0edee66a1 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageBuildAction.java @@ -16,6 +16,7 @@ import org.apache.commons.lang3.math.Fraction; import edu.hm.hafner.coverage.Metric; +import edu.hm.hafner.coverage.Metric.MetricTendency; import edu.hm.hafner.coverage.Node; import edu.hm.hafner.coverage.Value; import edu.hm.hafner.echarts.ChartModelConfiguration; @@ -429,6 +430,29 @@ public boolean hasDelta(final Baseline baseline, final Metric metric) { throw new NoSuchElementException("No such baseline: " + baseline); } + /** + * Returns whether a delta metric for the specified metric exists. + * + * @param baseline + * the baseline to use + * @param metric + * the metric to check + * + * @return {@code true} if a delta is available for the specified metric, {@code false} otherwise + */ + public Optional getDelta(final Baseline baseline, final Metric metric) { + if (baseline == Baseline.PROJECT) { + return Optional.ofNullable(difference.get(metric)); + } + if (baseline == Baseline.MODIFIED_LINES) { + return Optional.ofNullable(modifiedLinesCoverageDifference.get(metric)); + } + if (baseline == Baseline.MODIFIED_FILES) { + return Optional.ofNullable(modifiedFilesCoverageDifference.get(metric)); + } + return Optional.empty(); + } + /** * Returns whether a value for the specified metric exists. * @@ -486,6 +510,27 @@ public String formatDelta(final Baseline baseline, final Metric metric) { return Messages.Coverage_Not_Available(); } + /** + * Returns whether the trend of the values for the specific metric is positive or negative. + * + * @param baseline + * the baseline to use + * @param metric + * the metric to check + * + * @return {@code true} if the trend is positive, {@code false} otherwise + */ + public boolean isPositiveTrend(final Baseline baseline, final Metric metric) { + var delta = getDelta(baseline, metric); + if (delta.isPresent()) { + if (delta.get().compareTo(Fraction.ZERO) > 0) { + return metric.getTendency() == MetricTendency.LARGER_IS_BETTER; + } + return metric.getTendency() == MetricTendency.SMALLER_IS_BETTER; + } + return true; + } + /** * Returns the visible metrics for the project summary. * diff --git a/plugin/src/main/resources/coverage/coverage-summary.jelly b/plugin/src/main/resources/coverage/coverage-summary.jelly index 7139e9d10..59fa73de7 100644 --- a/plugin/src/main/resources/coverage/coverage-summary.jelly +++ b/plugin/src/main/resources/coverage/coverage-summary.jelly @@ -59,16 +59,15 @@
  • ${formatter.formatValueWithMetric(value)} - - - + + - + - (${delta}) + (${it.formatDelta(baseline, value.metric)})
  • From 08270d213bdd6877907cdb4037a3c2cbc14a4737 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 31 Mar 2023 23:42:49 +0200 Subject: [PATCH 06/14] Add a column that shows the maximum method complexity. --- .../coverage/metrics/steps/CoverageTableModel.java | 12 ++++++++++++ .../coverage/metrics/steps/Messages.properties | 1 + 2 files changed, 13 insertions(+) diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java index 9c091912f..3ac9470c7 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageTableModel.java @@ -128,6 +128,14 @@ public List getColumns() { .build(); columns.add(complexity); } + if (root.containsMetric(Metric.COMPLEXITY_MAXIMUM)) { + TableColumn maxComplexity = new ColumnBuilder().withHeaderLabel(Messages.Column_MaxComplexity()) + .withDataPropertyKey("maxComplexity") + .withResponsivePriority(900) + .withType(ColumnType.NUMBER) + .build(); + columns.add(maxComplexity); + } if (root.containsMetric(Metric.COMPLEXITY_DENSITY)) { TableColumn complexity = new ColumnBuilder().withHeaderLabel(Messages.Column_ComplexityDensity()) .withDataPropertyKey("density") @@ -253,6 +261,10 @@ public int getComplexity() { return file.getTypedValue(Metric.COMPLEXITY, ZERO_COMPLEXITY).getValue(); } + public int getMaxComplexity() { + return file.getTypedValue(Metric.COMPLEXITY_MAXIMUM, ZERO_COMPLEXITY).getValue(); + } + public DetailedCell getDensity() { double complexityDensity = file.getTypedValue(Metric.COMPLEXITY_DENSITY, ZERO_DENSITY) .getFraction() diff --git a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties index cb9351e53..7ac7b2960 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties +++ b/plugin/src/main/resources/io/jenkins/plugins/coverage/metrics/steps/Messages.properties @@ -29,6 +29,7 @@ Column.MutationCoverage=Mutation Column.DeltaMutationCoverage=Mutation {0} Column.LinesOfCode=LOC Column.Complexity=Complexity +Column.MaxComplexity=Max. Complexity Column.ComplexityDensity=Complexity / LOC MessagesViewModel.Title=Code Coverage From a9ab71df2d943fb2f73b9d2cda56e710e70e267e Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 1 Apr 2023 15:28:04 +0200 Subject: [PATCH 07/14] Enable PIT in CI. --- Jenkinsfile | 42 +++++++++++++++---- plugin/pom.xml | 19 +++++++++ .../metrics/model/ElementFormatter.java | 10 ++--- ...erTest.java => CoverageRecorderITest.java} | 2 +- 4 files changed, 59 insertions(+), 14 deletions(-) rename plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/{CoverageRecorderTest.java => CoverageRecorderITest.java} (97%) diff --git a/Jenkinsfile b/Jenkinsfile index cc930ea0d..35207a89d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,6 +5,7 @@ def configurations = [ def params = [ failFast: false, + pit: true, configurations: configurations, checkstyle: [qualityGates: [[threshold: 1, type: 'NEW', unstable: true]], filters:[includePackage('io.jenkins.plugins.coverage.metrics')]], @@ -86,7 +87,6 @@ def params = [ if (doArchiveArtifacts) { archivedArtifacts = true } - boolean incrementals // cf. JEP-305 stage("Checkout (${stageIdentifier})") { @@ -96,7 +96,8 @@ def params = [ readFile('.mvn/extensions.xml').contains('git-changelist-maven-extension') final String gitUnavailableMessage = '[buildPlugin] Git CLI may not be available' withEnv(["GITUNAVAILABLEMESSAGE=${gitUnavailableMessage}"]) { - if (incrementals) { // Incrementals needs 'git status -s' to be empty at start of job + if (incrementals) { + // Incrementals needs 'git status -s' to be empty at start of job if (isUnix()) { sh 'git clean -xffd || echo "$GITUNAVAILABLEMESSAGE"' } else { @@ -135,9 +136,11 @@ def params = [ if (isUnix()) { mavenOptions += '-Penable-jacoco' } - if (incrementals) { // set changelist and activate produce-incrementals profile + if (incrementals) { + // set changelist and activate produce-incrementals profile mavenOptions += '-Dset.changelist' - if (doArchiveArtifacts) { // ask Maven for the value of -rc999.abc123def456 + if (doArchiveArtifacts) { + // ask Maven for the value of -rc999.abc123def456 changelistF = "${pwd tmp: true}/changelist" mavenOptions += "help:evaluate -Dexpression=changelist -Doutput=$changelistF" } @@ -149,8 +152,12 @@ def params = [ mavenOptions += '-DskipTests' } mavenOptions += 'clean install' + def pit = params.containsKey('pit') ? params.pit : false + if (pit && first) { + mavenOptions += ' org.pitest:pitest-maven:mutationCoverage' + } try { - infra.runMaven(mavenOptions, jdk, null, null, addToolEnv, useArtifactCachingProxy) + infra.runMaven(mavenOptions, jdk, null, addToolEnv, useArtifactCachingProxy) } finally { if (!skipTests) { junit('**/target/surefire-reports/**/*.xml,**/target/failsafe-reports/**/*.xml,**/target/invoker-reports/**/*.xml') @@ -158,12 +165,17 @@ def params = [ discoverReferenceBuild() // Default configuration for JaCoCo can be overwritten using a `jacoco` parameter (map). // Configuration see: https://www.jenkins.io/doc/pipeline/steps/code-coverage-api/#recordcoverage-record-code-coverage-results - Map jacocoArguments = [tools: [[parser: 'JACOCO', pattern: '**/jacoco/jacoco.xml']]] + Map jacocoArguments = [tools: [[parser: 'JACOCO']]] if (params?.jacoco) { jacocoArguments.putAll(params.jacoco as Map) } recordCoverage jacocoArguments - + if (pit) { + recordCoverage( + tools: [[parser: 'PIT']], + id: 'pit', + name: 'Mutation Coverage') + } } } } @@ -198,7 +210,8 @@ def params = [ } if (first) { - if (skipTests) { // otherwise the reference build has been computed already + if (skipTests) { + // otherwise the reference build has been computed already discoverReferenceBuild() } echo "Recording static analysis results on '${stageIdentifier}'" @@ -271,6 +284,19 @@ def params = [ if (failFast && currentBuild.result == 'UNSTABLE') { error 'Static analysis quality gates not passed; halting early' } + /* + * If the current build was successful, we send the commits to Launchable so that + * the result can be consumed by a Launchable build in the future. We do not + * attempt to record commits for non-incrementalified plugins because such + * plugins' PR builds could not be consumed by anything else anyway, and all + * plugins currently in the BOM are incrementalified. We do not attempt to record + * commits on Windows because our Windows agents do not have Python installed. + */ + if (incrementals && platform != 'windows' && currentBuild.currentResult == 'SUCCESS') { + launchable.install() + launchable('verify') + launchable('record commit') + } } else { echo "Skipping static analysis results for ${stageIdentifier}" } diff --git a/plugin/pom.xml b/plugin/pom.xml index bba37f45d..d81e38e9c 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -363,6 +363,25 @@ io.jenkins.plugins.coverage.metrics + + org.pitest + pitest-maven + + + io.jenkins.plugins.coverage.metrics.* + + + io.jenkins.plugins.coverage.metrics.* + + + *Assert + *Assertions + *ArchitectureTest + *ITest + *jmh_generated* + + + org.revapi revapi-maven-plugin diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java index d5fe6732d..506a25aea 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/model/ElementFormatter.java @@ -345,7 +345,7 @@ public String formatPercentage(final int covered, final int total, final Locale public String formatDelta(final Fraction fraction, final Metric metric, final Locale locale) { if (metric.equals(Metric.COMPLEXITY) || metric.equals(Metric.COMPLEXITY_MAXIMUM) - || metric.equals(Metric.LOC)) { // TODO: move to metric? + || metric.equals(Metric.LOC)) { return String.format(locale, "%+d", fraction.intValue()); } return String.format(locale, "%+.2f%%", fraction.multiplyBy(HUNDRED).doubleValue()); @@ -384,10 +384,10 @@ public String getDisplayName(final Metric metric) { return Messages.Metric_MUTATION(); case COMPLEXITY: return Messages.Metric_COMPLEXITY(); - case COMPLEXITY_DENSITY: - return Messages.Metric_COMPLEXITY_DENSITY(); case COMPLEXITY_MAXIMUM: return Messages.Metric_COMPLEXITY_MAXIMUM(); + case COMPLEXITY_DENSITY: + return Messages.Metric_COMPLEXITY_DENSITY(); case LOC: return Messages.Metric_LOC(); default: @@ -453,10 +453,10 @@ public String getLabel(final Metric metric) { return Messages.Metric_Short_MUTATION(); case COMPLEXITY: return Messages.Metric_Short_COMPLEXITY(); - case COMPLEXITY_DENSITY: - return Messages.Metric_Short_COMPLEXITY_DENSITY(); case COMPLEXITY_MAXIMUM: return Messages.Metric_Short_COMPLEXITY_MAXIMUM(); + case COMPLEXITY_DENSITY: + return Messages.Metric_Short_COMPLEXITY_DENSITY(); case LOC: return Messages.Metric_Short_LOC(); default: diff --git a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderTest.java b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderITest.java similarity index 97% rename from plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderTest.java rename to plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderITest.java index 9d507d0a9..47e68c6aa 100644 --- a/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorderITest.java @@ -16,7 +16,7 @@ * * @author Ullrich Hafner */ -class CoverageRecorderTest extends IntegrationTestWithJenkinsPerSuite { +class CoverageRecorderITest extends IntegrationTestWithJenkinsPerSuite { @Test void shouldIgnoreEmptyListOfFiles() { WorkflowJob job = createPipeline(); From b3ade4b85c1b604ce91f1c3c373854319d08560c Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 1 Apr 2023 19:33:44 +0200 Subject: [PATCH 08/14] FIx path to coverage results. --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 35207a89d..53c5022b3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -165,14 +165,14 @@ def params = [ discoverReferenceBuild() // Default configuration for JaCoCo can be overwritten using a `jacoco` parameter (map). // Configuration see: https://www.jenkins.io/doc/pipeline/steps/code-coverage-api/#recordcoverage-record-code-coverage-results - Map jacocoArguments = [tools: [[parser: 'JACOCO']]] + Map jacocoArguments = [tools: [[parser: 'JACOCO', pattern: '**/jacoco/jacoco.xml']]] if (params?.jacoco) { jacocoArguments.putAll(params.jacoco as Map) } recordCoverage jacocoArguments if (pit) { recordCoverage( - tools: [[parser: 'PIT']], + tools: [[parser: 'PIT', pattern: '**/pit-reports/mutations.xml]], id: 'pit', name: 'Mutation Coverage') } From a82c5afdbda960f9e15f78195832380c0fad2fd3 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 1 Apr 2023 19:35:51 +0200 Subject: [PATCH 09/14] Fix PIT runner. --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 53c5022b3..72b410dc2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -172,7 +172,7 @@ def params = [ recordCoverage jacocoArguments if (pit) { recordCoverage( - tools: [[parser: 'PIT', pattern: '**/pit-reports/mutations.xml]], + tools: [[parser: 'PIT', pattern: '**/pit-reports/mutations.xml']], id: 'pit', name: 'Mutation Coverage') } From 4ee9b86e4d4858ca31e8cbbd1ae034edd638dec1 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 1 Apr 2023 20:35:06 +0200 Subject: [PATCH 10/14] Use user defined name of action as default name of checks. --- .../plugins/coverage/metrics/steps/CoverageRecorder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java index 878e57796..1e36964b1 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageRecorder.java @@ -205,7 +205,8 @@ public void setChecksName(final String checksName) { } public String getChecksName() { - return StringUtils.defaultIfBlank(checksName, CHECKS_DEFAULT_NAME); + return StringUtils.defaultIfBlank(checksName, + StringUtils.defaultIfBlank(getName(), CHECKS_DEFAULT_NAME)); } /** From 6a20cd1efea25e73d96e6369dd667119e17d7dd8 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 1 Apr 2023 20:36:52 +0200 Subject: [PATCH 11/14] Add a different checks name for mutation coverage. --- Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 72b410dc2..4eaef3f2f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -174,7 +174,8 @@ def params = [ recordCoverage( tools: [[parser: 'PIT', pattern: '**/pit-reports/mutations.xml']], id: 'pit', - name: 'Mutation Coverage') + name: 'Mutation Coverage', + checksName: 'Mutation Coverage') } } } From 8c90de360aae67749ed7cba13c3fe80be2bf0dad Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 1 Apr 2023 20:38:06 +0200 Subject: [PATCH 12/14] Fix URL. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa090b513..2728176e9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The Jenkins code coverage plug-in collects reports of code coverage or mutation - [Cobertura](https://cobertura.github.io/cobertura/) - [PIT](https://pitest.org/) -If your coverage tool is not yet supported by the code coverage plugin you can provide a pull request for the [Coverage Model](https://github.com/uhafner/coverage-model/pulls). +If your coverage tool is not yet supported by the code coverage plugin you can provide a pull request for the [Coverage Model](https://github.com/jenkinsci/coverage-model/pulls). The plugin publishes a report of the code coverage and mutation coverage in your build, so you can navigate to a summary report from the main build page. From there you can also dive into the details: - tree charts that show the distribution of coverage by type (line, instruction, branch, method, class, etc.) From f2e324877580e3f9d2b5c0c8c273b9229e54974d Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sun, 2 Apr 2023 00:44:11 +0200 Subject: [PATCH 13/14] Enable PIT via a profile. --- Jenkinsfile | 2 +- plugin/pom.xml | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4eaef3f2f..2664e26e4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -154,7 +154,7 @@ def params = [ mavenOptions += 'clean install' def pit = params.containsKey('pit') ? params.pit : false if (pit && first) { - mavenOptions += ' org.pitest:pitest-maven:mutationCoverage' + mavenOptions += '-Ppit' } try { infra.runMaven(mavenOptions, jdk, null, addToolEnv, useArtifactCachingProxy) diff --git a/plugin/pom.xml b/plugin/pom.xml index d81e38e9c..cdb5d62c1 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -392,6 +392,28 @@ + + + pit + + + + org.pitest + pitest-maven + + + test + + mutationCoverage + + + + + + + + + scm:git:https://github.com/${gitHubRepo}.git scm:git:git@github.com:${gitHubRepo}.git From 53c68f56050f7135203e22cdf0e106cc855aec86 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sun, 2 Apr 2023 12:55:00 +0200 Subject: [PATCH 14/14] Fix branch annotation message. --- .../coverage/metrics/steps/CoverageChecksPublisher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java index faa3fefbe..3730b0142 100644 --- a/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java +++ b/plugin/src/main/java/io/jenkins/plugins/coverage/metrics/steps/CoverageChecksPublisher.java @@ -297,10 +297,10 @@ private Collection getPartiallyCoveredLines(final Fi private String createBranchMessage(final int line, final int missed) { if (missed == 1) { - return "Line " + line + " is only partially covered, one branch is missing"; + return String.format("Line %d is only partially covered, one branch is missing", line); } - return "Line " + line + " is only partially covered, %d branches are missing."; + return String.format("Line %d is only partially covered, %d branches are missing", line, missed); } private ChecksAnnotationBuilder createAnnotationBuilder(final FileNode fileNode) {