From a6f8589f7015886ba1a325ce6955d27d65da07cf Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 3 Jul 2015 16:46:05 +0200 Subject: [PATCH] SONAR-6680 Create step to compute quality profile measure in compute engine --- .../computation/step/ComputationSteps.java | 3 +- .../step/ComputeQProfileMeasureStep.java | 119 +++++++++++++ .../step/QualityProfileEventsStep.java | 6 +- .../step/ComputeQProfileMeasureStepTest.java | 159 ++++++++++++++++++ 4 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeQProfileMeasureStep.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeQProfileMeasureStepTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java index 1d7f0e161e5..46c8c4b9241 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java @@ -65,7 +65,8 @@ public class ComputationSteps { // Must be executed after computation of differential measures QualityGateMeasuresStep.class, - + ComputeQProfileMeasureStep.class, + // Must be executed after computation of quality profile measure QualityProfileEventsStep.class, // Must be executed after computation of quality gate measure diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeQProfileMeasureStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeQProfileMeasureStep.java new file mode 100644 index 00000000000..5f5dbcce21b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeQProfileMeasureStep.java @@ -0,0 +1,119 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.step; + +import com.google.common.base.Optional; +import java.util.HashMap; +import java.util.Map; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.PathAwareVisitor; +import org.sonar.server.computation.component.TreeRootHolder; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.qualityprofile.QPMeasureData; +import org.sonar.server.computation.qualityprofile.QualityProfile; + +import static org.sonar.server.computation.component.Component.Type.MODULE; +import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; + +/** + * Aggregates quality profile on lower-level module nodes on their parent modules and project + */ +public class ComputeQProfileMeasureStep implements ComputationStep { + + private final TreeRootHolder treeRootHolder; + private final MeasureRepository measureRepository; + private final MetricRepository metricRepository; + + public ComputeQProfileMeasureStep(TreeRootHolder treeRootHolder, MeasureRepository measureRepository, MetricRepository metricRepository) { + this.treeRootHolder = treeRootHolder; + this.measureRepository = measureRepository; + this.metricRepository = metricRepository; + } + + @Override + public void execute() { + Metric qProfilesMetric = metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY); + new NewCoverageAggregationComponentVisitor(qProfilesMetric).visit(treeRootHolder.getRoot()); + } + + private class NewCoverageAggregationComponentVisitor extends PathAwareVisitor { + + private final Metric qProfilesMetric; + + public NewCoverageAggregationComponentVisitor(Metric qProfilesMetric) { + super(MODULE, POST_ORDER, new SimpleStackElementFactory() { + @Override + public QProfiles createForAny(Component component) { + return new QProfiles(); + } + }); + this.qProfilesMetric = qProfilesMetric; + } + + @Override + protected void visitProject(Component project, Path path) { + addMeasure(project, path.current()); + } + + @Override + protected void visitModule(Component module, Path path) { + Optional measure = measureRepository.getRawMeasure(module, qProfilesMetric); + QProfiles qProfiles = path.current(); + if (measure.isPresent()) { + qProfiles.add(measure.get()); + } else { + addMeasure(module, path.current()); + } + path.parent().add(qProfiles); + } + + private void addMeasure(Component component, QProfiles qProfiles) { + if (!qProfiles.profilesByKey.isEmpty()) { + measureRepository.add(component, qProfilesMetric, qProfiles.createMeasure()); + } + } + } + + private static class QProfiles { + private final Map profilesByKey = new HashMap<>(); + + public void add(Measure measure) { + profilesByKey.putAll(QPMeasureData.fromJson(measure.getStringValue()).getProfilesByKey()); + } + + public void add(QProfiles qProfiles) { + profilesByKey.putAll(qProfiles.profilesByKey); + } + + public Measure createMeasure() { + return Measure.newMeasureBuilder().create(QPMeasureData.toJson(new QPMeasureData(profilesByKey.values()))); + } + } + + @Override + public String getDescription() { + return "Computes Quality Profile measures"; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityProfileEventsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityProfileEventsStep.java index 20982691441..e8541194f51 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityProfileEventsStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityProfileEventsStep.java @@ -44,6 +44,11 @@ import org.sonar.server.computation.qualityprofile.QualityProfile; import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; +/** + * Computation of quality profile events + * + * As it depends upon {@link CoreMetrics#QUALITY_PROFILES_KEY}, it must be executed after {@link ComputeQProfileMeasureStep} + */ public class QualityProfileEventsStep implements ComputationStep { private final TreeRootHolder treeRootHolder; private final MetricRepository metricRepository; @@ -61,7 +66,6 @@ public class QualityProfileEventsStep implements ComputationStep { this.languageRepository = languageRepository; } - @Override public void execute() { new DepthTraversalTypeAwareVisitor(Component.Type.PROJECT, POST_ORDER) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeQProfileMeasureStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeQProfileMeasureStepTest.java new file mode 100644 index 00000000000..e2b96d9fdd8 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeQProfileMeasureStepTest.java @@ -0,0 +1,159 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.step; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.DumbComponent; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepositoryRule; +import org.sonar.server.computation.metric.MetricRepositoryRule; +import org.sonar.server.computation.qualityprofile.QPMeasureData; +import org.sonar.server.computation.qualityprofile.QualityProfile; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; +import static org.sonar.server.computation.component.Component.Type.MODULE; +import static org.sonar.server.computation.component.Component.Type.PROJECT; +import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; +import static org.sonar.server.computation.measure.MeasureRepoEntry.entryOf; +import static org.sonar.server.computation.measure.MeasureRepoEntry.toEntries; + +public class ComputeQProfileMeasureStepTest { + + private static final String QP_NAME_1 = "qp1"; + private static final String QP_NAME_2 = "qp1"; + private static final String LANGUAGE_KEY_1 = "language_key1"; + private static final String LANGUAGE_KEY_2 = "language_key2"; + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + @Rule + public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(CoreMetrics.QUALITY_PROFILES); + + @Rule + public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + + ComputeQProfileMeasureStep sut; + + @Before + public void setUp() throws Exception { + sut = new ComputeQProfileMeasureStep(treeRootHolder, measureRepository, metricRepository); + } + + @Test + public void add_quality_profile_measure_on_project() throws Exception { + DumbComponent project = DumbComponent.builder(PROJECT, 1) + .addChildren( + DumbComponent.builder(MODULE, 11) + .addChildren( + DumbComponent.builder(MODULE, 111).build() + ) + .build() + ).build(); + + treeRootHolder.setRoot(project); + + QualityProfile qp = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); + addMeasure(111, qp); + + sut.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(1))).containsOnly(entryOf(CoreMetrics.QUALITY_PROFILES_KEY, newMeasureBuilder().create(toJson(qp)))); + } + + @Test + public void add_quality_profile_measure_from_multiple_modules() throws Exception { + DumbComponent project = DumbComponent.builder(PROJECT, 1) + .addChildren( + DumbComponent.builder(MODULE, 11) + .addChildren( + DumbComponent.builder(MODULE, 111).build() + ) + .build(), + DumbComponent.builder(MODULE, 12).build() + ).build(); + + treeRootHolder.setRoot(project); + + QualityProfile qp1 = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); + addMeasure(111, qp1); + QualityProfile qp2 = createQProfile(QP_NAME_2, LANGUAGE_KEY_2); + addMeasure(12, qp2); + + sut.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(1))).containsOnly(entryOf(CoreMetrics.QUALITY_PROFILES_KEY, newMeasureBuilder().create(toJson(qp1, qp2)))); + } + + @Test + public void nothing_to_add_when_no_measure() throws Exception { + DumbComponent project = DumbComponent.builder(PROJECT, 1) + .addChildren( + DumbComponent.builder(MODULE, 11) + .addChildren( + DumbComponent.builder(MODULE, 111).build() + ) + .build() + ).build(); + + treeRootHolder.setRoot(project); + + sut.execute(); + + assertThat(measureRepository.getNewRawMeasures(1)).isEmpty(); + } + + @Test + public void nothing_to_add_when_measure_already_exists_on_project() throws Exception { + DumbComponent project = DumbComponent.builder(PROJECT, 1).build(); + + treeRootHolder.setRoot(project); + + QualityProfile qp = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); + addMeasure(1, qp); + + sut.execute(); + + assertThat(measureRepository.getNewRawMeasures(1)).isEmpty(); + } + + private static QualityProfile createQProfile(String qpName, String languageKey) { + return new QualityProfile(qpName + "-" + languageKey, qpName, languageKey, new Date()); + } + + private void addMeasure(int componentRef, QualityProfile... qps) { + Measure qualityProfileMeasure = newMeasureBuilder().create(toJson(qps)); + measureRepository.addRawMeasure(componentRef, CoreMetrics.QUALITY_PROFILES_KEY, qualityProfileMeasure); + } + + private static String toJson(QualityProfile... qps) { + List qualityProfiles = Arrays.asList(qps); + return QPMeasureData.toJson(new QPMeasureData(qualityProfiles)); + } +} -- 2.39.5