From 144b731489f00e0eb2406036ddcf3fab3a5126aa Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Tue, 7 Jun 2022 16:08:57 -0500 Subject: [PATCH] SONAR-11401 Calculate hotspot measures --- .../org/sonar/db/component/ComponentDao.java | 4 +- .../sonar/db/component/ComponentMapper.java | 2 +- .../sonar/db/component/ComponentMapper.xml | 4 +- .../sonar/db/component/ComponentDaoTest.java | 8 +- .../server/security/SecurityReviewRating.java | 10 +- .../measure/live/ComponentIndexImpl.java | 6 +- .../measure/live/HotspotMeasureUpdater.java | 60 --------- .../measure/live/LiveMeasureModule.java | 1 - .../live/LiveMeasureTreeUpdaterImpl.java | 80 ++++++++++-- .../measure/live/MeasureUpdateFormula.java | 9 ++ .../live/MeasureUpdateFormulaFactoryImpl.java | 14 ++- .../live/HotspotMeasureUpdaterTest.java | 119 ------------------ .../live/LiveMeasureTreeUpdaterImplTest.java | 105 +++++++++++++--- .../MeasureUpdateFormulaFactoryImplTest.java | 76 +++++++++-- 14 files changed, 259 insertions(+), 239 deletions(-) delete mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/HotspotMeasureUpdater.java delete mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/HotspotMeasureUpdaterTest.java diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java index 98655ebf25c..afaba3af0e2 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java @@ -224,9 +224,9 @@ public class ComponentDao implements Dao { return mapper(dbSession).selectDescendants(query, componentOpt.get().uuid(), query.getUuidPath(component)); } - public List selectChildren(DbSession dbSession, Collection components) { + public List selectChildren(DbSession dbSession, String branchUuid, Collection components) { Set uuidPaths = components.stream().map(c -> c.getUuidPath() + c.uuid() + ".").collect(Collectors.toSet()); - return mapper(dbSession).selectChildren(uuidPaths); + return mapper(dbSession).selectChildren(branchUuid, uuidPaths); } public ComponentDto selectOrFailByKey(DbSession session, String key) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java index f517bddea89..2d827bee828 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java @@ -69,7 +69,7 @@ public interface ComponentMapper { List selectDescendants(@Param("query") ComponentTreeQuery query, @Param("baseUuid") String baseUuid, @Param("baseUuidPath") String baseUuidPath); - List selectChildren(@Param("uuidPaths") Set uuidPaths); + List selectChildren(@Param("branchUuid") String branchUuid, @Param("uuidPaths") Set uuidPaths); /** * Returns all enabled projects (Scope {@link org.sonar.api.resources.Scopes#PROJECT} and qualifier diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml index efacf0809b4..05d2e777fe1 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -379,7 +379,9 @@ select from components p - where p.uuid_path in + where + p.project_uuid = #{branchUuid,jdbcType=VARCHAR} + and p.uuid_path in #{uuidPath,jdbcType=VARCHAR} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java index d0dd5e840a9..c7c7d77f643 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java @@ -1684,16 +1684,16 @@ public class ComponentDaoTest { db.commit(); // test children of root - assertThat(underTest.selectChildren(dbSession, List.of(project))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID); + assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(project))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID); // test children of intermediate component (module here) - assertThat(underTest.selectChildren(dbSession, List.of(module))).extracting("uuid").containsOnly(FILE_2_UUID, FILE_3_UUID); + assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(module))).extracting("uuid").containsOnly(FILE_2_UUID, FILE_3_UUID); // test children of leaf component (file here) - assertThat(underTest.selectChildren(dbSession, List.of(fileInProject))).isEmpty(); + assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(fileInProject))).isEmpty(); // test children of 2 components - assertThat(underTest.selectChildren(dbSession, List.of(project, module))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID, FILE_2_UUID, FILE_3_UUID); + assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(project, module))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID, FILE_2_UUID, FILE_3_UUID); } @Test diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java index 463c0b17901..35ce1e1b0a9 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java @@ -40,17 +40,17 @@ public class SecurityReviewRating { if (total == 0) { return Optional.empty(); } - return Optional.of(hotspotsReviewed * 100.0 / total); + return Optional.of(hotspotsReviewed * 100.0D / total); } public static Rating computeRating(@Nullable Double percent) { - if (percent == null || percent >= 80.0) { + if (percent == null || percent >= 80.0D) { return A; - } else if (percent >= 70.0) { + } else if (percent >= 70.0D) { return B; - } else if (percent >= 50.0) { + } else if (percent >= 50.0D) { return C; - } else if (percent >= 30.0) { + } else if (percent >= 30.0D) { return D; } return E; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/ComponentIndexImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/ComponentIndexImpl.java index 7b5da77499e..1bc345b18b1 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/ComponentIndexImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/ComponentIndexImpl.java @@ -53,7 +53,7 @@ public class ComponentIndexImpl implements ComponentIndex { sortedComponentsToRoot = loadTreeOfComponents(dbSession, touchedComponents); branchComponent = findBranchComponent(sortedComponentsToRoot); children = new HashMap<>(); - List childComponents = loadChildren(dbSession, sortedComponentsToRoot); + List childComponents = loadChildren(dbSession, branchComponent.uuid(), sortedComponentsToRoot); for (ComponentDto c : childComponents) { List uuidPathAsList = c.getUuidPathAsList(); String parentUuid = uuidPathAsList.get(uuidPathAsList.size() - 1); @@ -66,8 +66,8 @@ public class ComponentIndexImpl implements ComponentIndex { .orElseThrow(() -> new IllegalStateException("No project found in " + components)); } - private List loadChildren(DbSession dbSession, Collection components) { - return dbClient.componentDao().selectChildren(dbSession, components); + private List loadChildren(DbSession dbSession, String branchUuid, Collection components) { + return dbClient.componentDao().selectChildren(dbSession, branchUuid, components); } private List loadTreeOfComponents(DbSession dbSession, List touchedComponents) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/HotspotMeasureUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/HotspotMeasureUpdater.java deleted file mode 100644 index 8f624cb71e1..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/HotspotMeasureUpdater.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.measure.live; - -import java.util.Optional; -import org.sonar.api.issue.Issue; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.component.ComponentDto; - -import static org.sonar.server.security.SecurityReviewRating.computePercent; -import static org.sonar.server.security.SecurityReviewRating.computeRating; - -public class HotspotMeasureUpdater { - private final DbClient dbClient; - - public HotspotMeasureUpdater(DbClient dbClient) { - this.dbClient = dbClient; - } - public void apply(DbSession dbSession, MeasureMatrix matrix, ComponentIndex components, boolean useLeakFormulas, long beginningOfLeak) { - HotspotsCounter hotspotsCounter = new HotspotsCounter(dbClient.issueDao().selectBranchHotspotsCount(dbSession, components.getBranch().uuid(), beginningOfLeak)); - ComponentDto branch = components.getBranch(); - - long reviewed = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_REVIEWED, false); - long toReview = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false); - matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, reviewed); - matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, toReview); - Optional percent = computePercent(toReview, reviewed); - percent.ifPresent(p -> matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY, p)); - matrix.setValue(branch, CoreMetrics.SECURITY_REVIEW_RATING_KEY, computeRating(percent.orElse(null))); - - if (useLeakFormulas) { - reviewed = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_REVIEWED, true); - toReview = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true); - matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, reviewed); - matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, toReview); - percent = computePercent(toReview, reviewed); - percent.ifPresent(p -> matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, p)); - matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, computeRating(percent.orElse(null))); - } - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java index 595b3c627ed..3bd50d0463e 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java @@ -29,7 +29,6 @@ public class LiveMeasureModule extends Module { ComponentIndexFactory.class, LiveMeasureTreeUpdaterImpl.class, LiveMeasureComputerImpl.class, - HotspotMeasureUpdater.class, LiveQualityGateComputerImpl.class); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImpl.java index f362cf0b55d..58a85033757 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImpl.java @@ -22,6 +22,7 @@ package org.sonar.server.measure.live; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; import org.sonar.api.config.Configuration; import org.sonar.api.measures.Metric; @@ -36,17 +37,23 @@ import org.sonar.server.measure.DebtRatingGrid; import org.sonar.server.measure.Rating; import static com.google.common.base.Preconditions.checkState; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY; import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH; public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater { private final DbClient dbClient; private final MeasureUpdateFormulaFactory formulaFactory; - private final HotspotMeasureUpdater hotspotMeasureUpdater; - public LiveMeasureTreeUpdaterImpl(DbClient dbClient, MeasureUpdateFormulaFactory formulaFactory, HotspotMeasureUpdater hotspotMeasureUpdater) { + public LiveMeasureTreeUpdaterImpl(DbClient dbClient, MeasureUpdateFormulaFactory formulaFactory) { this.dbClient = dbClient; this.formulaFactory = formulaFactory; - this.hotspotMeasureUpdater = hotspotMeasureUpdater; } @Override @@ -59,12 +66,6 @@ public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater { // 2. aggregate new measures up the component tree updateMatrixWithHierarchy(measures, components, config, shouldUseLeakFormulas); - - // 3. Count hotspots at root level - // this is only necessary because the count of reviewed and to_review hotspots is only saved for the root (not for all components). - // For that reason, we can't incrementally generate the new counts up the tree. To have the correct numbers for the root component, we - // run this extra step that set the hotspots measures to the root based on the total count of hotspots. - hotspotMeasureUpdater.apply(dbSession, measures, components, shouldUseLeakFormulas, beginningOfLeak); } private void updateMatrixWithHierarchy(MeasureMatrix matrix, ComponentIndex components, Configuration config, boolean useLeakFormulas) { @@ -135,7 +136,7 @@ public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater { this.debtRatingGrid = debtRatingGrid; } - private void change(ComponentDto component, MeasureUpdateFormula formula) { + void change(ComponentDto component, MeasureUpdateFormula formula) { this.currentComponent = component; this.currentFormula = formula; } @@ -149,6 +150,65 @@ public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater { .collect(Collectors.toList()); } + /** + * Some child components may not have the measures 'SECURITY_HOTSPOTS_TO_REVIEW_STATUS' and 'SECURITY_HOTSPOTS_REVIEWED_STATUS' saved for them, + * so we may need to calculate them based on 'SECURITY_HOTSPOTS_REVIEWED' and 'SECURITY_HOTSPOTS'. + */ + @Override + public long getChildrenHotspotsReviewed() { + return getChildrenHotspotsReviewed(LiveMeasureDto::getValue, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_HOTSPOTS_KEY); + } + + /** + * Some child components may not have the measure 'SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY'. We assume that 'SECURITY_HOTSPOTS_KEY' has the same value. + */ + @Override + public long getChildrenHotspotsToReview() { + return componentIndex.getChildren(currentComponent) + .stream() + .map(c -> matrix.getMeasure(c, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).or(() -> matrix.getMeasure(c, SECURITY_HOTSPOTS_KEY))) + .mapToLong(lmOpt -> lmOpt.flatMap(lm -> Optional.ofNullable(lm.getValue())).orElse(0D).longValue()) + .sum(); + } + + @Override + public long getChildrenNewHotspotsReviewed() { + return getChildrenHotspotsReviewed(LiveMeasureDto::getVariation, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, NEW_SECURITY_HOTSPOTS_KEY); + } + + /** + * Some child components may not have the measure 'NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY'. We assume that 'NEW_SECURITY_HOTSPOTS_KEY' has the same value. + */ + @Override + public long getChildrenNewHotspotsToReview() { + return componentIndex.getChildren(currentComponent) + .stream() + .map(c -> matrix.getMeasure(c, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).or(() -> matrix.getMeasure(c, NEW_SECURITY_HOTSPOTS_KEY))) + .mapToLong(lmOpt -> lmOpt.flatMap(lm -> Optional.ofNullable(lm.getVariation())).orElse(0D).longValue()) + .sum(); + } + + private long getChildrenHotspotsReviewed(Function valueFunc, String metricKey, String percMetricKey, String hotspotsMetricKey) { + return componentIndex.getChildren(currentComponent) + .stream() + .mapToLong(c -> getHotspotsReviewed(c, valueFunc, metricKey, percMetricKey, hotspotsMetricKey)) + .sum(); + } + + private long getHotspotsReviewed(ComponentDto c, Function valueFunc, String metricKey, String percMetricKey, String hotspotsMetricKey) { + Optional measure = matrix.getMeasure(c, metricKey); + return measure.map(lm -> Optional.ofNullable(valueFunc.apply(lm)).orElse(0D).longValue()) + .orElseGet(() -> matrix.getMeasure(c, percMetricKey) + .flatMap(percentage -> matrix.getMeasure(c, hotspotsMetricKey) + .map(hotspots -> { + double perc = Optional.ofNullable(valueFunc.apply(percentage)).orElse(0D) / 100D; + double toReview = Optional.ofNullable(valueFunc.apply(hotspots)).orElse(0D); + double reviewed = (toReview * perc) / (1D - perc); + return Math.round(reviewed); + })) + .orElse(0L)); + } + public List getChildrenLeakValues() { List children = componentIndex.getChildren(currentComponent); return children.stream() diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormula.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormula.java index 420a5a94598..143f397e6cd 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormula.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/MeasureUpdateFormula.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.BiConsumer; +import java.util.stream.DoubleStream; import org.sonar.api.measures.Metric; import org.sonar.db.component.ComponentDto; import org.sonar.server.measure.DebtRatingGrid; @@ -79,6 +80,14 @@ class MeasureUpdateFormula { interface Context { List getChildrenValues(); + long getChildrenHotspotsReviewed(); + + long getChildrenHotspotsToReview(); + + long getChildrenNewHotspotsReviewed(); + + long getChildrenNewHotspotsToReview(); + List getChildrenLeakValues(); ComponentDto getComponent(); 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 02a0ee5a0f8..21f4e843b27 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 @@ -119,10 +119,12 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact new MeasureUpdateFormula(CoreMetrics.SECURITY_RATING, false, new MaxRatingChildren(), (context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, false).orElse(Severity.INFO)))), - new MeasureUpdateFormula(SECURITY_HOTSPOTS_REVIEWED_STATUS, false, new AddChildren(), + new MeasureUpdateFormula(SECURITY_HOTSPOTS_REVIEWED_STATUS, false, + (context, formula) -> context.setValue(context.getValue(SECURITY_HOTSPOTS_REVIEWED_STATUS).orElse(0D) + context.getChildrenHotspotsReviewed()), (context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false))), - new MeasureUpdateFormula(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, false, new AddChildren(), + new MeasureUpdateFormula(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, false, + (context, formula) -> context.setValue(context.getValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS).orElse(0D) + context.getChildrenHotspotsToReview()), (context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false))), new MeasureUpdateFormula(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, false, @@ -193,10 +195,12 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity)); }), - new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, true, new AddChildren(), + new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, true, + (context, formula) -> context.setLeakValue(context.getLeakValue(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS).orElse(0D) + context.getChildrenNewHotspotsReviewed()), (context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true))), - new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, true, new AddChildren(), + new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, true, + (context, formula) -> context.setLeakValue(context.getLeakValue(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS).orElse(0D) + context.getChildrenNewHotspotsToReview()), (context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true))), new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED, true, @@ -239,7 +243,7 @@ public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFact private static double newDebtDensity(MeasureUpdateFormula.Context context) { double debt = Math.max(context.getLeakValue(CoreMetrics.NEW_TECHNICAL_DEBT).orElse(0.0D), 0.0D); - Optional devCost = context.getText(CoreMetrics.NEW_DEVELOPMENT_COST).map(Double::parseDouble); + Optional devCost = context.getLeakValue(CoreMetrics.NEW_DEVELOPMENT_COST); if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) { return debt / devCost.get(); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/HotspotMeasureUpdaterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/HotspotMeasureUpdaterTest.java deleted file mode 100644 index 64a876b115c..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/HotspotMeasureUpdaterTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.measure.live; - -import java.util.Date; -import java.util.List; -import org.assertj.core.data.Offset; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.ComponentTesting; -import org.sonar.db.metric.MetricDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY; -import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY; -import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY; -import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY; -import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY; -import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING_KEY; - -public class HotspotMeasureUpdaterTest { - @Rule - public DbTester db = DbTester.create(); - - private final HotspotMeasureUpdater hotspotMeasureUpdater = new HotspotMeasureUpdater(db.getDbClient()); - private ComponentIndexImpl componentIndex; - private MeasureMatrix matrix; - private ComponentDto project; - private ComponentDto dir; - private ComponentDto file1; - private ComponentDto file2; - - @Before - public void setUp() { - // insert project and file structure - project = db.components().insertPrivateProject(); - dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java")); - file1 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir)); - file2 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir)); - - // other needed data - matrix = new MeasureMatrix(List.of(project, dir, file1, file2), insertHotspotMetrics(), List.of()); - componentIndex = new ComponentIndexImpl(db.getDbClient()); - } - - private List insertHotspotMetrics() { - return List.of( - db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)), - db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)), - db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_REVIEWED_KEY)), - db.measures().insertMetric(m -> m.setKey(SECURITY_REVIEW_RATING_KEY)), - - db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)), - db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)), - db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)), - db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_REVIEW_RATING_KEY)) - ); - } - - @Test - public void should_count_hotspots_for_root() { - db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(999)).setStatus("TO_REVIEW")); - db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(999)).setStatus("REVIEWED")); - - db.issues().insertHotspot(project, dir, i -> i.setIssueCreationDate(new Date(1001)).setStatus("TO_REVIEW")); - db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1002)).setStatus("REVIEWED")); - db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1003)).setStatus("REVIEWED")); - - componentIndex.load(db.getSession(), List.of(file1)); - hotspotMeasureUpdater.apply(db.getSession(), matrix, componentIndex, true, 1000L); - - assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY).get().getValue()).isEqualTo(3d); - assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).get().getValue()).isEqualTo(2d); - assertThat(matrix.getMeasure(project, SECURITY_REVIEW_RATING_KEY).get().getDataAsString()).isEqualTo("C"); - assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_REVIEWED_KEY).get().getValue()).isEqualTo(60d); - - assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY).get().getVariation()).isEqualTo(2d); - assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).get().getVariation()).isEqualTo(1d); - assertThat(matrix.getMeasure(project, NEW_SECURITY_REVIEW_RATING_KEY).get().getVariation()).isEqualTo(3); - assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).get().getVariation()).isCloseTo(66d, Offset.offset(1d)); - } - - @Test - public void dont_create_leak_measures_if_no_leak_period() { - db.issues().insertHotspot(project, dir, i -> i.setIssueCreationDate(new Date(1001)).setStatus("TO_REVIEW")); - db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1002)).setStatus("REVIEWED")); - db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1003)).setStatus("REVIEWED")); - - componentIndex.load(db.getSession(), List.of(file1)); - hotspotMeasureUpdater.apply(db.getSession(), matrix, componentIndex, false, 1000L); - - assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)).isEmpty(); - assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)).isEmpty(); - assertThat(matrix.getMeasure(project, NEW_SECURITY_REVIEW_RATING_KEY)).isEmpty(); - assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).isEmpty(); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImplTest.java index 2f0413de2e9..ab72d88b161 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/LiveMeasureTreeUpdaterImplTest.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.sonar.api.config.Configuration; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.measures.Metric; +import org.sonar.api.rules.RuleType; import org.sonar.db.DbTester; import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchType; @@ -40,19 +41,24 @@ import org.sonar.db.metric.MetricDto; import org.sonar.db.newcodeperiod.NewCodePeriodType; import org.sonar.db.rule.RuleDto; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.sonar.api.CoreProperties.RATING_GRID; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY; +import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY; public class LiveMeasureTreeUpdaterImplTest { @Rule public DbTester db = DbTester.create(); private final Configuration config = new MapSettings().setProperty(RATING_GRID, "0.05,0.1,0.2,0.5").asConfig(); - private final HotspotMeasureUpdater hotspotMeasureUpdater = mock(HotspotMeasureUpdater.class); private LiveMeasureTreeUpdaterImpl treeUpdater; private ComponentIndexImpl componentIndex; private MeasureMatrix matrix; @@ -84,7 +90,7 @@ public class LiveMeasureTreeUpdaterImplTest { @Test public void should_aggregate_values_up_the_hierarchy() { snapshot = db.components().insertSnapshot(project); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new AggregateValuesFormula(), hotspotMeasureUpdater); + treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new AggregateValuesFormula()); componentIndex.load(db.getSession(), List.of(file1)); List initialValues = List.of( @@ -106,7 +112,7 @@ public class LiveMeasureTreeUpdaterImplTest { @Test public void should_set_values_up_the_hierarchy() { snapshot = db.components().insertSnapshot(project); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new SetValuesFormula(), hotspotMeasureUpdater); + treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new SetValuesFormula()); componentIndex.load(db.getSession(), List.of(file1)); treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix); @@ -121,7 +127,7 @@ public class LiveMeasureTreeUpdaterImplTest { @Test public void dont_use_leak_formulas_if_no_period() { snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(null)); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater); + treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak()); componentIndex.load(db.getSession(), List.of(file1)); treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix); @@ -133,7 +139,7 @@ public class LiveMeasureTreeUpdaterImplTest { public void use_leak_formulas_if_pr() { snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(null)); branch.setBranchType(BranchType.PULL_REQUEST); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater); + treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak()); componentIndex.load(db.getSession(), List.of(file1)); treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix); @@ -144,7 +150,7 @@ public class LiveMeasureTreeUpdaterImplTest { @Test public void calculate_new_metrics_if_using_new_code_branch_reference() { snapshot = db.components().insertSnapshot(project, s -> s.setPeriodMode(NewCodePeriodType.REFERENCE_BRANCH.name())); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater); + treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak()); componentIndex.load(db.getSession(), List.of(file1)); treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix); @@ -155,9 +161,9 @@ public class LiveMeasureTreeUpdaterImplTest { @Test public void issue_counter_based_on_new_code_branch_reference() { snapshot = db.components().insertSnapshot(project, s -> s.setPeriodMode(NewCodePeriodType.REFERENCE_BRANCH.name())); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater); + treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak()); - RuleDto rule = db.rules().insert(); + RuleDto rule = db.rules().insert(r -> r.setType(RuleType.BUG)); IssueDto issue1 = db.issues().insertIssue(rule, project, file1); IssueDto issue2 = db.issues().insertIssue(rule, project, file1); db.issues().insertNewCodeReferenceIssue(issue1); @@ -170,7 +176,7 @@ public class LiveMeasureTreeUpdaterImplTest { @Test public void issue_counter_uses_begin_of_leak() { snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(1000L)); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater); + treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak()); db.issues().insertIssue(i -> i.setIssueCreationDate(new Date(999)).setComponentUuid(file1.uuid())); db.issues().insertIssue(i -> i.setIssueCreationDate(new Date(1001)).setComponentUuid(file1.uuid())); @@ -183,13 +189,64 @@ public class LiveMeasureTreeUpdaterImplTest { } @Test - public void calls_hotspot_updater() { - snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(1000L)); + public void context_calculates_hotspot_counts_from_percentage() { + List metrics = List.of(new MetricDto().setKey(SECURITY_HOTSPOTS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_KEY), + new MetricDto().setKey(SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)); + componentIndex.load(db.getSession(), List.of(file1)); + matrix = new MeasureMatrix(List.of(project, dir, file1, file2), metrics, List.of()); + + LiveMeasureTreeUpdaterImpl.FormulaContextImpl context = new LiveMeasureTreeUpdaterImpl.FormulaContextImpl(matrix, componentIndex, null); + matrix.setValue(file1, SECURITY_HOTSPOTS_KEY, 4d); + matrix.setValue(file1, SECURITY_HOTSPOTS_REVIEWED_KEY, 33d); + + matrix.setValue(file2, SECURITY_HOTSPOTS_KEY, 2d); + matrix.setValue(file2, SECURITY_HOTSPOTS_REVIEWED_KEY, 50d); + + context.change(dir, null); + assertThat(context.getChildrenHotspotsToReview()).isEqualTo(6); + assertThat(context.getChildrenHotspotsReviewed()).isEqualTo(4); + } + @Test + public void context_calculates_new_hotspot_counts_from_percentage() { + List metrics = List.of(new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_KEY), new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY), + new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY), new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)); componentIndex.load(db.getSession(), List.of(file1)); - treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater); - treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix); - verify(hotspotMeasureUpdater).apply(eq(db.getSession()), any(), eq(componentIndex), eq(true), eq(1000L)); + matrix = new MeasureMatrix(List.of(project, dir, file1, file2), metrics, List.of()); + + LiveMeasureTreeUpdaterImpl.FormulaContextImpl context = new LiveMeasureTreeUpdaterImpl.FormulaContextImpl(matrix, componentIndex, null); + matrix.setLeakValue(file1, NEW_SECURITY_HOTSPOTS_KEY, 4d); + matrix.setLeakValue(file1, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, 33d); + + matrix.setLeakValue(file2, NEW_SECURITY_HOTSPOTS_KEY, 2d); + matrix.setLeakValue(file2, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, 50d); + + context.change(dir, null); + assertThat(context.getChildrenNewHotspotsToReview()).isEqualTo(6); + assertThat(context.getChildrenNewHotspotsReviewed()).isEqualTo(4); + } + + @Test + public void context_returns_hotspots_counts_from_measures() { + List metrics = List.of(new MetricDto().setKey(SECURITY_HOTSPOTS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_KEY), + new MetricDto().setKey(SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)); + componentIndex.load(db.getSession(), List.of(file1)); + matrix = new MeasureMatrix(List.of(project, dir, file1, file2), metrics, List.of()); + + LiveMeasureTreeUpdaterImpl.FormulaContextImpl context = new LiveMeasureTreeUpdaterImpl.FormulaContextImpl(matrix, componentIndex, null); + matrix.setValue(file1, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, 5D); + matrix.setValue(file1, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, 5D); + matrix.setValue(file1, SECURITY_HOTSPOTS_KEY, 6d); + matrix.setValue(file1, SECURITY_HOTSPOTS_REVIEWED_KEY, 33d); + + matrix.setValue(file2, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, 5D); + matrix.setValue(file2, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, 5D); + matrix.setValue(file2, SECURITY_HOTSPOTS_KEY, 4d); + matrix.setValue(file2, SECURITY_HOTSPOTS_REVIEWED_KEY, 50d); + + context.change(dir, null); + assertThat(context.getChildrenHotspotsToReview()).isEqualTo(10); + assertThat(context.getChildrenHotspotsReviewed()).isEqualTo(10); } private class AggregateValuesFormula implements MeasureUpdateFormulaFactory { @@ -205,6 +262,18 @@ public class LiveMeasureTreeUpdaterImplTest { } } + private class NoOpFormula implements MeasureUpdateFormulaFactory { + + @Override + public List getFormulas() { + return emptyList(); + } + + @Override public Set getFormulaMetrics() { + return emptySet(); + } + } + private class SetValuesFormula implements MeasureUpdateFormulaFactory { @Override public List getFormulas() { 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 bf7548dbb6e..c68cc2c8813 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 @@ -102,6 +102,26 @@ public class MeasureUpdateFormulaFactoryImplTest { @Test public void hierarchy_combining_other_metrics() { + new HierarchyTester(SECURITY_HOTSPOTS_TO_REVIEW_STATUS) + .withValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d) + .withChildrenHotspotsCounts(10, 10, 2, 10) + .expectedResult(3d); + + new HierarchyTester(SECURITY_HOTSPOTS_REVIEWED_STATUS) + .withValue(SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d) + .withChildrenHotspotsCounts(2, 10, 10, 10) + .expectedResult(3d); + + new HierarchyTester(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS) + .withValue(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d) + .withChildrenHotspotsCounts(10, 10, 10, 2) + .expectedResult(3d); + + new HierarchyTester(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS) + .withValue(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d) + .withChildrenHotspotsCounts(10, 2, 10, 10) + .expectedResult(3d); + new HierarchyTester(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED) .withValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d) .withValue(SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d) @@ -818,44 +838,44 @@ public class MeasureUpdateFormulaFactoryImplTest { .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0) - .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "160") + .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 160.0) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 12.5) .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.C); withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0) - .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "10") + .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 10.0D) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 200.0) .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.E); // A is 5% --> min debt is exactly 200*0.05=10 - with(CoreMetrics.NEW_DEVELOPMENT_COST, "200") + withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 200.0) .andLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 10.0) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 5.0) .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0) - .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "0") + .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0) - .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "80") + .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0); withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0) - .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "0") + .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); // bug, debt can't be negative withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0) - .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "80") + .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); // bug, cost can't be negative withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0) - .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "-80") + .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, -80.0) .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0) .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A); } @@ -992,11 +1012,33 @@ public class MeasureUpdateFormulaFactoryImplTest { this.initialValues = initialValues; } - @Override public List getChildrenValues() { + @Override + public List getChildrenValues() { return initialValues.childrenValues; } - @Override public List getChildrenLeakValues() { + @Override + public long getChildrenHotspotsReviewed() { + return initialValues.childrenHotspotsReviewed; + } + + @Override + public long getChildrenHotspotsToReview() { + return initialValues.childrenHotspotsToReview; + } + + @Override + public long getChildrenNewHotspotsReviewed() { + return initialValues.childrenNewHotspotsReviewed; + } + + @Override + public long getChildrenNewHotspotsToReview() { + return initialValues.childrenNewHotspotsToReview; + } + + @Override + public List getChildrenLeakValues() { return initialValues.childrenLeakValues; } @@ -1067,6 +1109,11 @@ public class MeasureUpdateFormulaFactoryImplTest { private final List childrenValues = new ArrayList<>(); private final List childrenLeakValues = new ArrayList<>(); private final Map text = new HashMap<>(); + private long childrenHotspotsReviewed = 0; + private long childrenNewHotspotsReviewed = 0; + private long childrenHotspotsToReview = 0; + private long childrenNewHotspotsToReview = 0; + } private class HierarchyTester { @@ -1089,6 +1136,15 @@ public class MeasureUpdateFormulaFactoryImplTest { return this; } + public HierarchyTester withChildrenHotspotsCounts(long childrenHotspotsReviewed, long childrenNewHotspotsReviewed, long childrenHotspotsToReview, + long childrenNewHotspotsToReview) { + this.initialValues.childrenHotspotsReviewed = childrenHotspotsReviewed; + this.initialValues.childrenNewHotspotsReviewed = childrenNewHotspotsReviewed; + this.initialValues.childrenHotspotsToReview = childrenHotspotsToReview; + this.initialValues.childrenNewHotspotsToReview = childrenNewHotspotsToReview; + return this; + } + public HierarchyTester withValue(Double value) { return withValue(metric, value); } -- 2.39.5