aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-webserver-webapi
diff options
context:
space:
mode:
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>2024-08-12 15:20:00 +0200
committersonartech <sonartech@sonarsource.com>2024-08-26 20:03:06 +0000
commitd72b9cd73e9d34a2be53cba7fe95a01cce18ee89 (patch)
tree8385b9fbb101fb40f76ff223cb9eaaac185e78ce /server/sonar-webserver-webapi
parentdca06894a62db2826103a1e046ae78ee0ef1294a (diff)
downloadsonarqube-d72b9cd73e9d34a2be53cba7fe95a01cce18ee89.tar.gz
sonarqube-d72b9cd73e9d34a2be53cba7fe95a01cce18ee89.zip
SONAR-22727 Manage new metrics in the live update
Diffstat (limited to 'server/sonar-webserver-webapi')
-rw-r--r--server/sonar-webserver-webapi/build.gradle2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/IssueCounter.java61
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImpl.java145
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/MeasureUpdateFormulaFactoryImplTest.java565
4 files changed, 599 insertions, 174 deletions
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<SoftwareQuality, Map<Severity, Count>> bySoftwareQualityAndSeverity = new EnumMap<>(SoftwareQuality.class);
+ private final Map<SoftwareQuality, Effort> effortOfUnresolvedBySoftwareQuality = new EnumMap<>(SoftwareQuality.class);
+ private final Map<SoftwareQuality, HighestImpactSeverity> highestSeverityOfUnresolvedBySoftwareQuality = new EnumMap<>(SoftwareQuality.class);
IssueCounter(Collection<IssueGroupDto> groups, Collection<IssueImpactGroupDto> 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<Severity> 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<Double> 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<Double> 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<Metric> 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<Double> 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<Double> 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;
@@ -153,6 +166,75 @@ class MeasureUpdateFormulaFactoryImplTest {
}
@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<Metric<?>> 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<Metric<?>> 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);
with(newGroup(), newGroup().setCount(4)).assertThatValueIs(CoreMetrics.VIOLATIONS, 5);
@@ -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<Arguments> 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<Arguments> 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<Arguments> 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) {