From d3704d8bc1c5eeb059ef8f3a4d62bd0a59b05e2e Mon Sep 17 00:00:00 2001 From: =?utf8?q?L=C3=A9o=20Geoffroy?= Date: Fri, 9 Aug 2024 11:43:28 +0200 Subject: [PATCH] SONAR-22727 Add new software qualities remediation efforts measures --- .../issue/EffortAggregator.java | 74 ++++++-- .../issue/NewEffortAggregator.java | 52 +++++ .../MaintainabilityMeasuresVisitor.java | 5 - .../issue/EffortAggregatorTest.java | 164 ++++++++++------ .../issue/NewEffortAggregatorTest.java | 178 ++++++++++++------ .../metric/SoftwareQualitiesMetrics.java | 4 +- .../metric/SoftwareQualitiesMetricsTest.java | 2 +- 7 files changed, 344 insertions(+), 135 deletions(-) diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/EffortAggregator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/EffortAggregator.java index 22efd4cd1bc..434e328b35b 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/EffortAggregator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/EffortAggregator.java @@ -28,16 +28,23 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.metric.SoftwareQualitiesMetrics; import static org.sonar.api.measures.CoreMetrics.RELIABILITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.CoreMetrics.SECURITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY; /** * Compute effort related measures : * {@link CoreMetrics#TECHNICAL_DEBT_KEY} * {@link CoreMetrics#RELIABILITY_REMEDIATION_EFFORT_KEY} * {@link CoreMetrics#SECURITY_REMEDIATION_EFFORT_KEY} + * {@link SoftwareQualitiesMetrics#SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY} + * {@link SoftwareQualitiesMetrics#SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY} + * {@link SoftwareQualitiesMetrics#SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY} */ public class EffortAggregator extends IssueVisitor { @@ -48,13 +55,24 @@ public class EffortAggregator extends IssueVisitor { private final Metric reliabilityEffortMetric; private final Metric securityEffortMetric; + private final Metric softwareQualityMaintainabilityEffortMetric; + private final Metric softwareQualityReliabilityEffortMetric; + private final Metric softwareQualitySecurityEffortMetric; + private EffortCounter effortCounter; public EffortAggregator(MetricRepository metricRepository, MeasureRepository measureRepository) { this.measureRepository = measureRepository; + + // Based on issue Type and Severity this.maintainabilityEffortMetric = metricRepository.getByKey(TECHNICAL_DEBT_KEY); this.reliabilityEffortMetric = metricRepository.getByKey(RELIABILITY_REMEDIATION_EFFORT_KEY); this.securityEffortMetric = metricRepository.getByKey(SECURITY_REMEDIATION_EFFORT_KEY); + + // Based on software qualities + this.softwareQualityMaintainabilityEffortMetric = metricRepository.getByKey(SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY); + this.softwareQualityReliabilityEffortMetric = metricRepository.getByKey(SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY); + this.softwareQualitySecurityEffortMetric = metricRepository.getByKey(SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY); } @Override @@ -90,14 +108,17 @@ public class EffortAggregator extends IssueVisitor { private void computeMaintainabilityEffortMeasure(Component component) { measureRepository.add(component, maintainabilityEffortMetric, Measure.newMeasureBuilder().create(effortCounter.maintainabilityEffort)); + measureRepository.add(component, softwareQualityMaintainabilityEffortMetric, Measure.newMeasureBuilder().create(effortCounter.softwareQualityMaintainabilityEffort)); } private void computeReliabilityEffortMeasure(Component component) { measureRepository.add(component, reliabilityEffortMetric, Measure.newMeasureBuilder().create(effortCounter.reliabilityEffort)); + measureRepository.add(component, softwareQualityReliabilityEffortMetric, Measure.newMeasureBuilder().create(effortCounter.softwareQualityReliabilityEffort)); } private void computeSecurityEffortMeasure(Component component) { measureRepository.add(component, securityEffortMetric, Measure.newMeasureBuilder().create(effortCounter.securityEffort)); + measureRepository.add(component, softwareQualitySecurityEffortMetric, Measure.newMeasureBuilder().create(effortCounter.softwareQualitySecurityEffort)); } private static class EffortCounter { @@ -105,25 +126,52 @@ public class EffortAggregator extends IssueVisitor { private long reliabilityEffort = 0L; private long securityEffort = 0L; + private long softwareQualityMaintainabilityEffort = 0L; + private long softwareQualityReliabilityEffort = 0L; + private long softwareQualitySecurityEffort = 0L; + void add(DefaultIssue issue) { Long issueEffort = issue.effortInMinutes(); if (issueEffort != null && issueEffort != 0L) { - switch (issue.type()) { - case CODE_SMELL: - maintainabilityEffort += issueEffort; - break; - case BUG: - reliabilityEffort += issueEffort; + computeTypeEffort(issue, issueEffort); + computeSoftwareQualityEffort(issue, issueEffort); + } + } + + private void computeSoftwareQualityEffort(DefaultIssue issue, Long issueEffort) { + issue.impacts().forEach((sq, severity) -> { + switch (sq) { + case MAINTAINABILITY: + softwareQualityMaintainabilityEffort += issueEffort; break; - case VULNERABILITY: - securityEffort += issueEffort; + case RELIABILITY: + softwareQualityReliabilityEffort += issueEffort; break; - case SECURITY_HOTSPOT: - // Not counted + case SECURITY: + softwareQualitySecurityEffort += issueEffort; break; default: - throw new IllegalStateException(String.format("Unknown type '%s'", issue.type())); + throw new IllegalStateException(String.format("Unknown software quality '%s'", sq)); } + }); + } + + private void computeTypeEffort(DefaultIssue issue, Long issueEffort) { + switch (issue.type()) { + case CODE_SMELL: + maintainabilityEffort += issueEffort; + break; + case BUG: + reliabilityEffort += issueEffort; + break; + case VULNERABILITY: + securityEffort += issueEffort; + break; + case SECURITY_HOTSPOT: + // Not counted + break; + default: + throw new IllegalStateException(String.format("Unknown type '%s'", issue.type())); } } @@ -131,6 +179,10 @@ public class EffortAggregator extends IssueVisitor { maintainabilityEffort += effortCounter.maintainabilityEffort; reliabilityEffort += effortCounter.reliabilityEffort; securityEffort += effortCounter.securityEffort; + + softwareQualityMaintainabilityEffort += effortCounter.softwareQualityMaintainabilityEffort; + softwareQualityReliabilityEffort += effortCounter.softwareQualityReliabilityEffort; + softwareQualitySecurityEffort += effortCounter.softwareQualitySecurityEffort; } } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java index f080b95ad2b..5006fe2fe16 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregator.java @@ -29,16 +29,23 @@ import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.Metric; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.metric.SoftwareQualitiesMetrics; import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY; /** * Compute new effort related measures : * {@link CoreMetrics#NEW_TECHNICAL_DEBT_KEY} * {@link CoreMetrics#NEW_RELIABILITY_REMEDIATION_EFFORT_KEY} * {@link CoreMetrics#NEW_SECURITY_REMEDIATION_EFFORT_KEY} + * {@link SoftwareQualitiesMetrics#NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY} + * {@link SoftwareQualitiesMetrics#NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY} + * {@link SoftwareQualitiesMetrics#NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY} */ public class NewEffortAggregator extends IssueVisitor { private final Map counterByComponentUuid = new HashMap<>(); @@ -48,15 +55,25 @@ public class NewEffortAggregator extends IssueVisitor { private final Metric newReliabilityEffortMetric; private final Metric newSecurityEffortMetric; private final NewIssueClassifier newIssueClassifier; + private final Metric newSoftwareQualityMaintainabilityEffortMetric; + private final Metric newSoftwareQualityReliabilityEffortMetric; + private final Metric newSoftwareQualitySecurityEffortMetric; private NewEffortCounter counter = null; public NewEffortAggregator(MetricRepository metricRepository, MeasureRepository measureRepository, NewIssueClassifier newIssueClassifier) { this.measureRepository = measureRepository; + // Based on issue Type and Severity this.newMaintainabilityEffortMetric = metricRepository.getByKey(NEW_TECHNICAL_DEBT_KEY); this.newReliabilityEffortMetric = metricRepository.getByKey(NEW_RELIABILITY_REMEDIATION_EFFORT_KEY); this.newSecurityEffortMetric = metricRepository.getByKey(NEW_SECURITY_REMEDIATION_EFFORT_KEY); + + // Based on software qualities + this.newSoftwareQualityMaintainabilityEffortMetric = metricRepository.getByKey(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY); + this.newSoftwareQualityReliabilityEffortMetric = metricRepository.getByKey(NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY); + this.newSoftwareQualitySecurityEffortMetric = metricRepository.getByKey(NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY); + this.newIssueClassifier = newIssueClassifier; } @@ -85,6 +102,10 @@ public class NewEffortAggregator extends IssueVisitor { computeMeasure(component, newMaintainabilityEffortMetric, counter.maintainabilitySum); computeMeasure(component, newReliabilityEffortMetric, counter.reliabilitySum); computeMeasure(component, newSecurityEffortMetric, counter.securitySum); + + computeMeasure(component, newSoftwareQualityMaintainabilityEffortMetric, counter.softwareQualityMaintainabilitySum); + computeMeasure(component, newSoftwareQualityReliabilityEffortMetric, counter.softwareQualityReliabilitySum); + computeMeasure(component, newSoftwareQualitySecurityEffortMetric, counter.softwareQualitySecuritySum); } counter = null; } @@ -99,14 +120,45 @@ public class NewEffortAggregator extends IssueVisitor { private final EffortSum reliabilitySum = new EffortSum(); private final EffortSum securitySum = new EffortSum(); + private final EffortSum softwareQualityMaintainabilitySum = new EffortSum(); + private final EffortSum softwareQualityReliabilitySum = new EffortSum(); + private final EffortSum softwareQualitySecuritySum = new EffortSum(); + void add(NewEffortCounter otherCounter) { maintainabilitySum.add(otherCounter.maintainabilitySum); reliabilitySum.add(otherCounter.reliabilitySum); securitySum.add(otherCounter.securitySum); + + softwareQualityMaintainabilitySum.add(otherCounter.softwareQualityMaintainabilitySum); + softwareQualityReliabilitySum.add(otherCounter.softwareQualityReliabilitySum); + softwareQualitySecuritySum.add(otherCounter.softwareQualitySecuritySum); } void add(Component component, DefaultIssue issue) { long newEffort = calculate(component, issue); + computeTypeEffort(issue, newEffort); + computeSoftwareQualityEffort(issue, newEffort); + } + + private void computeSoftwareQualityEffort(DefaultIssue issue, long newEffort) { + issue.impacts().forEach((sq, severity) -> { + switch (sq) { + case MAINTAINABILITY: + softwareQualityMaintainabilitySum.add(newEffort); + break; + case RELIABILITY: + softwareQualityReliabilitySum.add(newEffort); + break; + case SECURITY: + softwareQualitySecuritySum.add(newEffort); + break; + default: + throw new IllegalStateException(String.format("Unknown software quality '%s'", sq)); + } + }); + } + + private void computeTypeEffort(DefaultIssue issue, long newEffort) { switch (issue.type()) { case CODE_SMELL: maintainabilitySum.add(newEffort); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java index 94cb910e2c2..8301ead2685 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java @@ -24,7 +24,6 @@ import org.sonar.api.measures.CoreMetrics; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit; import org.sonar.ce.task.projectanalysis.component.PathAwareVisitorAdapter; -import org.sonar.ce.task.projectanalysis.formula.counter.RatingValue; import org.sonar.ce.task.projectanalysis.measure.Measure; import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.measure.RatingMeasures; @@ -148,8 +147,6 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter
impacts) { + return newMaintainabilityIssueWithoutEffort().setEffort(Duration.create(effort)).replaceImpacts(impacts); + } + + private static DefaultIssue newMaintainabilityIssue(long effort) { + return newMaintainabilityIssueWithoutEffort().setEffort(Duration.create(effort)).setType(CODE_SMELL).replaceImpacts(Map.of(MAINTAINABILITY, HIGH)); } - private static DefaultIssue newBugIssue(long effort) { - return newCodeSmellIssueWithoutEffort().setEffort(Duration.create(effort)).setType(BUG); + private static DefaultIssue newReliabilityIssue(long effort) { + return newMaintainabilityIssueWithoutEffort().setEffort(Duration.create(effort)).setType(BUG).replaceImpacts(Map.of(RELIABILITY, HIGH)); } - private static DefaultIssue newVulnerabilityIssue(long effort) { - return newCodeSmellIssueWithoutEffort().setEffort(Duration.create(effort)).setType(VULNERABILITY); + private static DefaultIssue newSecurityIssue(long effort) { + return newMaintainabilityIssueWithoutEffort().setEffort(Duration.create(effort)).setType(VULNERABILITY).replaceImpacts(Map.of(SECURITY, HIGH)); } - private static DefaultIssue newCodeSmellIssueWithoutEffort() { - return new DefaultIssue().setType(CODE_SMELL); + private static DefaultIssue newMaintainabilityIssueWithoutEffort() { + return new DefaultIssue().setType(CODE_SMELL).replaceImpacts(Map.of(MAINTAINABILITY, HIGH)); } - private static DefaultIssue newBugIssueWithoutEffort() { - return new DefaultIssue().setType(BUG); + private static DefaultIssue newReliabilityIssueWithoutEffort() { + return new DefaultIssue().setType(BUG).replaceImpacts(Map.of(RELIABILITY, HIGH)); } - private static DefaultIssue newVulnerabilityIssueWithoutEffort() { - return new DefaultIssue().setType(VULNERABILITY); + private static DefaultIssue newSecurityIssueWithoutEffort() { + return new DefaultIssue().setType(VULNERABILITY).replaceImpacts(Map.of(SECURITY, HIGH)); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java index 914c8185a90..9aef4e37ac5 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewEffortAggregatorTest.java @@ -19,7 +19,13 @@ */ package org.sonar.ce.task.projectanalysis.issue; -import org.junit.Test; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.Duration; import org.sonar.ce.task.projectanalysis.analysis.Branch; @@ -39,6 +45,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; +import static org.sonar.api.issue.impact.Severity.HIGH; +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.NEW_RELIABILITY_REMEDIATION_EFFORT; import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY; import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT; @@ -48,33 +58,42 @@ import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY; import static org.sonar.api.rules.RuleType.BUG; import static org.sonar.api.rules.RuleType.CODE_SMELL; import static org.sonar.api.rules.RuleType.VULNERABILITY; - -public class NewEffortAggregatorTest { +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT; +import static org.sonar.server.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY; + +class NewEffortAggregatorTest { private static final Component FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid("FILE").build(); private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 2).addChildren(FILE).build(); - @org.junit.Rule + @RegisterExtension public PeriodHolderRule periodsHolder = new PeriodHolderRule(); - @org.junit.Rule + @RegisterExtension public MetricRepositoryRule metricRepository = new MetricRepositoryRule() .add(NEW_TECHNICAL_DEBT) .add(NEW_RELIABILITY_REMEDIATION_EFFORT) - .add(NEW_SECURITY_REMEDIATION_EFFORT); - @org.junit.Rule + .add(NEW_SECURITY_REMEDIATION_EFFORT) + .add(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT) + .add(NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT) + .add(NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT); + @RegisterExtension public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(); private final NewIssueClassifier newIssueClassifier = mock(NewIssueClassifier.class); private final NewEffortAggregator underTest = new NewEffortAggregator(metricRepository, measureRepository, newIssueClassifier); @Test - public void sum_new_maintainability_effort_of_issues() { + void sum_new_maintainability_effort_of_issues() { when(newIssueClassifier.isEnabled()).thenReturn(true); when(newIssueClassifier.isNew(any(), any())).thenReturn(true); - DefaultIssue unresolved1 = newCodeSmellIssue(10L); + DefaultIssue unresolved1 = newMaintainabilityIssue(10L); DefaultIssue old1 = oldCodeSmellIssue(100L); - DefaultIssue unresolved2 = newCodeSmellIssue(30L); + DefaultIssue unresolved2 = newMaintainabilityIssue(30L); DefaultIssue old2 = oldCodeSmellIssue(300L); - DefaultIssue unresolvedWithoutDebt = newCodeSmellIssueWithoutEffort(); - DefaultIssue resolved = newCodeSmellIssue(50L).setResolution(RESOLUTION_FIXED); + DefaultIssue unresolvedWithoutDebt = newMaintainabilityIssueWithoutEffort(); + DefaultIssue resolved = newMaintainabilityIssue(50L).setResolution(RESOLUTION_FIXED); underTest.beforeComponent(FILE); underTest.onIssue(FILE, unresolved1); @@ -86,18 +105,38 @@ public class NewEffortAggregatorTest { underTest.afterComponent(FILE); assertValue(FILE, NEW_TECHNICAL_DEBT_KEY, 10 + 30); + assertValue(FILE, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, 10 + 30); } @Test - public void new_maintainability_effort_is_only_computed_using_code_smell_issues() { + void sum_effort_when_multiple_impacts() { when(newIssueClassifier.isEnabled()).thenReturn(true); when(newIssueClassifier.isNew(any(), any())).thenReturn(true); - DefaultIssue codeSmellIssue = newCodeSmellIssue(10); + + DefaultIssue unresolved1 = createIssue(CODE_SMELL, List.of(MAINTAINABILITY, RELIABILITY, SECURITY), 10, true); + DefaultIssue unresolved2 = createIssue(CODE_SMELL, List.of(MAINTAINABILITY, RELIABILITY, SECURITY), 10, true); + + underTest.beforeComponent(FILE); + underTest.onIssue(FILE, unresolved1); + underTest.onIssue(FILE, unresolved2); + underTest.afterComponent(FILE); + + // total maintainability effort + assertValue(FILE, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, 20); + assertValue(FILE, NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY, 20); + assertValue(FILE, NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY, 20); + } + + @Test + void new_maintainability_effort_is_only_computed_using_maintainability_issues() { + when(newIssueClassifier.isEnabled()).thenReturn(true); + when(newIssueClassifier.isNew(any(), any())).thenReturn(true); + DefaultIssue codeSmellIssue = newMaintainabilityIssue(10); DefaultIssue oldSmellIssue = oldCodeSmellIssue(100); // Issues of type BUG and VULNERABILITY should be ignored - DefaultIssue bugIssue = newBugIssue(15); + DefaultIssue bugIssue = newReliabilityIssue(15); DefaultIssue oldBugIssue = oldBugIssue(150); - DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12); + DefaultIssue vulnerabilityIssue = newSecurityIssue(12); DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120); underTest.beforeComponent(FILE); @@ -111,19 +150,20 @@ public class NewEffortAggregatorTest { // Only effort of CODE SMELL issue is used assertValue(FILE, NEW_TECHNICAL_DEBT_KEY, 10); + assertValue(FILE, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, 10); } @Test - public void sum_new_reliability_effort_of_issues() { + void sum_new_reliability_effort_of_issues() { when(newIssueClassifier.isEnabled()).thenReturn(true); when(newIssueClassifier.isNew(any(), any())).thenReturn(true); - DefaultIssue unresolved1 = newBugIssue(10L); + DefaultIssue unresolved1 = newReliabilityIssue(10L); DefaultIssue old1 = oldBugIssue(100L); - DefaultIssue unresolved2 = newBugIssue(30L); + DefaultIssue unresolved2 = newReliabilityIssue(30L); DefaultIssue old2 = oldBugIssue(300L); - DefaultIssue unresolvedWithoutDebt = newBugIssueWithoutEffort(); - DefaultIssue resolved = newBugIssue(50L).setResolution(RESOLUTION_FIXED); + DefaultIssue unresolvedWithoutDebt = newReliabilityIssueWithoutEffort(); + DefaultIssue resolved = newReliabilityIssue(50L).setResolution(RESOLUTION_FIXED); underTest.beforeComponent(FILE); underTest.onIssue(FILE, unresolved1); @@ -135,18 +175,19 @@ public class NewEffortAggregatorTest { underTest.afterComponent(FILE); assertValue(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 10 + 30); + assertValue(FILE, NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY, 10 + 30); } @Test - public void new_reliability_effort_is_only_computed_using_bug_issues() { + void new_reliability_effort_is_only_computed_using_bug_issues() { when(newIssueClassifier.isEnabled()).thenReturn(true); when(newIssueClassifier.isNew(any(), any())).thenReturn(true); - DefaultIssue bugIssue = newBugIssue(15); + DefaultIssue bugIssue = newReliabilityIssue(15); DefaultIssue oldBugIssue = oldBugIssue(150); // Issues of type CODE SMELL and VULNERABILITY should be ignored - DefaultIssue codeSmellIssue = newCodeSmellIssue(10); + DefaultIssue codeSmellIssue = newMaintainabilityIssue(10); DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100); - DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12); + DefaultIssue vulnerabilityIssue = newSecurityIssue(12); DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120); underTest.beforeComponent(FILE); @@ -160,17 +201,18 @@ public class NewEffortAggregatorTest { // Only effort of BUG issue is used assertValue(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 15); + assertValue(FILE, NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY, 15); } @Test - public void sum_new_vulnerability_effort_of_issues() { + void sum_new_vulnerability_effort_of_issues() { when(newIssueClassifier.isEnabled()).thenReturn(true); - DefaultIssue unresolved1 = newVulnerabilityIssue(10L); + DefaultIssue unresolved1 = newSecurityIssue(10L); DefaultIssue old1 = oldVulnerabilityIssue(100L); - DefaultIssue unresolved2 = newVulnerabilityIssue(30L); + DefaultIssue unresolved2 = newSecurityIssue(30L); DefaultIssue old2 = oldVulnerabilityIssue(300L); - DefaultIssue unresolvedWithoutDebt = newVulnerabilityIssueWithoutEffort(); - DefaultIssue resolved = newVulnerabilityIssue(50L).setResolution(RESOLUTION_FIXED); + DefaultIssue unresolvedWithoutDebt = newSecurityIssueWithoutEffort(); + DefaultIssue resolved = newSecurityIssue(50L).setResolution(RESOLUTION_FIXED); DefaultIssue oldResolved = oldVulnerabilityIssue(500L).setResolution(RESOLUTION_FIXED); underTest.beforeComponent(FILE); @@ -184,18 +226,19 @@ public class NewEffortAggregatorTest { underTest.afterComponent(FILE); assertValue(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 10 + 30); + assertValue(FILE, NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY, 10 + 30); } @Test - public void new_security_effort_is_only_computed_using_vulnerability_issues() { + void new_security_effort_is_only_computed_using_vulnerability_issues() { when(newIssueClassifier.isEnabled()).thenReturn(true); when(newIssueClassifier.isNew(any(), any())).thenReturn(true); - DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12); + DefaultIssue vulnerabilityIssue = newSecurityIssue(12); DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120); // Issues of type CODE SMELL and BUG should be ignored - DefaultIssue codeSmellIssue = newCodeSmellIssue(10); + DefaultIssue codeSmellIssue = newMaintainabilityIssue(10); DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100); - DefaultIssue bugIssue = newBugIssue(15); + DefaultIssue bugIssue = newReliabilityIssue(15); DefaultIssue oldBugIssue = oldBugIssue(150); underTest.beforeComponent(FILE); @@ -209,25 +252,26 @@ public class NewEffortAggregatorTest { // Only effort of VULNERABILITY issue is used assertValue(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 12); + assertValue(FILE, NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY, 12); } @Test - public void aggregate_new_characteristic_measures_of_children() { + void aggregate_new_characteristic_measures_of_children() { when(newIssueClassifier.isEnabled()).thenReturn(true); when(newIssueClassifier.isNew(any(), any())).thenReturn(true); - DefaultIssue codeSmellIssue = newCodeSmellIssue(10); + DefaultIssue codeSmellIssue = newMaintainabilityIssue(10); DefaultIssue oldCodeSmellIssue = oldCodeSmellIssue(100); - DefaultIssue bugIssue = newBugIssue(8); + DefaultIssue bugIssue = newReliabilityIssue(8); DefaultIssue oldBugIssue = oldBugIssue(80); - DefaultIssue vulnerabilityIssue = newVulnerabilityIssue(12); + DefaultIssue vulnerabilityIssue = newSecurityIssue(12); DefaultIssue oldVulnerabilityIssue = oldVulnerabilityIssue(120); - DefaultIssue codeSmellProjectIssue = newCodeSmellIssue(30); + DefaultIssue codeSmellProjectIssue = newMaintainabilityIssue(30); DefaultIssue oldCodeSmellProjectIssue = oldCodeSmellIssue(300); - DefaultIssue bugProjectIssue = newBugIssue(28); + DefaultIssue bugProjectIssue = newReliabilityIssue(28); DefaultIssue oldBugProjectIssue = oldBugIssue(280); - DefaultIssue vulnerabilityProjectIssue = newVulnerabilityIssue(32); + DefaultIssue vulnerabilityProjectIssue = newSecurityIssue(32); DefaultIssue oldVulnerabilityProjectIssue = oldVulnerabilityIssue(320); underTest.beforeComponent(FILE); @@ -250,15 +294,19 @@ public class NewEffortAggregatorTest { assertValue(PROJECT, NEW_TECHNICAL_DEBT_KEY, 10 + 30); assertValue(PROJECT, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 8 + 28); assertValue(PROJECT, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 12 + 32); + + assertValue(PROJECT, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, 10 + 30); + assertValue(PROJECT, NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY, 8 + 28); + assertValue(PROJECT, NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY, 12 + 32); } @Test - public void no_measures_if_no_periods() { + void no_measures_if_no_periods() { when(newIssueClassifier.isEnabled()).thenReturn(false); Branch branch = mock(Branch.class); when(branch.getType()).thenReturn(BranchType.BRANCH); periodsHolder.setPeriod(null); - DefaultIssue unresolved = newCodeSmellIssue(10); + DefaultIssue unresolved = newMaintainabilityIssue(10); underTest.beforeComponent(FILE); underTest.onIssue(FILE, unresolved); @@ -268,7 +316,7 @@ public class NewEffortAggregatorTest { } @Test - public void should_have_empty_measures_if_no_issues() { + void should_have_empty_measures_if_no_issues() { when(newIssueClassifier.isEnabled()).thenReturn(true); when(newIssueClassifier.isNew(any(), any())).thenReturn(true); @@ -278,6 +326,10 @@ public class NewEffortAggregatorTest { assertValue(FILE, NEW_TECHNICAL_DEBT_KEY, 0); assertValue(FILE, NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, 0); assertValue(FILE, NEW_SECURITY_REMEDIATION_EFFORT_KEY, 0); + + assertValue(FILE, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY, 0); + assertValue(FILE, NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY, 0); + assertValue(FILE, NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY, 0); } private void assertValue(Component component, String metricKey, int value) { @@ -285,52 +337,58 @@ public class NewEffortAggregatorTest { assertThat(newMeasure.getLongValue()).isEqualTo(value); } - private DefaultIssue newCodeSmellIssue(long effort) { - return createIssue(CODE_SMELL, effort, true); + private DefaultIssue newMaintainabilityIssue(long effort) { + return createIssue(CODE_SMELL, MAINTAINABILITY, effort, true); } private DefaultIssue oldCodeSmellIssue(long effort) { - return createIssue(CODE_SMELL, effort, false); + return createIssue(CODE_SMELL, MAINTAINABILITY, effort, false); } - private DefaultIssue newBugIssue(long effort) { - return createIssue(BUG, effort, true); + private DefaultIssue newReliabilityIssue(long effort) { + return createIssue(BUG, RELIABILITY, effort, true); } private DefaultIssue oldBugIssue(long effort) { - return createIssue(BUG, effort, false); + return createIssue(BUG, RELIABILITY, effort, false); } - private DefaultIssue newVulnerabilityIssue(long effort) { - return createIssue(VULNERABILITY, effort, true); + private DefaultIssue newSecurityIssue(long effort) { + return createIssue(VULNERABILITY, SECURITY, effort, true); } private DefaultIssue oldVulnerabilityIssue(long effort) { - return createIssue(VULNERABILITY, effort, false); + return createIssue(VULNERABILITY, SECURITY, effort, false); } - private DefaultIssue newCodeSmellIssueWithoutEffort() { + private DefaultIssue newMaintainabilityIssueWithoutEffort() { DefaultIssue defaultIssue = new DefaultIssue() .setKey(UuidFactoryFast.getInstance().create()) + .replaceImpacts(Map.of(MAINTAINABILITY, Severity.HIGH)) .setType(CODE_SMELL); when(newIssueClassifier.isNew(any(), eq(defaultIssue))).thenReturn(true); return defaultIssue; } - private DefaultIssue createIssue(RuleType type, long effort, boolean isNew) { + private DefaultIssue createIssue(RuleType type, SoftwareQuality softwareQuality, long effort, boolean isNew) { + return createIssue(type, List.of(softwareQuality), effort, isNew); + } + + private DefaultIssue createIssue(RuleType type, List softwareQualities, long effort, boolean isNew) { DefaultIssue defaultIssue = new DefaultIssue() .setKey(UuidFactoryFast.getInstance().create()) .setEffort(Duration.create(effort)) - .setType(type); + .setType(type) + .replaceImpacts(softwareQualities.stream().collect(Collectors.toMap(e -> e, e -> HIGH))); when(newIssueClassifier.isNew(any(), eq(defaultIssue))).thenReturn(isNew); return defaultIssue; } - private static DefaultIssue newBugIssueWithoutEffort() { - return new DefaultIssue().setType(BUG); + private static DefaultIssue newReliabilityIssueWithoutEffort() { + return new DefaultIssue().setType(BUG).replaceImpacts(Map.of(RELIABILITY, Severity.HIGH)); } - private static DefaultIssue newVulnerabilityIssueWithoutEffort() { - return new DefaultIssue().setType(VULNERABILITY); + private static DefaultIssue newSecurityIssueWithoutEffort() { + return new DefaultIssue().setType(VULNERABILITY).replaceImpacts(Map.of(SECURITY, Severity.HIGH)); } } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/metric/SoftwareQualitiesMetrics.java b/server/sonar-server-common/src/main/java/org/sonar/server/metric/SoftwareQualitiesMetrics.java index b5d037ddd04..229e9ebb76f 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/metric/SoftwareQualitiesMetrics.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/metric/SoftwareQualitiesMetrics.java @@ -208,7 +208,7 @@ public class SoftwareQualitiesMetrics implements Metrics { public static final String SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY = "software_quality_reliability_remediation_effort"; - public static final Metric RELIABILITY_REMEDIATION_EFFORT = + public static final Metric SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT = new Metric.Builder(SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY, "Software Quality Reliability Remediation Effort", Metric.ValueType.WORK_DUR) .setDescription("Software quality reliability remediation effort") @@ -275,7 +275,7 @@ public class SoftwareQualitiesMetrics implements Metrics { NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, - RELIABILITY_REMEDIATION_EFFORT, + SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/metric/SoftwareQualitiesMetricsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/metric/SoftwareQualitiesMetricsTest.java index 717c88af58a..b4315ffaa51 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/metric/SoftwareQualitiesMetricsTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/metric/SoftwareQualitiesMetricsTest.java @@ -41,7 +41,7 @@ class SoftwareQualitiesMetricsTest { SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT, - SoftwareQualitiesMetrics.RELIABILITY_REMEDIATION_EFFORT, + SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO); -- 2.39.5