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 146213f99..082ce4562 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 @@ -3,6 +3,13 @@ import java.util.Collection; import java.util.Locale; +import org.apache.commons.lang3.math.Fraction; + +import edu.hm.hafner.coverage.FractionValue; +import edu.hm.hafner.coverage.Metric; +import edu.hm.hafner.coverage.SafeFraction; +import edu.hm.hafner.coverage.Value; + import io.jenkins.plugins.coverage.metrics.model.CoverageStatistics; import io.jenkins.plugins.coverage.metrics.model.ElementFormatter; import io.jenkins.plugins.util.QualityGateEvaluator; @@ -15,10 +22,13 @@ * @author Johannes Walter */ class CoverageQualityGateEvaluator extends QualityGateEvaluator { + private static final Fraction HUNDRED = Fraction.getFraction("100.0"); + private static final ElementFormatter FORMATTER = new ElementFormatter(); private final CoverageStatistics statistics; - CoverageQualityGateEvaluator(final Collection qualityGates, final CoverageStatistics statistics) { + CoverageQualityGateEvaluator(final Collection qualityGates, + final CoverageStatistics statistics) { super(qualityGates); this.statistics = statistics; @@ -29,14 +39,41 @@ protected void evaluate(final CoverageQualityGate qualityGate, final QualityGate var baseline = qualityGate.getBaseline(); var possibleValue = statistics.getValue(baseline, qualityGate.getMetric()); if (possibleValue.isPresent()) { - var actualValue = possibleValue.get(); + var actualValue = convertActualValue(possibleValue.get()); var status = actualValue.isOutOfValidRange( qualityGate.getThreshold()) ? qualityGate.getStatus() : QualityGateStatus.PASSED; - result.add(qualityGate, status, FORMATTER.format(actualValue, Locale.ENGLISH)); + result.add(qualityGate, status, FORMATTER.format(possibleValue.get(), Locale.ENGLISH)); } else { result.add(qualityGate, QualityGateStatus.INACTIVE, "n/a"); } } + + /** + * Converts the actual value to a percentage if necessary. Delta values are internally stored as fractions, but + * users expect percentages when they are displayed or used in thresholds. + * + * @param value + * the actual stored value + * + * @return the converted value + */ + private Value convertActualValue(final Value value) { + var metric = value.getMetric(); + if (metric.equals(Metric.COMPLEXITY) + || metric.equals(Metric.COMPLEXITY_MAXIMUM) + || metric.equals(Metric.LOC)) { + return value; // ignore integer based metrics + } + if (value instanceof FractionValue) { // delta percentage + return new FractionValue(metric, covertToPercentage((FractionValue) value)); + } + + return value; + } + + private Fraction covertToPercentage(final FractionValue value) { + return new SafeFraction(value.getFraction()).multiplyBy(HUNDRED); + } } 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 535616616..9d28b1fd9 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 @@ -5,6 +5,8 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import edu.hm.hafner.coverage.Metric; @@ -137,6 +139,51 @@ void shouldReportUnstableIfWorseAndSuccessIfBetter() { "-> [Modified files (difference to overall project) - Line Coverage]: ≪Success≫ - (Actual value: +5.00%, Quality gate: 0.00)"); } + @ParameterizedTest(name = "A quality gate of {0} should not be passed if the coverage drops by 10%") + @ValueSource(ints = {8, 1, 0, -1, -8}) + void shouldHandleNegativeValues(final double minimum) { + Collection qualityGates = new ArrayList<>(); + + qualityGates.add(new CoverageQualityGate(minimum, Metric.FILE, Baseline.PROJECT_DELTA, QualityGateCriticality.UNSTABLE)); + + CoverageQualityGateEvaluator evaluator = new CoverageQualityGateEvaluator(qualityGates, createStatistics()); + QualityGateResult result = evaluator.evaluate(); + + assertThat(result).hasOverallStatus(QualityGateStatus.WARNING).isNotSuccessful().isNotInactive().hasMessages( + String.format( + "-> [Overall project (difference to reference job) - File Coverage]: ≪Unstable≫ - (Actual value: -10.00%%, Quality gate: %.2f)", minimum)); + } + + @ParameterizedTest(name = "A quality gate of {0} should be passed if the coverage is at 50%") + @ValueSource(ints = {-10, 0, 10, 50}) + void shouldPassAllThresholds(final double minimum) { + Collection qualityGates = new ArrayList<>(); + + qualityGates.add(new CoverageQualityGate(minimum, Metric.LINE, Baseline.PROJECT, QualityGateCriticality.UNSTABLE)); + + CoverageQualityGateEvaluator evaluator = new CoverageQualityGateEvaluator(qualityGates, createStatistics()); + QualityGateResult result = evaluator.evaluate(); + + assertThat(result).hasOverallStatus(QualityGateStatus.PASSED).isSuccessful().isNotInactive().hasMessages( + String.format( + "-> [Overall project - Line Coverage]: ≪Success≫ - (Actual value: 50.00%%, Quality gate: %.2f)", minimum)); + } + + @ParameterizedTest(name = "A quality gate of {0} should not be passed if the coverage is at 50%") + @ValueSource(ints = {51, 60, 70, 200}) + void shouldFailAllThresholds(final double minimum) { + Collection qualityGates = new ArrayList<>(); + + qualityGates.add(new CoverageQualityGate(minimum, Metric.LINE, Baseline.PROJECT, QualityGateCriticality.UNSTABLE)); + + CoverageQualityGateEvaluator evaluator = new CoverageQualityGateEvaluator(qualityGates, createStatistics()); + QualityGateResult result = evaluator.evaluate(); + + assertThat(result).hasOverallStatus(QualityGateStatus.WARNING).isNotSuccessful().isNotInactive().hasMessages( + String.format( + "-> [Overall project - Line Coverage]: ≪Unstable≫ - (Actual value: 50.00%%, Quality gate: %.2f)", minimum)); + } + @Test void shouldReportUnstableIfLargerThanThreshold() { Collection qualityGates = new ArrayList<>(); @@ -155,7 +202,7 @@ void shouldReportUnstableIfLargerThanThreshold() { } @Test - void shouldReportUnstableIfWorseAndSuccessIfBetter2() { + void shouldReportUnstableIfWorseAndSuccessIfLargerThanThreshold() { Collection qualityGates = new ArrayList<>(); var minimum = 0;