From d72b9cd73e9d34a2be53cba7fe95a01cce18ee89 Mon Sep 17 00:00:00 2001 From: =?utf8?q?L=C3=A9o=20Geoffroy?= Date: Mon, 12 Aug 2024 15:20:00 +0200 Subject: [PATCH] SONAR-22727 Manage new metrics in the live update --- .../java/org/sonar/db/issue/IssueDaoIT.java | 34 +- .../sonar/db/issue/IssueImpactGroupDto.java | 9 + .../org/sonar/db/issue/IssueMapper.xml | 4 +- server/sonar-webserver-webapi/build.gradle | 2 + .../server/measure/live/IssueCounter.java | 61 ++ .../live/MeasureUpdateFormulaFactoryImpl.java | 145 ++++- .../MeasureUpdateFormulaFactoryImplTest.java | 565 +++++++++++++----- 7 files changed, 631 insertions(+), 189 deletions(-) diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java index 29c6932e53f..6d6a2e9bd20 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java @@ -223,8 +223,8 @@ class IssueDaoIT { ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent(); RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("java").setLanguage("java") .replaceAllDefaultImpacts(List.of(new ImpactDto() - .setSoftwareQuality(MAINTAINABILITY) - .setSeverity(MEDIUM), + .setSoftwareQuality(MAINTAINABILITY) + .setSeverity(MEDIUM), new ImpactDto() .setSoftwareQuality(RELIABILITY) .setSeverity(LOW)))); @@ -343,7 +343,7 @@ class IssueDaoIT { IntStream.range(0, statusesB.size()).forEach(i -> insertBranchIssue(branchB, fileB, rule, "B" + i, statusesB.get(i), updatedAt)); List branchAIssuesA1 = underTest.selectByBranch(db.getSession(), Set.of("issueA0", "issueA1", "issueA3", - "issueWithResolution"), + "issueWithResolution"), buildSelectByBranchQuery(branchA, false, changedSince)); assertThat(branchAIssuesA1) @@ -565,25 +565,25 @@ class IssueDaoIT { } @ParameterizedTest - @ValueSource(booleans = {true, false}) + @ValueSource(booleans = {true, false}) void selectIssueImpactGroupsByComponent_shouldReturnImpactGroups(boolean inLeak) { ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project)); RuleDto rule = db.rules().insert(); db.issues().insert(rule, project, file, - i -> i.setStatus(STATUS_OPEN).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, LOW)))); + i -> i.setStatus(STATUS_OPEN).setEffort(60L).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, LOW)))); db.issues().insert(rule, project, file, - i -> i.setStatus(STATUS_OPEN).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, HIGH)))); + i -> i.setStatus(STATUS_OPEN).setEffort(60L).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, HIGH)))); db.issues().insert(rule, project, file, - i -> i.setStatus(STATUS_REOPENED).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); + i -> i.setStatus(STATUS_REOPENED).setEffort(60L).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); db.issues().insert(rule, project, file, - i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); + i -> i.setStatus(STATUS_RESOLVED).setEffort(60L).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); // issues in ignored status db.issues().insert(rule, project, file, - i -> i.setStatus(Issue.STATUS_CLOSED).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); + i -> i.setStatus(Issue.STATUS_CLOSED).setEffort(60L).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); db.issues().insert(rule, project, file, - i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); + i -> i.setStatus(STATUS_RESOLVED).setEffort(60L).setResolution(RESOLUTION_FALSE_POSITIVE).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); Collection result = underTest.selectIssueImpactGroupsByComponent(db.getSession(), file, inLeak ? 1L : Long.MAX_VALUE); @@ -594,6 +594,9 @@ class IssueDaoIT { assertThat(result.stream().filter(g -> g.getSoftwareQuality() == SECURITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(4); assertThat(result.stream().filter(g -> g.getSoftwareQuality() == MAINTAINABILITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2); + assertThat(result.stream().filter(g -> g.getSoftwareQuality() == SECURITY).mapToDouble(IssueImpactGroupDto::getEffort).sum()).isEqualTo(4 * 60); + assertThat(result.stream().filter(g -> g.getSoftwareQuality() == MAINTAINABILITY).mapToDouble(IssueImpactGroupDto::getEffort).sum()).isEqualTo(2 * 60); + assertThat(result.stream().filter(g -> g.getSeverity() == HIGH).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(5); assertThat(result.stream().filter(g -> g.getSeverity() == LOW).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne(); @@ -618,9 +621,9 @@ class IssueDaoIT { ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project)); RuleDto rule = db.rules().insert(); IssueDto issueInNewCodePeriod = db.issues().insert(rule, project, file, - i -> i.setStatus(STATUS_OPEN).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, LOW)))); + i -> i.setStatus(STATUS_OPEN).setEffort(1L).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, LOW)))); db.issues().insert(rule, project, file, - i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); + i -> i.setStatus(STATUS_RESOLVED).setEffort(2L).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH)))); db.issues().insertNewCodeReferenceIssue(issueInNewCodePeriod); @@ -633,6 +636,10 @@ class IssueDaoIT { assertThat(result.stream().filter(g -> g.getSoftwareQuality() == SECURITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.getSoftwareQuality() == MAINTAINABILITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(1); + assertThat(result.stream().filter(g -> g.getSoftwareQuality() == SECURITY && g.isInLeak()).mapToDouble(IssueImpactGroupDto::getEffort).sum()).isEqualTo(1d); + assertThat(result.stream().filter(g -> g.getSoftwareQuality() == MAINTAINABILITY && g.isInLeak()).mapToDouble(IssueImpactGroupDto::getEffort).sum()).isEqualTo(1d); + assertThat(result.stream().filter(g -> g.getSoftwareQuality() == SECURITY && !g.isInLeak()).mapToDouble(IssueImpactGroupDto::getEffort).sum()).isEqualTo(2d); + assertThat(result.stream().filter(g -> g.getSeverity() == HIGH).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.getSeverity() == LOW).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne(); @@ -642,7 +649,6 @@ class IssueDaoIT { assertThat(result.stream().filter(g -> RESOLUTION_WONT_FIX.equals(g.getResolution())).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne(); - assertThat(result.stream().filter(IssueImpactGroupDto::isInLeak).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2); assertThat(result.stream().filter(g -> g.isInLeak() && g.getSoftwareQuality() == SECURITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(1); assertThat(result.stream().filter(g -> g.isInLeak() && g.getSoftwareQuality() == MAINTAINABILITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(1); @@ -1017,7 +1023,7 @@ class IssueDaoIT { underTest.updateIfBeforeSelectedDate(db.getSession(), issueDto); assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).getImpacts()).extracting(i -> i.getSoftwareQuality(), - i -> i.getSeverity()) + i -> i.getSeverity()) .containsExactlyInAnyOrder(tuple(RELIABILITY, MEDIUM), tuple(SECURITY, LOW)); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactGroupDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactGroupDto.java index dbf05f35843..7326fb3072f 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactGroupDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueImpactGroupDto.java @@ -32,6 +32,7 @@ public class IssueImpactGroupDto { private Severity severity; private long count; private boolean inLeak; + private double effort; public IssueImpactGroupDto() { // nothing to do @@ -85,4 +86,12 @@ public class IssueImpactGroupDto { public void setInLeak(boolean inLeak) { this.inLeak = inLeak; } + + public double getEffort() { + return effort; + } + + public void setEffort(double effort) { + this.effort = effort; + } } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml index 3d70d0aeba2..422e50a7922 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml @@ -529,7 +529,8 @@ i2.software_quality as softwareQuality, i2.severity as severity, count(i2.kee) as "count", - i2.inLeak as inLeak + i2.inLeak as inLeak, + sum(i2.effort) as effort from ( select i.status, @@ -537,6 +538,7 @@ i.kee, ii.software_quality, ii.severity, + i.effort, case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then ${_true} else ${_false} end as inLeak diff --git a/server/sonar-webserver-webapi/build.gradle b/server/sonar-webserver-webapi/build.gradle index f8fa9990bbe..d532a1c888c 100644 --- a/server/sonar-webserver-webapi/build.gradle +++ b/server/sonar-webserver-webapi/build.gradle @@ -39,6 +39,7 @@ dependencies { testImplementation 'com.squareup.okhttp3:mockwebserver' testImplementation 'jakarta.servlet:jakarta.servlet-api' testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures' @@ -53,6 +54,7 @@ dependencies { testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java index 76d7da9d867..c7c0fbb34ec 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java @@ -24,6 +24,7 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.issue.IssueStatus; import org.sonar.api.issue.impact.Severity; @@ -50,6 +51,8 @@ class IssueCounter { private long prioritizedRuleIssues = 0; private final Count highImpactAccepted = new Count(); private final Map> bySoftwareQualityAndSeverity = new EnumMap<>(SoftwareQuality.class); + private final Map effortOfUnresolvedBySoftwareQuality = new EnumMap<>(SoftwareQuality.class); + private final Map highestSeverityOfUnresolvedBySoftwareQuality = new EnumMap<>(SoftwareQuality.class); IssueCounter(Collection groups, Collection impactGroups) { for (IssueGroupDto group : groups) { @@ -117,6 +120,16 @@ class IssueCounter { .add(group); } + if (group.getResolution() == null) { + effortOfUnresolvedBySoftwareQuality + .computeIfAbsent(group.getSoftwareQuality(), k -> new Effort()) + .add(group); + + highestSeverityOfUnresolvedBySoftwareQuality + .computeIfAbsent(group.getSoftwareQuality(), k -> new HighestImpactSeverity()) + .add(group); + } + if (Severity.HIGH == group.getSeverity() && IssueStatus.ACCEPTED == issueStatus) { highImpactAccepted.add(group); } @@ -127,6 +140,11 @@ class IssueCounter { .map(hs -> hs.severity(onlyInLeak)); } + public Optional getHighestSeverityOfUnresolved(SoftwareQuality softwareQuality, boolean onlyInLeak) { + return Optional.ofNullable(highestSeverityOfUnresolvedBySoftwareQuality.get(softwareQuality)) + .map(hs -> hs.severity(onlyInLeak)); + } + public double sumEffortOfUnresolved(RuleType type, boolean onlyInLeak) { Effort effort = effortOfUnresolved.get(type); if (effort == null) { @@ -135,6 +153,14 @@ class IssueCounter { return onlyInLeak ? effort.leak : effort.absolute; } + public double sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality softwareQuality, boolean onlyInLeak) { + Effort effort = effortOfUnresolvedBySoftwareQuality.get(softwareQuality); + if (effort == null) { + return 0.0; + } + return onlyInLeak ? effort.leak : effort.absolute; + } + public long countUnresolvedBySeverity(String severity, boolean onlyInLeak) { return value(unresolvedBySeverity.get(severity), onlyInLeak); } @@ -220,6 +246,13 @@ class IssueCounter { leak += group.getEffort(); } } + + void add(IssueImpactGroupDto group) { + absolute += group.getEffort(); + if (group.isInLeak()) { + leak += group.getEffort(); + } + } } private static class HighestSeverity { @@ -238,4 +271,32 @@ class IssueCounter { return SeverityUtil.getSeverityFromOrdinal(inLeak ? leak : absolute); } } + + private static class HighestImpactSeverity { + private Severity absolute = null; + private Severity leak = null; + + void add(IssueImpactGroupDto group) { + absolute = getMaxSeverity(absolute, group.getSeverity()); + if (group.isInLeak()) { + leak = getMaxSeverity(leak, group.getSeverity()); + } + } + + Severity getMaxSeverity(@Nullable Severity currentSeverity, Severity newSeverity) { + if (currentSeverity == null) { + return newSeverity; + } + if (newSeverity.ordinal() > currentSeverity.ordinal()) { + return newSeverity; + } else { + return currentSeverity; + } + } + + @CheckForNull + Severity severity(boolean inLeak) { + return inLeak ? leak : absolute; + } + } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java index 61f1b6595f4..3f5871bd5fd 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java @@ -41,8 +41,28 @@ import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED; import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS; import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS; +import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING; import static org.sonar.server.measure.Rating.RATING_BY_SEVERITY; +import static org.sonar.server.measure.Rating.RATING_BY_SOFTWARE_QUALITY_SEVERITY; import static org.sonar.server.metric.IssueCountMetrics.PRIORITIZED_RULE_ISSUES; +import static org.sonar.server.security.SecurityReviewRating.computeAToDRating; import static org.sonar.server.security.SecurityReviewRating.computePercent; import static org.sonar.server.security.SecurityReviewRating.computeRating; @@ -127,18 +147,19 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.VULNERABILITY, false))), new MeasureUpdateFormula(CoreMetrics.SQALE_DEBT_RATIO, false, false, - (context, formula) -> context.setValue(100.0 * debtDensity(context)), - (context, issues) -> context.setValue(100.0 * debtDensity(context)), + (context, formula) -> context.setValue(100.0 * debtDensity(TECHNICAL_DEBT, context)), + (context, issues) -> context.setValue(100.0 * debtDensity(TECHNICAL_DEBT, context)), asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)), new MeasureUpdateFormula(CoreMetrics.SQALE_RATING, false, false, - (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(context))), - (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(TECHNICAL_DEBT, context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(TECHNICAL_DEBT, context))), asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)), new MeasureUpdateFormula(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, false, false, - (context, formula) -> context.setValue(effortToReachMaintainabilityRatingA(context)), - (context, issues) -> context.setValue(effortToReachMaintainabilityRatingA(context)), asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)), + (context, formula) -> context.setValue(effortToReachMaintainabilityRatingA(CoreMetrics.TECHNICAL_DEBT, context)), + (context, issues) -> context.setValue(effortToReachMaintainabilityRatingA(CoreMetrics.TECHNICAL_DEBT, context)), + asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)), new MeasureUpdateFormula(CoreMetrics.RELIABILITY_RATING, false, new MaxRatingChildren(), (context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.BUG, false).orElse(Severity.INFO)))), @@ -251,19 +272,109 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact }), new MeasureUpdateFormula(CoreMetrics.NEW_SQALE_DEBT_RATIO, true, false, - (context, formula) -> context.setValue(100.0D * newDebtDensity(context)), - (context, issues) -> context.setValue(100.0D * newDebtDensity(context)), + (context, formula) -> context.setValue(100.0D * newDebtDensity(CoreMetrics.NEW_TECHNICAL_DEBT, context)), + (context, issues) -> context.setValue(100.0D * newDebtDensity(CoreMetrics.NEW_TECHNICAL_DEBT, context)), asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)), new MeasureUpdateFormula(CoreMetrics.NEW_MAINTAINABILITY_RATING, true, false, - (context, formula) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(context))), - (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(context))), - asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST))); + (context, formula) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(CoreMetrics.NEW_TECHNICAL_DEBT, context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(CoreMetrics.NEW_TECHNICAL_DEBT, context))), + asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)), + + // Metrics based on Software Qualities + new MeasureUpdateFormula(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, false, true, new AddChildren(), + (context, issues) -> context.setValue(issues.sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, false))), + + new MeasureUpdateFormula(SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, false, true, new AddChildren(), + (context, issues) -> context.setValue(issues.sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality.RELIABILITY, false))), + + new MeasureUpdateFormula(SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, false, true, new AddChildren(), + (context, issues) -> context.setValue(issues.sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality.SECURITY, false))), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, true, true, new AddChildren(), + (context, issues) -> context.setValue(issues.sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality.MAINTAINABILITY, true))), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, true, true, new AddChildren(), + (context, issues) -> context.setValue(issues.sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality.RELIABILITY, true))), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, true, true, new AddChildren(), + (context, issues) -> context.setValue(issues.sumEffortOfUnresolvedBySoftwareQuality(SoftwareQuality.SECURITY, true))), + + new MeasureUpdateFormula(SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, false, true, + (context, formula) -> context.setValue(100.0 * debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context)), + (context, issues) -> context.setValue(100.0 * debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context)), + asList(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.DEVELOPMENT_COST)), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, true, true, + (context, formula) -> context.setValue(100.0D * newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context)), + (context, issues) -> context.setValue(100.0D * newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context)), + asList(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.NEW_DEVELOPMENT_COST)), + + new MeasureUpdateFormula(SOFTWARE_QUALITY_MAINTAINABILITY_RATING, false, true, + (context, issues) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(debtDensity(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + asList(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.DEVELOPMENT_COST)), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING, true, true, + (context, formula) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + (context, issues) -> context.setValue(context.getDebtRatingGrid().getAToDRatingForDensity(newDebtDensity(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context))), + asList(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.NEW_DEVELOPMENT_COST)), + + new MeasureUpdateFormula(EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A, false, true, + (context, formula) -> context.setValue(effortToReachMaintainabilityRatingA(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context)), + (context, issues) -> context.setValue(effortToReachMaintainabilityRatingA(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, context)), + asList(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, CoreMetrics.DEVELOPMENT_COST)), + + new MeasureUpdateFormula(SOFTWARE_QUALITY_RELIABILITY_RATING, false, true, new MaxRatingChildren(), + (context, issues) -> { + Rating rating = issues.getHighestSeverityOfUnresolved(SoftwareQuality.RELIABILITY, false) + .map(RATING_BY_SOFTWARE_QUALITY_SEVERITY::get) + .orElse(Rating.A); + context.setValue(rating); + }), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, true, true, new MaxRatingChildren(), + (context, issues) -> { + Rating rating = issues.getHighestSeverityOfUnresolved(SoftwareQuality.RELIABILITY, true) + .map(RATING_BY_SOFTWARE_QUALITY_SEVERITY::get) + .orElse(Rating.A); + context.setValue(rating); + }), + + new MeasureUpdateFormula(SOFTWARE_QUALITY_SECURITY_RATING, false, true, new MaxRatingChildren(), + (context, issues) -> { + Rating rating = issues.getHighestSeverityOfUnresolved(SoftwareQuality.SECURITY, false) + .map(RATING_BY_SOFTWARE_QUALITY_SEVERITY::get) + .orElse(Rating.A); + context.setValue(rating); + }), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_SECURITY_RATING, true, true, new MaxRatingChildren(), + (context, issues) -> { + Rating rating = issues.getHighestSeverityOfUnresolved(SoftwareQuality.SECURITY, true) + .map(RATING_BY_SOFTWARE_QUALITY_SEVERITY::get) + .orElse(Rating.A); + context.setValue(rating); + }), + + new MeasureUpdateFormula(SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, false, true, + (context, formula) -> context.setValue(computeAToDRating(context.getValue(SECURITY_HOTSPOTS_REVIEWED).orElse(null))), + (context, issues) -> { + Optional percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false)); + context.setValue(computeAToDRating(percent.orElse(null))); + }), + + new MeasureUpdateFormula(NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, true, true, + (context, formula) -> context.setValue(computeAToDRating(context.getValue(NEW_SECURITY_HOTSPOTS_REVIEWED).orElse(null))), + (context, issues) -> { + Optional percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true)); + context.setValue(computeAToDRating(percent.orElse(null))); + })); private static final Set FORMULA_METRICS = MeasureUpdateFormulaFactory.extractMetrics(FORMULAS); - private static double debtDensity(MeasureUpdateFormula.Context context) { - double debt = Math.max(context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0D), 0.0D); + private static double debtDensity(Metric maintainabilityRemediationEffortMetric, MeasureUpdateFormula.Context context) { + double debt = Math.max(context.getValue(maintainabilityRemediationEffortMetric).orElse(0.0D), 0.0D); Optional devCost = context.getText(CoreMetrics.DEVELOPMENT_COST).map(Double::parseDouble); if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) { return debt / devCost.get(); @@ -271,8 +382,8 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact return 0.0D; } - private static double newDebtDensity(MeasureUpdateFormula.Context context) { - double debt = Math.max(context.getValue(CoreMetrics.NEW_TECHNICAL_DEBT).orElse(0.0D), 0.0D); + private static double newDebtDensity(Metric maintainabilityRemediationEffortMetric, MeasureUpdateFormula.Context context) { + double debt = Math.max(context.getValue(maintainabilityRemediationEffortMetric).orElse(0.0D), 0.0D); Optional devCost = context.getValue(CoreMetrics.NEW_DEVELOPMENT_COST); if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) { return debt / devCost.get(); @@ -280,9 +391,9 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact return 0.0D; } - private static double effortToReachMaintainabilityRatingA(MeasureUpdateFormula.Context context) { + private static double effortToReachMaintainabilityRatingA(Metric maintainabilityRemediationEffortMetric, MeasureUpdateFormula.Context context) { double developmentCost = context.getText(CoreMetrics.DEVELOPMENT_COST).map(Double::parseDouble).orElse(0.0D); - double effort = context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0D); + double effort = context.getValue(maintainabilityRemediationEffortMetric).orElse(0.0D); double upperGradeCost = context.getDebtRatingGrid().getGradeLowerBound(Rating.B) * developmentCost; return upperGradeCost < effort ? (effort - upperGradeCost) : 0.0D; } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java index a6f5746718e..1eb1e92a377 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java @@ -30,14 +30,19 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import javax.annotation.Nullable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.sonar.api.issue.Issue; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric; import org.sonar.api.rule.Severity; import org.sonar.api.rules.RuleType; +import org.sonar.core.metric.SoftwareQualitiesMetrics; import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueGroupDto; import org.sonar.db.issue.IssueImpactGroupDto; @@ -46,12 +51,15 @@ import org.sonar.server.measure.Rating; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.sonar.api.issue.impact.Severity.HIGH; import static org.sonar.api.issue.impact.Severity.LOW; import static org.sonar.api.issue.impact.Severity.MEDIUM; import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY; import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY; +import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A; +import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS; @@ -60,6 +68,11 @@ import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED; import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS; import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS; import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING; +import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO; +import static org.sonar.api.measures.CoreMetrics.SQALE_RATING; +import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY; +import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT; +import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT; import static org.sonar.server.metric.IssueCountMetrics.PRIORITIZED_RULE_ISSUES; import static org.sonar.test.JsonAssert.assertJson; @@ -152,6 +165,75 @@ class MeasureUpdateFormulaFactoryImplTest { .expectedRating(Rating.E); } + @Test + void computeHierarchy_shouldRecomputeSoftwareQualityMetricsCombiningOtherMetrics() { + new HierarchyTester(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING) + .withValue(SECURITY_HOTSPOTS_REVIEWED, 0d) + .expectedRating(Rating.D); + new HierarchyTester(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING) + .withValue(NEW_SECURITY_HOTSPOTS_REVIEWED, 0d) + .expectedRating(Rating.D); + new HierarchyTester(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO) + .withValue(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) + .withValue(CoreMetrics.DEVELOPMENT_COST, "40") + .expectedResult(25d); + new HierarchyTester(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO) + .withValue(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) + .withValue(CoreMetrics.NEW_DEVELOPMENT_COST, 40d) + .expectedResult(25d); + + new HierarchyTester(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING) + .withValue(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) + .withValue(CoreMetrics.DEVELOPMENT_COST, "40") + .expectedRating(Rating.D); + + new HierarchyTester(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING) + .withValue(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) + .withValue(CoreMetrics.NEW_DEVELOPMENT_COST, 40d) + .expectedRating(Rating.D); + + new HierarchyTester(SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A) + .withValue(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 10d) + .withValue(CoreMetrics.DEVELOPMENT_COST, "40") + .expectedResult(8d); + } + + static List> softwareQualityRatingMetric() { + return List.of( + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, + SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, + SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING); + } + + @MethodSource("softwareQualityRatingMetric") + @ParameterizedTest + void computeHierarchy_shoudRecomputeSoftwareQualityMetricsBasedOnRating(Metric ratingMetric) { + new HierarchyTester(ratingMetric) + .withValue(1d) + .withChildrenValues(2d, 3d) + .expectedRating(Rating.C); + } + + static List> softwareQualitySummingMetric() { + return List.of( + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT); + } + + @MethodSource("softwareQualitySummingMetric") + @ParameterizedTest + void computeHierarchy_shoudRecomputeSoftwareQualityMetricsBasedOnSumming(Metric summingMetric) { + new HierarchyTester(summingMetric) + .withValue(1d) + .withChildrenValues(2d, 3d) + .expectedResult(6d); + } + @Test void test_violations() { withNoIssues().assertThatValueIs(CoreMetrics.VIOLATIONS, 0); @@ -182,7 +264,7 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.BUG).setCount(7), // not bugs newGroup(RuleType.CODE_SMELL).setCount(11)) - .assertThatValueIs(CoreMetrics.BUGS, 3 + 5); + .assertThatValueIs(CoreMetrics.BUGS, 3 + 5); } @Test @@ -195,7 +277,7 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.CODE_SMELL).setCount(7), // not code smells newGroup(RuleType.BUG).setCount(11)) - .assertThatValueIs(CoreMetrics.CODE_SMELLS, 3 + 5); + .assertThatValueIs(CoreMetrics.CODE_SMELLS, 3 + 5); } @Test @@ -208,7 +290,7 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.VULNERABILITY).setCount(7), // not vulnerabilities newGroup(RuleType.BUG).setCount(11)) - .assertThatValueIs(CoreMetrics.VULNERABILITIES, 3 + 5); + .assertThatValueIs(CoreMetrics.VULNERABILITIES, 3 + 5); } @Test @@ -221,18 +303,26 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.SECURITY_HOTSPOT).setCount(7), // not hotspots newGroup(RuleType.BUG).setCount(11)) - .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 3 + 5); + .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 3 + 5); } @Test - void test_security_review_rating() { + void test_security_review_ratings() { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.B); + .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.B) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.B); withNoIssues() - .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.A); + .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.A) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.A); + + with( + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(3).setInLeak(true), + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true)) + .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.E) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.D); } @Test @@ -240,7 +330,7 @@ class MeasureUpdateFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(SECURITY_HOTSPOTS_REVIEWED, 75.0); + .assertThatValueIs(SECURITY_HOTSPOTS_REVIEWED, 75.0); withNoIssues() .assertNoValue(SECURITY_HOTSPOTS_REVIEWED); @@ -251,7 +341,7 @@ class MeasureUpdateFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); + .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); withNoIssues() .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0); @@ -262,7 +352,7 @@ class MeasureUpdateFormulaFactoryImplTest { with( newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3), newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1)) - .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); + .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); withNoIssues() .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0); @@ -290,11 +380,11 @@ class MeasureUpdateFormulaFactoryImplTest { newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(17), newResolvedGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(19), newResolvedGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.INFO).setCount(21)) - .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 11 + 13) - .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 7) - .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 3 + 5) - .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0) - .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0); + .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 11 + 13) + .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 7) + .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 3 + 5) + .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0) + .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0); } @Test @@ -314,8 +404,8 @@ class MeasureUpdateFormulaFactoryImplTest { // exclude unresolved newGroup(RuleType.VULNERABILITY).setCount(17), newGroup(RuleType.BUG).setCount(19)) - .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 5) - .assertThatValueIs(CoreMetrics.ACCEPTED_ISSUES, 7 + 11); + .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 5) + .assertThatValueIs(CoreMetrics.ACCEPTED_ISSUES, 7 + 11); } @Test @@ -334,9 +424,9 @@ class MeasureUpdateFormulaFactoryImplTest { // exclude security hotspot newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN).setCount(12), newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(13)) - .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 3 + 5) - .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 9 + 11) - .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7); + .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 3 + 5) + .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 9 + 11) + .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7); } @Test @@ -353,7 +443,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setEffort(7.0), // exclude resolved newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0)) - .assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 3.0 + 5.0); + .assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 3.0 + 5.0); } @Test @@ -367,7 +457,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0), // exclude resolved newResolvedGroup(RuleType.BUG).setEffort(17.0)) - .assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 3.0 + 5.0); + .assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 3.0 + 5.0); } @Test @@ -381,122 +471,142 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0), // exclude resolved newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0)) - .assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 3.0 + 5.0); + .assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 3.0 + 5.0); } - @Test - void test_sqale_debt_ratio_and_sqale_rating() { + private static Stream maintainabilityMetrics() { + return Stream.of( + arguments(TECHNICAL_DEBT, SQALE_DEBT_RATIO, SQALE_RATING), + arguments(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING)); + } + + @ParameterizedTest + @MethodSource("maintainabilityMetrics") + void test_sqale_debt_ratio_and_sqale_rating(Metric maintainabilityRemediationEffortMetric, Metric maintainabilityDebtRatioMetric, Metric maintainabilityRatingMetric) { withNoIssues() - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); // technical_debt not computed with(CoreMetrics.DEVELOPMENT_COST, "0") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); with(CoreMetrics.DEVELOPMENT_COST, "20") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); // development_cost not computed - with(CoreMetrics.TECHNICAL_DEBT, 0) - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); - with(CoreMetrics.TECHNICAL_DEBT, 20) - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + with(maintainabilityRemediationEffortMetric, 0) + .assertThatValueIs(maintainabilityDebtRatioMetric, 0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); + with(maintainabilityRemediationEffortMetric, 20) + .assertThatValueIs(maintainabilityDebtRatioMetric, 0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); // input measures are available - with(CoreMetrics.TECHNICAL_DEBT, 20.0) + with(maintainabilityRemediationEffortMetric, 20.0) .andText(CoreMetrics.DEVELOPMENT_COST, "0") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0.0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); - with(CoreMetrics.TECHNICAL_DEBT, 20.0) + with(maintainabilityRemediationEffortMetric, 20.0) .andText(CoreMetrics.DEVELOPMENT_COST, "160") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 12.5) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.C); + .assertThatValueIs(maintainabilityDebtRatioMetric, 12.5) + .assertThatValueIs(maintainabilityRatingMetric, Rating.C); - with(CoreMetrics.TECHNICAL_DEBT, 20.0) + Verifier verifier = with(maintainabilityRemediationEffortMetric, 20.0) .andText(CoreMetrics.DEVELOPMENT_COST, "10") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 200.0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.E); + .assertThatValueIs(maintainabilityDebtRatioMetric, 200.0); + switch (maintainabilityRatingMetric.key()) { + case SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY -> verifier.assertThatValueIs(maintainabilityRatingMetric, Rating.D); + case SQALE_RATING_KEY -> verifier.assertThatValueIs(maintainabilityRatingMetric, Rating.E); + default -> throw new IllegalArgumentException("Unexpected metric: " + maintainabilityRatingMetric.key()); + } // A is 5% --> min debt is exactly 200*0.05=10 with(CoreMetrics.DEVELOPMENT_COST, "200") - .and(CoreMetrics.TECHNICAL_DEBT, 10.0) - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 5.0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .and(maintainabilityRemediationEffortMetric, 10.0) + .assertThatValueIs(maintainabilityDebtRatioMetric, 5.0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); - with(CoreMetrics.TECHNICAL_DEBT, 0.0) + with(maintainabilityRemediationEffortMetric, 0.0) .andText(CoreMetrics.DEVELOPMENT_COST, "0") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0.0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); - with(CoreMetrics.TECHNICAL_DEBT, 0.0) + with(maintainabilityRemediationEffortMetric, 0.0) .andText(CoreMetrics.DEVELOPMENT_COST, "80") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0.0); - with(CoreMetrics.TECHNICAL_DEBT, -20.0) + with(maintainabilityRemediationEffortMetric, -20.0) .andText(CoreMetrics.DEVELOPMENT_COST, "0") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0.0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); // bug, debt can't be negative - with(CoreMetrics.TECHNICAL_DEBT, -20.0) + with(maintainabilityRemediationEffortMetric, -20.0) .andText(CoreMetrics.DEVELOPMENT_COST, "80") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0.0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); // bug, cost can't be negative - with(CoreMetrics.TECHNICAL_DEBT, 20.0) + with(maintainabilityRemediationEffortMetric, 20.0) .andText(CoreMetrics.DEVELOPMENT_COST, "-80") - .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0) - .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A); + .assertThatValueIs(maintainabilityDebtRatioMetric, 0.0) + .assertThatValueIs(maintainabilityRatingMetric, Rating.A); } - @Test - void test_effort_to_reach_maintainability_rating_A() { + private static Stream effortToReachAMetrics() { + return Stream.of( + arguments(TECHNICAL_DEBT, EFFORT_TO_REACH_MAINTAINABILITY_RATING_A), + arguments(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A)); + } + + @ParameterizedTest + @MethodSource("effortToReachAMetrics") + void test_effort_to_reach_maintainability_rating_A(Metric maintainabilityRemediationEffortMetric, Metric effortToReachAMetric) { withNoIssues() - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0); + .assertThatValueIs(effortToReachAMetric, 0.0); // technical_debt not computed with(CoreMetrics.DEVELOPMENT_COST, 0.0) - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0); + .assertThatValueIs(effortToReachAMetric, 0.0); with(CoreMetrics.DEVELOPMENT_COST, 20.0) - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0); + .assertThatValueIs(effortToReachAMetric, 0.0); // development_cost not computed - with(CoreMetrics.TECHNICAL_DEBT, 0.0) - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0); - with(CoreMetrics.TECHNICAL_DEBT, 20.0) + with(maintainabilityRemediationEffortMetric, 0.0) + .assertThatValueIs(effortToReachAMetric, 0.0); + with(maintainabilityRemediationEffortMetric, 20.0) // development cost is considered as zero, so the effort is to reach... zero - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 20.0); + .assertThatValueIs(effortToReachAMetric, 20.0); // B to A with(CoreMetrics.DEVELOPMENT_COST, "200") - .and(CoreMetrics.TECHNICAL_DEBT, 40.0) + .and(maintainabilityRemediationEffortMetric, 40.0) // B is 5% --> goal is to reach 200*0.05=10 --> effort is 40-10=30 - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 40.0 - (200.0 * 0.05)); + .assertThatValueIs(effortToReachAMetric, 40.0 - (200.0 * 0.05)); // E to A with(CoreMetrics.DEVELOPMENT_COST, "200") - .and(CoreMetrics.TECHNICAL_DEBT, 180.0) + .and(maintainabilityRemediationEffortMetric, 180.0) // B is 5% --> goal is to reach 200*0.05=10 --> effort is 180-10=170 - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 180.0 - (200.0 * 0.05)); + .assertThatValueIs(effortToReachAMetric, 180.0 - (200.0 * 0.05)); // already A with(CoreMetrics.DEVELOPMENT_COST, "200") - .and(CoreMetrics.TECHNICAL_DEBT, 8.0) + .and(maintainabilityRemediationEffortMetric, 8.0) // B is 5% --> goal is to reach 200*0.05=10 --> debt is already at 8 --> effort to reach A is zero - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0); + .assertThatValueIs(effortToReachAMetric, 0.0); // exactly lower range of B with(CoreMetrics.DEVELOPMENT_COST, "200") - .and(CoreMetrics.TECHNICAL_DEBT, 10.0) + .and(maintainabilityRemediationEffortMetric, 10.0) // B is 5% --> goal is to reach 200*0.05=10 --> debt is 10 --> effort to reach A is zero // FIXME need zero to reach A but effective rating is B ! - .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0); + .assertThatValueIs(effortToReachAMetric, 0.0); } @Test @@ -509,14 +619,50 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(5), // excluded, not a bug newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3)) - // highest severity of bugs is CRITICAL --> D - .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.D); + // highest severity of bugs is CRITICAL --> D + .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.D); with( newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3), newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5)) - // no bugs --> A - .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A); + // no bugs --> A + .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A); + } + + @Test + void test_software_quality_reliability_rating() { + withNoIssues() + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.A); + + with( + newImpactGroup(MAINTAINABILITY, HIGH, 1), + newImpactGroup(RELIABILITY, MEDIUM, 1), + newImpactGroup(RELIABILITY, LOW, 1), + newImpactGroup(SECURITY, MEDIUM, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.C); + + with( + newImpactGroup(RELIABILITY, LOW, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.B); + } + + @Test + void test_software_quality_new_reliability_rating() { + withNoIssues() + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.A); + + with( + newImpactGroup(MAINTAINABILITY, HIGH, 1, true), + newImpactGroup(RELIABILITY, MEDIUM, 1, true), + newImpactGroup(RELIABILITY, LOW, 1, true), + newImpactGroup(RELIABILITY, HIGH, 1, false), + newImpactGroup(SECURITY, HIGH, 1, true)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.C); + + with( + newImpactGroup(RELIABILITY, LOW, 1, true), + newImpactGroup(RELIABILITY, MEDIUM, 1, false)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING, Rating.B); } @Test @@ -529,14 +675,50 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(5), // excluded, not a vulnerability newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3)) - // highest severity of vulnerabilities is CRITICAL --> D - .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.D); + // highest severity of vulnerabilities is CRITICAL --> D + .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.D); with( newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3), newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5)) - // no vulnerabilities --> A - .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A); + // no vulnerabilities --> A + .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A); + } + + @Test + void test_software_quality_security_rating() { + withNoIssues() + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.A); + + with( + newImpactGroup(MAINTAINABILITY, HIGH, 1), + newImpactGroup(SECURITY, MEDIUM, 1), + newImpactGroup(SECURITY, LOW, 1), + newImpactGroup(RELIABILITY, MEDIUM, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.C); + + with( + newImpactGroup(SECURITY, LOW, 1)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING, Rating.B); + } + + @Test + void test_software_quality_new_security_rating() { + withNoIssues() + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.A); + + with( + newImpactGroup(MAINTAINABILITY, HIGH, 1, true), + newImpactGroup(SECURITY, MEDIUM, 1, true), + newImpactGroup(SECURITY, LOW, 1, true), + newImpactGroup(SECURITY, HIGH, 1, false), + newImpactGroup(RELIABILITY, HIGH, 1, true)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.C); + + with( + newImpactGroup(SECURITY, LOW, 1, true), + newImpactGroup(SECURITY, MEDIUM, 1, false)) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING, Rating.B); } @Test @@ -550,7 +732,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not bugs newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(9), newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 5 + 7); } @@ -565,7 +747,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not code smells newGroup(RuleType.BUG).setInLeak(true).setCount(9), newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 5 + 7); } @Test @@ -579,7 +761,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not vulnerabilities newGroup(RuleType.BUG).setInLeak(true).setCount(9), newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 5 + 7); } @Test @@ -593,7 +775,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not hotspots newGroup(RuleType.BUG).setInLeak(true).setCount(9), newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 5 + 7); } @Test @@ -608,7 +790,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setInLeak(false).setCount(11), newGroup(RuleType.CODE_SMELL).setInLeak(false).setCount(13), newGroup(RuleType.VULNERABILITY).setInLeak(false).setCount(17)) - .assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 5 + 7 + 9); + .assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 5 + 7 + 9); } @Test @@ -625,7 +807,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 3 + 5 + 7); } @Test @@ -642,7 +824,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 3 + 5 + 7); } @Test @@ -659,7 +841,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 3 + 5 + 7); } @Test @@ -676,7 +858,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 3 + 5 + 7); } @Test @@ -693,7 +875,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(false).setCount(11), newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(false).setCount(13)) - .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7); + .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7); } @Test @@ -709,7 +891,7 @@ class MeasureUpdateFormulaFactoryImplTest { // not in leak newGroup(RuleType.CODE_SMELL).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(false).setCount(5), newGroup(RuleType.BUG).setResolution(Issue.RESOLUTION_WONT_FIX).setInLeak(false).setCount(50)) - .assertThatLeakValueIs(CoreMetrics.NEW_ACCEPTED_ISSUES, 4 + 40); + .assertThatLeakValueIs(CoreMetrics.NEW_ACCEPTED_ISSUES, 4 + 40); } @Test @@ -725,7 +907,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.BUG).setEffort(7.0).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0).setInLeak(true)) - .assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0); } @Test @@ -740,7 +922,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.BUG).setEffort(17.0).setInLeak(true)) - .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 3.0); } @Test @@ -755,7 +937,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0).setInLeak(true)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 3.0); } @Test @@ -771,8 +953,8 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true)) - // highest severity of bugs on leak period is minor -> B - .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.B); + // highest severity of bugs on leak period is minor -> B + .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.B); } @Test @@ -788,8 +970,8 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true), // exclude resolved newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true)) - // highest severity of bugs on leak period is minor -> B - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.B); + // highest severity of bugs on leak period is minor -> B + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.B); } @Test @@ -799,10 +981,20 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Issue.STATUS_TO_REVIEW).setInLeak(false)) - .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.B); + .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.B) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.B); withNoIssues() - .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.A); + .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.A) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.A); + + with( + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(3).setInLeak(true), + newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), + // not in leak + newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Issue.STATUS_TO_REVIEW).setInLeak(false)) + .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.E) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING, Rating.D); } @Test @@ -812,7 +1004,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 75.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 75.0); withNoIssues() .assertNoLeakValue(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED); @@ -825,7 +1017,7 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0); withNoIssues() .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0); @@ -838,81 +1030,94 @@ class MeasureUpdateFormulaFactoryImplTest { newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true), // not in leak newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false)) - .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); + .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0); withNoIssues() .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0); } - @Test - void test_new_sqale_debt_ratio_and_new_maintainability_rating() { + private static Stream newMaintainabilityMetrics() { + return Stream.of( + arguments(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_SQALE_DEBT_RATIO, CoreMetrics.NEW_MAINTAINABILITY_RATING), + arguments(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, + SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING)); + } + + @ParameterizedTest + @MethodSource("newMaintainabilityMetrics") + void test_new_sqale_debt_ratio_and_new_maintainability_rating(Metric newMaintainabilityRemediationEffortMetric, Metric newMaintainabilityDebtRatioMetric, + Metric newMaintainabilityRatingMetric) { withNoIssues() - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); // technical_debt not computed with(CoreMetrics.NEW_DEVELOPMENT_COST, 0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); with(CoreMetrics.NEW_DEVELOPMENT_COST, 20) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); // development_cost not computed - with(CoreMetrics.NEW_TECHNICAL_DEBT, 0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); - with(CoreMetrics.NEW_TECHNICAL_DEBT, 20) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + with(newMaintainabilityRemediationEffortMetric, 0) + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); + with(newMaintainabilityRemediationEffortMetric, 20) + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); // input measures are available - with(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0) + with(newMaintainabilityRemediationEffortMetric, 20.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0.0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); - with(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0) + with(newMaintainabilityRemediationEffortMetric, 20.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, 160.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 12.5) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.C); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 12.5) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.C); - with(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0) + Verifier verifier = with(newMaintainabilityRemediationEffortMetric, 20.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, 10.0D) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 200.0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.E); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 200.0); + switch (newMaintainabilityRatingMetric.key()) { + case NEW_MAINTAINABILITY_RATING_KEY -> verifier.assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.E); + case SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY -> verifier.assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.D); + default -> throw new IllegalArgumentException("Unexpected metric: " + newMaintainabilityRatingMetric.key()); + } // A is 5% --> min debt is exactly 200*0.05=10 with(CoreMetrics.NEW_DEVELOPMENT_COST, 200.0) - .and(CoreMetrics.NEW_TECHNICAL_DEBT, 10.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 5.0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .and(newMaintainabilityRemediationEffortMetric, 10.0) + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 5.0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); - with(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0) + with(newMaintainabilityRemediationEffortMetric, 0.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0.0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); - with(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0) + with(newMaintainabilityRemediationEffortMetric, 0.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0.0); - with(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0) + with(newMaintainabilityRemediationEffortMetric, -20.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0.0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); // bug, debt can't be negative - with(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0) + with(newMaintainabilityRemediationEffortMetric, -20.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0.0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); // bug, cost can't be negative - with(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0) + with(newMaintainabilityRemediationEffortMetric, 20.0) .and(CoreMetrics.NEW_DEVELOPMENT_COST, -80.0) - .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) - .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); + .assertThatLeakValueIs(newMaintainabilityDebtRatioMetric, 0.0) + .assertThatLeakValueIs(newMaintainabilityRatingMetric, Rating.A); } @Test @@ -930,7 +1135,34 @@ class MeasureUpdateFormulaFactoryImplTest { newImpactGroup(SECURITY, LOW, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 6), newImpactGroup(SECURITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_FALSE_POSITIVE, 7), newImpactGroup(RELIABILITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8)) - .assertThatValueIs(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, 4 + 8); + .assertThatValueIs(CoreMetrics.HIGH_IMPACT_ACCEPTED_ISSUES, 4 + 8); + } + + @Test + void compute_shouldComputeRemediationEffortBasedOnSoftwareQuality() { + withNoIssues() + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 0) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 0) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 0) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 0d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 0d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 0d); + + with( + newImpactGroup(RELIABILITY, HIGH, 3, 1d), + newImpactGroup(RELIABILITY, MEDIUM, 1, 2d, true), + newImpactGroup(SECURITY, MEDIUM, 1, 1d), + newImpactGroup(MAINTAINABILITY, MEDIUM, 1, 1d), + newImpactGroup(MAINTAINABILITY, HIGH, 1, 2d, true), + newImpactGroup(SECURITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 4, 1d, false), + newImpactGroup(RELIABILITY, HIGH, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false), + newImpactGroup(MAINTAINABILITY, MEDIUM, Issue.STATUS_RESOLVED, Issue.RESOLUTION_WONT_FIX, 8, 1d, false)) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 1d + 2d) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 1d + 2d) + .assertThatValueIs(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 1d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, 2d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, 2d) + .assertThatLeakValueIs(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, 0d); } @Test @@ -954,9 +1186,9 @@ class MeasureUpdateFormulaFactoryImplTest { newImpactGroup(MAINTAINABILITY, MEDIUM, 10), newImpactGroup(MAINTAINABILITY, LOW, 11), newImpactGroup(SECURITY, HIGH, 3)) - .assertThatJsonValueIs(CoreMetrics.RELIABILITY_ISSUES, impactMeasureToJson(8, 3, 4, 1)) - .assertThatJsonValueIs(CoreMetrics.MAINTAINABILITY_ISSUES, impactMeasureToJson(21, 0, 10, 11)) - .assertThatJsonValueIs(CoreMetrics.SECURITY_ISSUES, impactMeasureToJson(3, 3, 0, 0)); + .assertThatJsonValueIs(CoreMetrics.RELIABILITY_ISSUES, impactMeasureToJson(8, 3, 4, 1)) + .assertThatJsonValueIs(CoreMetrics.MAINTAINABILITY_ISSUES, impactMeasureToJson(21, 0, 10, 11)) + .assertThatJsonValueIs(CoreMetrics.SECURITY_ISSUES, impactMeasureToJson(3, 3, 0, 0)); } @Test @@ -1098,18 +1330,37 @@ class MeasureUpdateFormulaFactoryImplTest { } private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, - String status, @Nullable String resolution, long count) { + String status, @Nullable String resolution, long count, double effort, boolean inLeak) { IssueImpactGroupDto dto = new IssueImpactGroupDto(); dto.setSoftwareQuality(softwareQuality); dto.setSeverity(severity); dto.setStatus(status); dto.setResolution(resolution); dto.setCount(count); + dto.setEffort(effort); + dto.setInLeak(inLeak); return dto; } + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, + String status, @Nullable String resolution, long count) { + return newImpactGroup(softwareQuality, severity, status, resolution, count, 0, false); + } + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count) { - return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count); + return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, 0, false); + } + + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count, boolean inLeak) { + return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, 0, inLeak); + } + + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count, double effort) { + return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, effort, false); + } + + private static IssueImpactGroupDto newImpactGroup(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity, long count, double effort, boolean inLeak) { + return newImpactGroup(softwareQuality, severity, Issue.STATUS_OPEN, null, count, effort, inLeak); } private static IssueGroupDto newResolvedGroup(RuleType ruleType) { -- 2.39.5