From 14a416be493074ac409e3d753b56fb06048624e1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Fri, 26 Jun 2015 18:56:40 +0200 Subject: [PATCH] SONAR-6645 move New Coverage computation to Compute Engine --- .../container/ComputeEngineContainerImpl.java | 4 + .../newcoverage/FileCoverageMetricKeys.java | 59 +++ .../newcoverage/ItFileCoverageMetricKeys.java | 59 +++ .../newcoverage/NewCoverageMetricKeys.java | 41 ++ .../NewCoverageMetricKeysModule.java | 32 ++ .../OverallFileCoverageMetricKeys.java | 59 +++ .../measure/newcoverage/package-info.java | 24 ++ .../computation/step/ComputationSteps.java | 2 + .../step/NewCoverageAggregationStep.java | 249 +++++++++++++ .../step/NewCoverageMeasuresStep.java | 352 ++++++++++++++++++ .../measure/MeasureRepositoryRule.java | 4 +- .../step/NewCoverageAggregationStepTest.java | 293 +++++++++++++++ .../step/NewCoverageMeasuresStepTest.java | 334 +++++++++++++++++ 13 files changed, 1510 insertions(+), 2 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/FileCoverageMetricKeys.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/ItFileCoverageMetricKeys.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeys.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeysModule.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/OverallFileCoverageMetricKeys.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/package-info.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageAggregationStep.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageMeasuresStep.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageAggregationStepTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageMeasuresStepTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java index 2c7e62a7c88..dad331ca5ff 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java @@ -51,6 +51,7 @@ import org.sonar.server.computation.issue.ScmAccountCacheLoader; import org.sonar.server.computation.issue.SourceLinesCache; import org.sonar.server.computation.language.LanguageRepositoryImpl; import org.sonar.server.computation.measure.MeasureRepositoryImpl; +import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeysModule; import org.sonar.server.computation.metric.MetricRepositoryImpl; import org.sonar.server.computation.period.PeriodsHolderImpl; import org.sonar.server.computation.qualitygate.EvaluationResultTextConverterImpl; @@ -149,6 +150,9 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co QualityGateServiceImpl.class, EvaluationResultTextConverterImpl.class, + // new coverage measures + NewCoverageMetricKeysModule.class, + // issues ScmAccountCacheLoader.class, ScmAccountCache.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/FileCoverageMetricKeys.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/FileCoverageMetricKeys.java new file mode 100644 index 00000000000..004abe6573e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/FileCoverageMetricKeys.java @@ -0,0 +1,59 @@ +/* + * 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.measure.newcoverage; + +import org.sonar.api.measures.CoreMetrics; + +public class FileCoverageMetricKeys implements NewCoverageMetricKeys { + @Override + public String coverageLineHitsData() { + return CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY; + } + + @Override + public String conditionsByLine() { + return CoreMetrics.CONDITIONS_BY_LINE_KEY; + } + + @Override + public String coveredConditionsByLine() { + return CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY; + } + + @Override + public String newLinesToCover() { + return CoreMetrics.NEW_LINES_TO_COVER_KEY; + } + + @Override + public String newUncoveredLines() { + return CoreMetrics.NEW_UNCOVERED_LINES_KEY; + } + + @Override + public String newConditionsToCover() { + return CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY; + } + + @Override + public String newUncoveredConditions() { + return CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/ItFileCoverageMetricKeys.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/ItFileCoverageMetricKeys.java new file mode 100644 index 00000000000..e6676fac7fc --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/ItFileCoverageMetricKeys.java @@ -0,0 +1,59 @@ +/* + * 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.measure.newcoverage; + +import org.sonar.api.measures.CoreMetrics; + +public class ItFileCoverageMetricKeys implements NewCoverageMetricKeys { + @Override + public String coverageLineHitsData() { + return CoreMetrics.IT_COVERAGE_LINE_HITS_DATA_KEY; + } + + @Override + public String conditionsByLine() { + return CoreMetrics.IT_CONDITIONS_BY_LINE_KEY; + } + + @Override + public String coveredConditionsByLine() { + return CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE_KEY; + } + + @Override + public String newLinesToCover() { + return CoreMetrics.NEW_IT_LINES_TO_COVER_KEY; + } + + @Override + public String newUncoveredLines() { + return CoreMetrics.NEW_IT_UNCOVERED_LINES_KEY; + } + + @Override + public String newConditionsToCover() { + return CoreMetrics.NEW_IT_CONDITIONS_TO_COVER_KEY; + } + + @Override + public String newUncoveredConditions() { + return CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS_KEY; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeys.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeys.java new file mode 100644 index 00000000000..7aa98942aaa --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeys.java @@ -0,0 +1,41 @@ +/* + * 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.measure.newcoverage; + +/** + * Holds the keys of the 3 metrics from which are computed 4 new coverage metrics. + */ +public interface NewCoverageMetricKeys { + /* input metrics */ + String coverageLineHitsData(); + + String conditionsByLine(); + + String coveredConditionsByLine(); + + /* output metrics */ + String newLinesToCover(); + + String newUncoveredLines(); + + String newConditionsToCover(); + + String newUncoveredConditions(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeysModule.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeysModule.java new file mode 100644 index 00000000000..35014bb71ce --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeysModule.java @@ -0,0 +1,32 @@ +/* + * 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.measure.newcoverage; + +import org.sonar.core.component.Module; + +public class NewCoverageMetricKeysModule extends Module { + @Override + protected void configureModule() { + add( + FileCoverageMetricKeys.class, + ItFileCoverageMetricKeys.class, + OverallFileCoverageMetricKeys.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/OverallFileCoverageMetricKeys.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/OverallFileCoverageMetricKeys.java new file mode 100644 index 00000000000..9297fa7572f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/OverallFileCoverageMetricKeys.java @@ -0,0 +1,59 @@ +/* + * 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.measure.newcoverage; + +import org.sonar.api.measures.CoreMetrics; + +public class OverallFileCoverageMetricKeys implements NewCoverageMetricKeys { + @Override + public String coverageLineHitsData() { + return CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA_KEY; + } + + @Override + public String conditionsByLine() { + return CoreMetrics.OVERALL_CONDITIONS_BY_LINE_KEY; + } + + @Override + public String coveredConditionsByLine() { + return CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE_KEY; + } + + @Override + public String newLinesToCover() { + return CoreMetrics.NEW_OVERALL_LINES_TO_COVER_KEY; + } + + @Override + public String newUncoveredLines() { + return CoreMetrics.NEW_OVERALL_UNCOVERED_LINES_KEY; + } + + @Override + public String newConditionsToCover() { + return CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER_KEY; + } + + @Override + public String newUncoveredConditions() { + return CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS_KEY; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/package-info.java new file mode 100644 index 00000000000..656c487e8bd --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.computation.measure.newcoverage; + +import javax.annotation.ParametersAreNonnullByDefault; 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 d4e6d6fa0ff..a40b9e94fda 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 @@ -57,6 +57,8 @@ public class ComputationSteps { CustomMeasuresCopyStep.class, ComputeIssueMeasuresStep.class, SqaleMeasuresStep.class, + NewCoverageMeasuresStep.class, + NewCoverageAggregationStep.class, // Must be executed after computation of all measures FillMeasuresWithVariationsStep.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageAggregationStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageAggregationStep.java new file mode 100644 index 00000000000..91f878d99c5 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageAggregationStep.java @@ -0,0 +1,249 @@ +/* + * 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.Function; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +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.measure.MeasureVariations; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolder; + +import static com.google.common.collect.FluentIterable.from; +import static org.sonar.server.computation.component.Component.Type.FILE; +import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; +import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; + +public class NewCoverageAggregationStep implements ComputationStep { + private static final List AGGREGATED_METRIC_KEYS = ImmutableList.of( + CoreMetrics.NEW_LINES_TO_COVER_KEY, + CoreMetrics.NEW_UNCOVERED_LINES_KEY, + CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY, + CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY, + CoreMetrics.NEW_IT_LINES_TO_COVER_KEY, + CoreMetrics.NEW_IT_UNCOVERED_LINES_KEY, + CoreMetrics.NEW_IT_CONDITIONS_TO_COVER_KEY, + CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS_KEY, + CoreMetrics.NEW_OVERALL_LINES_TO_COVER_KEY, + CoreMetrics.NEW_OVERALL_UNCOVERED_LINES_KEY, + CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER_KEY, + CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS_KEY + ); + private static final VariationBuildersFactory VARIATION_BUILDERS_FACTORY = new VariationBuildersFactory(); + + private final TreeRootHolder treeRootHolder; + private final PeriodsHolder periodsHolder; + private final MeasureRepository measureRepository; + private final List metrics; + + public NewCoverageAggregationStep(TreeRootHolder treeRootHolder, PeriodsHolder periodsHolder, + final MetricRepository metricRepository, MeasureRepository measureRepository) { + this.treeRootHolder = treeRootHolder; + this.periodsHolder = periodsHolder; + this.measureRepository = measureRepository; + this.metrics = from(AGGREGATED_METRIC_KEYS) + .transform(new Function() { + @Override + @Nonnull + public Metric apply(@Nonnull String input) { + return metricRepository.getByKey(input); + } + }).toList(); + } + + @Override + public void execute() { + new NewCoverageAggregationComponentVisitor() + .visit(treeRootHolder.getRoot()); + } + + private final class NewCoverageAggregationComponentVisitor extends PathAwareVisitor { + public NewCoverageAggregationComponentVisitor() { + super(FILE, POST_ORDER, VARIATION_BUILDERS_FACTORY); + } + + @Override + protected void visitProject(Component project, Path path) { + addNewMeasures(project, path.current()); + } + + @Override + protected void visitModule(Component module, Path path) { + addNewMeasures(module, path.current()); + updateParentVariations(path); + } + + @Override + protected void visitDirectory(Component directory, Path path) { + addNewMeasures(directory, path.current()); + updateParentVariations(path); + } + + @Override + protected void visitFile(Component file, Path path) { + initVariationBuilders(file, path.parent()); + } + } + + /** + * Creates a new VariationBuilders instance for all Component types except FILE. + */ + private static class VariationBuildersFactory extends PathAwareVisitor.SimpleStackElementFactory { + @Override + public VariationBuilders createForAny(Component component) { + return new VariationBuilders(); + } + + @Override + public VariationBuilders createForFile(Component file) { + return null; + } + } + + private void initVariationBuilders(Component file, VariationBuilders builders) { + for (Metric metric : metrics) { + Optional measureOptional = measureRepository.getRawMeasure(file, metric); + if (!measureOptional.isPresent()) { + continue; + } + Measure measure = measureOptional.get(); + if (!measure.hasVariations() || measure.getValueType() != Measure.ValueType.NO_VALUE) { + continue; + } + + builders.getOrCreateBuilder(metric.getKey()).incrementVariationsBy(measure.getVariations(), periodsHolder); + } + } + + private void addNewMeasures(Component component, VariationBuilders current) { + for (Metric metric : metrics) { + Optional builder = current.getBuilder(metric.getKey()); + if (builder.isPresent() && builder.get().hasVariation()) { + measureRepository.add(component, metric, newMeasureBuilder().setVariations(builder.get().build()).createNoValue()); + } + } + } + + private void updateParentVariations(PathAwareVisitor.Path path) { + path.parent().add(path.current()); + } + + /** + * Holds a VariationBuilder instance for each Metric for which the aggregation is computed in this step + */ + private static final class VariationBuilders { + private final Map builders; + + public VariationBuilders() { + this.builders = new HashMap<>(AGGREGATED_METRIC_KEYS.size()); + } + + public VariationBuilder getOrCreateBuilder(String metricKey) { + VariationBuilder builder = this.builders.get(metricKey); + if (builder == null) { + builder = new VariationBuilder(); + this.builders.put(metricKey, builder); + } + return builder; + } + + public Optional getBuilder(String metricKey) { + return Optional.fromNullable(this.builders.get(metricKey)); + } + + public void add(VariationBuilders current) { + for (Map.Entry entry : current.builders.entrySet()) { + VariationBuilder builder = getOrCreateBuilder(entry.getKey()); + builder.incrementVariationsBy(entry.getValue()); + } + } + + } + + /** + * This class holds the variations for a specific Component in the tree and for a specific Metric. + * It is mutable and exposes methods to easily increment variations from either a specific + * MeasureVariations instance. + * This class stores variations as int and makes calculations on int values because all metrics actually have int + * values in their variations. + */ + private static final class VariationBuilder { + private final Integer[] variations = new Integer[5]; + + public void incrementVariationsBy(MeasureVariations variations, PeriodsHolder periodsHolder) { + for (Period period : periodsHolder.getPeriods()) { + if (variations.hasVariation(period.getIndex())) { + increment(period.getIndex() - 1, (int) variations.getVariation(period.getIndex())); + } + } + } + + public boolean hasVariation() { + for (@Nullable Integer variation : variations) { + if (variation != null) { + return true; + } + } + return false; + } + + public void incrementVariationsBy(VariationBuilder variationBuilder) { + for (int i = 0; i < variationBuilder.variations.length; i++) { + increment(i, variationBuilder.variations[i]); + } + } + + private void increment(int arrayIndex, @Nullable Integer value) { + if (value == null) { + return; + } + if (variations[arrayIndex] == null) { + variations[arrayIndex] = 0; + } + variations[arrayIndex] += value; + } + + public MeasureVariations build() { + Double[] res = new Double[variations.length]; + for (int i = 0; i < variations.length; i++) { + res[i] = variations[i] == null ? null : (double) variations[i]; + } + return new MeasureVariations(res); + } + } + + @Override + public String getDescription() { + return "Aggregates New Coverage measures"; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageMeasuresStep.java new file mode 100644 index 00000000000..de709598a7c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageMeasuresStep.java @@ -0,0 +1,352 @@ +/* + * 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.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.apache.commons.lang.ObjectUtils; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.server.computation.batch.BatchReportReader; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; +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.measure.MeasureVariations; +import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeys; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolder; + +import static com.google.common.collect.FluentIterable.from; +import static java.util.Arrays.asList; +import static org.sonar.server.computation.component.Component.Type.FILE; +import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; +import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; + +/** + * Computes measures related to the New Coverage. These measures do not have values, only variations. + */ +public class NewCoverageMeasuresStep implements ComputationStep { + private final TreeRootHolder treeRootHolder; + private final PeriodsHolder periodsHolder; + private final BatchReportReader batchReportReader; + private final MeasureRepository measureRepository; + + private final List newCoverageMetricsList; + + public NewCoverageMeasuresStep(TreeRootHolder treeRootHolder, PeriodsHolder periodsHolder, BatchReportReader batchReportReader, + MeasureRepository measureRepository, final MetricRepository metricRepository, NewCoverageMetricKeys... newCoverageMetricsList) { + this.treeRootHolder = treeRootHolder; + this.periodsHolder = periodsHolder; + this.batchReportReader = batchReportReader; + this.measureRepository = measureRepository; + this.newCoverageMetricsList = from(asList(newCoverageMetricsList)) + .transform(new Function() { + @Nullable + @Override + public NewCoverageMetrics apply(@Nullable NewCoverageMetricKeys input) { + return new NewCoverageMetrics(metricRepository, input); + } + }) + .toList(); + } + + @Override + public void execute() { + new DepthTraversalTypeAwareVisitor(FILE, POST_ORDER) { + @Override + public void visitFile(Component file) { + if (!file.getFileAttributes().isUnitTest()) { + processFileComponent(file); + } + } + }.visit(treeRootHolder.getRoot()); + } + + private void processFileComponent(Component file) { + for (NewCoverageMetrics newCoverageMetrics : newCoverageMetricsList) { + Counters counters = new Counters(file, periodsHolder); + if (populateCounters(newCoverageMetrics, counters)) { + compute(newCoverageMetrics, counters); + } + } + } + + private boolean populateCounters(NewCoverageMetrics newCoverageMetrics, Counters counters) { + Component fileComponent = counters.getComponent(); + BatchReport.Changesets componentScm = batchReportReader.readChangesets(fileComponent.getRef()); + if (componentScm == null) { + return false; + } + + Optional hitsByLineMeasure = measureRepository.getRawMeasure(fileComponent, newCoverageMetrics.coverageLineHitsData); + if (!hitsByLineMeasure.isPresent() || hitsByLineMeasure.get().getValueType() == Measure.ValueType.NO_VALUE) { + return false; + } + + Map hitsByLine = parseCountByLine(hitsByLineMeasure); + Map conditionsByLine = parseCountByLine(measureRepository.getRawMeasure(fileComponent, newCoverageMetrics.conditionsByLine)); + Map coveredConditionsByLine = parseCountByLine(measureRepository.getRawMeasure(fileComponent, newCoverageMetrics.coveredConditionsByLine)); + + for (Map.Entry entry : hitsByLine.entrySet()) { + int lineId = entry.getKey(); + int hits = entry.getValue(); + int conditions = (Integer) ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0); + int coveredConditions = (Integer) ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0); + BatchReport.Changesets.Changeset changeset = componentScm.getChangeset(componentScm.getChangesetIndexByLine(lineId - 1)); + Date date = changeset.hasDate() ? new Date(changeset.getDate()) : null; + + counters.analyze(date, hits, conditions, coveredConditions); + } + + return true; + } + + private static Map parseCountByLine(Optional measure) { + if (measure.isPresent() && measure.get().getValueType() != Measure.ValueType.NO_VALUE) { + return KeyValueFormat.parseIntInt(measure.get().getStringValue()); + } + return Collections.emptyMap(); + } + + private void compute(NewCoverageMetrics newCoverageMetrics, Counters counters) { + computeMeasure(counters, newCoverageMetrics.newLinesToCover, NewLinesToCoverComputer.INSTANCE); + computeMeasure(counters, newCoverageMetrics.newUncoveredLines, NewUncoveredLinesComputer.INSTANCE); + computeMeasure(counters, newCoverageMetrics.newConditionsToCover, NewConditionsToCoverComputer.INSTANCE); + computeMeasure(counters, newCoverageMetrics.newUncoveredConditions, NewUncoveredConditionsComputer.INSTANCE); + } + + private void computeMeasure(final Counters counters, Metric metric, NewCoverageMeasureComputer measureComputer) { + List nonEmptyCounters = from(counters.getCounters()).filter(CounterHasNewCode.INSTANCE).toList(); + if (nonEmptyCounters.isEmpty()) { + measureRepository.add(counters.getComponent(), metric, newMeasureBuilder().createNoValue()); + return; + } + + MeasureVariations.Builder variationsBuilder = MeasureVariations.newMeasureVarationsBuilder(); + for (Counter counter : nonEmptyCounters) { + variationsBuilder.setVariation(counter.getPeriodIndex(), measureComputer.compute(counter)); + } + measureRepository.add(counters.getComponent(), metric, newMeasureBuilder().setVariations(variationsBuilder.build()).createNoValue()); + } + + @Override + public String getDescription() { + return "Computation of New Coverage measures"; + } + + /** + * Internal class storing the Metric objects for each property of a {@link NewCoverageMetricKeys} instance + */ + private static final class NewCoverageMetrics { + private final Metric coverageLineHitsData; + private final Metric conditionsByLine; + private final Metric coveredConditionsByLine; + private final Metric newLinesToCover; + private final Metric newUncoveredLines; + private final Metric newConditionsToCover; + private final Metric newUncoveredConditions; + + public NewCoverageMetrics(MetricRepository metricRepository, NewCoverageMetricKeys keys) { + this.coverageLineHitsData = metricRepository.getByKey(keys.coverageLineHitsData()); + this.conditionsByLine = metricRepository.getByKey(keys.conditionsByLine()); + this.coveredConditionsByLine = metricRepository.getByKey(keys.coveredConditionsByLine()); + this.newLinesToCover = metricRepository.getByKey(keys.newLinesToCover()); + this.newUncoveredLines = metricRepository.getByKey(keys.newUncoveredLines()); + this.newConditionsToCover = metricRepository.getByKey(keys.newConditionsToCover()); + this.newUncoveredConditions = metricRepository.getByKey(keys.newUncoveredConditions()); + } + + } + + /** + * Holds the {@link Counter}s (one for each Period in a specific {@link PeriodsHolder}) for a specific Component. + */ + private static final class Counters { + private final Component component; + private final List counters; + + public Counters(Component component, PeriodsHolder periodsHolder) { + this.component = component; + this.counters = from(periodsHolder.getPeriods()).transform(PeriodToCounter.INSTANCE).toList(); + } + + public void analyze(@Nullable Date lineDate, int hits, int conditions, int coveredConditions) { + if (lineDate == null) { + return; + } + for (Counter counter : getCounters()) { + counter.analyze(lineDate, hits, conditions, coveredConditions); + } + } + + public Component getComponent() { + return component; + } + + private Iterable getCounters() { + return this.counters; + } + + } + + public static final class Counter { + private final int periodIndex; + private final Date date; + private Integer newLines; + private Integer newCoveredLines; + private Integer newConditions; + private Integer newCoveredConditions; + + private Counter(int periodIndex, Date date) { + this.periodIndex = periodIndex; + this.date = date; + } + + public int getPeriodIndex() { + return periodIndex; + } + + void analyze(Date lineDate, int hits, int conditions, int coveredConditions) { + if (lineDate.after(date)) { + addLine(hits > 0); + addConditions(conditions, coveredConditions); + } + } + + void addLine(boolean covered) { + if (newLines == null) { + newLines = 0; + } + newLines += 1; + if (covered) { + if (newCoveredLines == null) { + newCoveredLines = 0; + } + newCoveredLines += 1; + } + } + + void addConditions(int count, int countCovered) { + if (newConditions == null) { + newConditions = 0; + } + newConditions += count; + if (count > 0) { + if (newCoveredConditions == null) { + newCoveredConditions = 0; + } + newCoveredConditions += countCovered; + } + } + + boolean hasNewCode() { + return newLines != null; + } + + public int getNewLines() { + return newLines != null ? newLines : 0; + } + + public int getNewCoveredLines() { + return newCoveredLines != null ? newCoveredLines : 0; + } + + public int getNewConditions() { + return newConditions != null ? newConditions : 0; + } + + public int getNewCoveredConditions() { + return newCoveredConditions != null ? newCoveredConditions : 0; + } + } + + private enum PeriodToCounter implements Function { + INSTANCE; + + @Override + @Nonnull + public Counter apply(@Nonnull Period input) { + return new Counter(input.getIndex(), new Date(input.getSnapshotDate())); + } + } + + private enum CounterHasNewCode implements Predicate { + INSTANCE; + + @Override + public boolean apply(@Nonnull Counter input) { + return input.hasNewCode(); + } + } + + /** + * Represents a way of computing a measure value from a given Counter. + */ + private interface NewCoverageMeasureComputer { + int compute(Counter counter); + } + + private enum NewLinesToCoverComputer implements NewCoverageMeasureComputer { + INSTANCE; + + @Override + public int compute(Counter counter) { + return counter.getNewLines(); + } + } + + private enum NewUncoveredLinesComputer implements NewCoverageMeasureComputer { + INSTANCE; + + @Override + public int compute(Counter counter) { + return counter.getNewLines() - counter.getNewCoveredLines(); + } + } + + private enum NewConditionsToCoverComputer implements NewCoverageMeasureComputer { + INSTANCE; + + @Override + public int compute(Counter counter) { + return counter.getNewConditions(); + } + } + + private enum NewUncoveredConditionsComputer implements NewCoverageMeasureComputer { + INSTANCE; + + @Override + public int compute(Counter counter) { + return counter.getNewConditions() - counter.getNewCoveredConditions(); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java index cd050090bdc..381579a9e1e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java @@ -21,7 +21,6 @@ package org.sonar.server.computation.measure; import com.google.common.base.Optional; import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.SetMultimap; import java.util.HashMap; @@ -42,6 +41,7 @@ import org.sonar.server.computation.metric.MetricRepositoryRule; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.FluentIterable.from; import static com.google.common.collect.Maps.filterKeys; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -133,7 +133,7 @@ public class MeasureRepositoryRule extends ExternalResource implements MeasureRe checkAndInitProvidersState(); ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); - for (Map.Entry entry : FluentIterable.from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(isNewMeasure)) { + for (Map.Entry entry : from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(isNewMeasure)) { builder.put(entry.getKey().getMetricKey(), entry.getValue()); } return builder.build(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageAggregationStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageAggregationStepTest.java new file mode 100644 index 00000000000..1305e2ad3ff --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageAggregationStepTest.java @@ -0,0 +1,293 @@ +/* + * 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.Function; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import javax.annotation.Nonnull; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.DumbComponent; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepoEntry; +import org.sonar.server.computation.measure.MeasureRepositoryRule; +import org.sonar.server.computation.measure.MeasureVariations; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepositoryRule; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolderRule; + +import static com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.ImmutableList.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.measures.CoreMetrics.NEW_LINES_TO_COVER_KEY; +import static org.sonar.server.computation.component.Component.Type.DIRECTORY; +import static org.sonar.server.computation.component.Component.Type.FILE; +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.component.DumbComponent.builder; +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; + +@RunWith(DataProviderRunner.class) +public class NewCoverageAggregationStepTest { + + private static final DumbComponent MOST_SIMPLE_ONE_FILE_TREE = builder(PROJECT, 1) + .addChildren( + builder(MODULE, 2) + .addChildren( + builder(DIRECTORY, 3) + .addChildren( + builder(FILE, 4).build()) + .build() + ).build() + ).build(); + + private static final DumbComponent MANY_FILES_TREE = builder(PROJECT, 1) + .addChildren( + builder(MODULE, 11) + .addChildren( + builder(DIRECTORY, 111) + .addChildren( + builder(FILE, 1111).build(), + builder(FILE, 1112).build() + ).build(), + builder(DIRECTORY, 112) + .addChildren( + builder(FILE, 1121).build(), + builder(FILE, 1122).build(), + builder(FILE, 1123).build() + ).build() + ).build(), + builder(MODULE, 12) + .addChildren( + builder(DIRECTORY, 121) + .addChildren( + builder(FILE, 1211).build(), + builder(FILE, 1212).build() + ).build(), + builder(DIRECTORY, 122).build() + ).build(), + builder(MODULE, 13).build() + ).build(); + + private static final String SOME_MODE = "some mode"; + private static final long SOME_SNAPSHOT_DATE = 1234L; + private static final long SOME_SNAPSHOT_ID = 876L; + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + @Rule + public PeriodsHolderRule periodHolder = new PeriodsHolderRule(); + @Rule + public MetricRepositoryRule metricRepository = new MetricRepositoryRule() + .add(CoreMetrics.NEW_LINES_TO_COVER) + .add(CoreMetrics.NEW_UNCOVERED_LINES) + .add(CoreMetrics.NEW_CONDITIONS_TO_COVER) + .add(CoreMetrics.NEW_UNCOVERED_CONDITIONS) + .add(CoreMetrics.NEW_IT_LINES_TO_COVER) + .add(CoreMetrics.NEW_IT_UNCOVERED_LINES) + .add(CoreMetrics.NEW_IT_CONDITIONS_TO_COVER) + .add(CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS) + .add(CoreMetrics.NEW_OVERALL_LINES_TO_COVER) + .add(CoreMetrics.NEW_OVERALL_UNCOVERED_LINES) + .add(CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER) + .add(CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS); + @Rule + public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + + private NewCoverageAggregationStep underTest = new NewCoverageAggregationStep(treeRootHolder, periodHolder, metricRepository, measureRepository); + + @DataProvider + public static Object[][] trees_without_FILE_COMPONENT() { + return new Object[][] { + {builder(PROJECT, 1).build()}, + {builder(PROJECT, 1).addChildren(builder(MODULE, 2).build()).build()}, + {builder(PROJECT, 1).addChildren(builder(MODULE, 2).addChildren(builder(DIRECTORY, 3).build()).build()).build()}, + }; + } + + @Test + @UseDataProvider("trees_without_FILE_COMPONENT") + public void no_measures_added_if_there_is_only_PROJECT_component(Component root) { + treeRootHolder.setRoot(root); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void no_measures_added_if_there_is_no_rawMeasure_on_FILE_component() { + treeRootHolder.setRoot(MOST_SIMPLE_ONE_FILE_TREE); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void no_measures_added_if_there_is_rawMeasure_has_no_variation_on_FILE_component() { + treeRootHolder.setRoot(MOST_SIMPLE_ONE_FILE_TREE); + measureRepository.addRawMeasure(4, NEW_LINES_TO_COVER_KEY, newMeasureBuilder().createNoValue()); + + underTest.execute(); + + assertThat(measureRepository.getNewRawMeasures(1).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(2).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(3).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(4).isEmpty()).isTrue(); + } + + @Test + public void no_measures_added_if_there_is_rawMeasure_is_not_NOVALUE_on_FILE_component() { + treeRootHolder.setRoot(MOST_SIMPLE_ONE_FILE_TREE); + measureRepository.addRawMeasure(4, NEW_LINES_TO_COVER_KEY, newMeasureBuilder().setVariations(new MeasureVariations(1d)).create("some value")); + + underTest.execute(); + + assertThat(measureRepository.getNewRawMeasures(1).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(2).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(3).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(4).isEmpty()).isTrue(); + } + + @Test + public void no_measures_added_if_there_is_no_period() { + treeRootHolder.setRoot(MOST_SIMPLE_ONE_FILE_TREE); + periodHolder.setPeriods(); + measureRepository.addRawMeasure(4, NEW_LINES_TO_COVER_KEY, createMeasure(2d)); + + underTest.execute(); + + assertThat(measureRepository.getNewRawMeasures(1).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(2).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(3).isEmpty()).isTrue(); + assertThat(measureRepository.getNewRawMeasures(4).isEmpty()).isTrue(); + } + + @Test + public void measures_added_even_if_rawMeasure_has_variation_0_on_FILE_component() { + treeRootHolder.setRoot(MOST_SIMPLE_ONE_FILE_TREE); + periodHolder.setPeriods(createPeriod(2)); + measureRepository.addRawMeasure(4, NEW_LINES_TO_COVER_KEY, createMeasure(0d)); + + underTest.execute(); + + MeasureRepoEntry expectedEntry = entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(0d)); + assertThat(toEntries(measureRepository.getNewRawMeasures(1))).containsOnly(expectedEntry); + assertThat(toEntries(measureRepository.getNewRawMeasures(2))).containsOnly(expectedEntry); + assertThat(toEntries(measureRepository.getNewRawMeasures(3))).containsOnly(expectedEntry); + assertThat(measureRepository.getNewRawMeasures(4).isEmpty()).isTrue(); + } + + @Test + public void measures_added_on_all_components_but_FILE_and_are_sum_of_childrens_value() { + treeRootHolder.setRoot(MANY_FILES_TREE); + periodHolder.setPeriods(createPeriod(2)); + for (Integer fileComponentRef : of(1111, 1112, 1121, 1122, 1123, 1211, 1212)) { + measureRepository.addRawMeasure(fileComponentRef, NEW_LINES_TO_COVER_KEY, createMeasure((double) fileComponentRef)); + } + + underTest.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(111))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1111 + 1112)) + ); + assertThat(toEntries(measureRepository.getNewRawMeasures(112))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1121 + 1122 + 1123)) + ); + assertThat(toEntries(measureRepository.getNewRawMeasures(121))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1211 + 1212)) + ); + assertThat(measureRepository.getNewRawMeasures(122).isEmpty()).isTrue(); + assertThat(toEntries(measureRepository.getNewRawMeasures(11))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1111 + 1112 + 1121 + 1122 + 1123)) + ); + assertThat(toEntries(measureRepository.getNewRawMeasures(12))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1211 + 1212)) + ); + assertThat(measureRepository.getNewRawMeasures(13).isEmpty()).isTrue(); + assertThat(toEntries(measureRepository.getNewRawMeasures(1))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1111 + 1112 + 1121 + 1122 + 1123 + 1211 + 1212)) + ); + } + + @Test + public void verify_measures_are_created_for_all_metrics() { + treeRootHolder.setRoot(MOST_SIMPLE_ONE_FILE_TREE); + periodHolder.setPeriods(createPeriod(2)); + for (Metric metric : metricRepository.getAll()) { + measureRepository.addRawMeasure(4, metric.getKey(), createMeasure(metric.getKey().hashCode())); + } + + underTest.execute(); + + MeasureRepoEntry[] expectedEntries = from(metricRepository.getAll()) + .transform(new Function() { + @Override + @Nonnull + public MeasureRepoEntry apply(@Nonnull Metric input) { + return entryOf(input.getKey(), createMeasure(input.getKey().hashCode())); + } + }).toArray(MeasureRepoEntry.class); + + assertThat(toEntries(measureRepository.getNewRawMeasures(1))).containsOnly(expectedEntries); + assertThat(toEntries(measureRepository.getNewRawMeasures(2))).containsOnly(expectedEntries); + assertThat(toEntries(measureRepository.getNewRawMeasures(3))).containsOnly(expectedEntries); + assertThat(measureRepository.getNewRawMeasures(4).isEmpty()).isTrue(); + } + + @Test + public void verify_measures_are_created_for_all_periods() { + treeRootHolder.setRoot(MOST_SIMPLE_ONE_FILE_TREE); + periodHolder.setPeriods(createPeriod(1), createPeriod(2), createPeriod(3), createPeriod(4), createPeriod(5)); + Measure measure = newMeasureBuilder().setVariations(new MeasureVariations(5d, 4d, 3d, 2d, 1d)).createNoValue(); + measureRepository.addRawMeasure(4, NEW_LINES_TO_COVER_KEY, measure); + + underTest.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(1))).containsOnly(entryOf(NEW_LINES_TO_COVER_KEY, measure)); + assertThat(toEntries(measureRepository.getNewRawMeasures(2))).containsOnly(entryOf(NEW_LINES_TO_COVER_KEY, measure)); + assertThat(toEntries(measureRepository.getNewRawMeasures(3))).containsOnly(entryOf(NEW_LINES_TO_COVER_KEY, measure)); + assertThat(measureRepository.getNewRawMeasures(4).isEmpty()).isTrue(); + } + + @Test + public void verify_description() { + assertThat(underTest.getDescription()).isEqualTo("Aggregates New Coverage measures"); + + } + + private Measure createMeasure(double variation2) { + return newMeasureBuilder().setVariations(new MeasureVariations(null, variation2)).createNoValue(); + } + + private static Period createPeriod(int periodIndex) { + return new Period(periodIndex, SOME_MODE, null, SOME_SNAPSHOT_DATE, SOME_SNAPSHOT_ID); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageMeasuresStepTest.java new file mode 100644 index 00000000000..8dfe0b9f2a8 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageMeasuresStepTest.java @@ -0,0 +1,334 @@ +/* + * 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 javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.server.computation.batch.BatchReportReaderRule; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.DumbComponent; +import org.sonar.server.computation.component.FileAttributes; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepositoryRule; +import org.sonar.server.computation.measure.MeasureVariations; +import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeys; +import org.sonar.server.computation.metric.MetricRepositoryRule; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolderRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.measures.CoreMetrics.CONDITIONS_BY_LINE_KEY; +import static org.sonar.api.measures.CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY; +import static org.sonar.api.measures.CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_LINES_TO_COVER_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY; +import static org.sonar.api.measures.CoreMetrics.NEW_UNCOVERED_LINES_KEY; +import static org.sonar.api.utils.DateUtils.parseDate; +import static org.sonar.batch.protocol.output.BatchReport.Changesets; +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; +import static org.sonar.server.computation.measure.MeasureVariations.newMeasureVarationsBuilder; + +public class NewCoverageMeasuresStepTest { + private static final NewCoverageMetricKeys SOME_COVERAGE_METRIC_KEYS = new SomeNewCoverageMetricKeys(); + + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + @Rule + public PeriodsHolderRule periodsHolder = new PeriodsHolderRule(); + @Rule + public MetricRepositoryRule metricRepository = new MetricRepositoryRule() + // adds metrics referenced by SomeNewCoverageMetricKeys + .add(CoreMetrics.COVERAGE_LINE_HITS_DATA) + .add(CoreMetrics.CONDITIONS_BY_LINE) + .add(CoreMetrics.COVERED_CONDITIONS_BY_LINE) + .add(CoreMetrics.NEW_LINES_TO_COVER) + .add(CoreMetrics.NEW_UNCOVERED_LINES) + .add(CoreMetrics.NEW_CONDITIONS_TO_COVER) + .add(CoreMetrics.NEW_UNCOVERED_CONDITIONS); + @Rule + public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + + private NewCoverageMeasuresStep underTest = new NewCoverageMeasuresStep(treeRootHolder, periodsHolder, reportReader, measureRepository, metricRepository, + SOME_COVERAGE_METRIC_KEYS); + public static final DumbComponent FILE_COMPONENT = DumbComponent.builder(Component.Type.FILE, 1).setFileAttributes(new FileAttributes(false, null)).build(); + + @Before + public void setUp() throws Exception { + periodsHolder.setPeriods( + new Period(2, "mode_p_1", null, parseDate("2009-12-25").getTime(), 1), + new Period(5, "mode_p_5", null, parseDate("2011-02-18").getTime(), 2)); + } + + @Test + public void no_measure_for_PROJECT_component() { + treeRootHolder.setRoot(DumbComponent.builder(Component.Type.PROJECT, 1).build()); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void no_measure_for_MODULE_component() { + treeRootHolder.setRoot(DumbComponent.builder(Component.Type.MODULE, 1).build()); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void no_measure_for_DIRECTORY_component() { + treeRootHolder.setRoot(DumbComponent.builder(Component.Type.DIRECTORY, 1).build()); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void no_measure_for_unit_test_FILE_component() { + treeRootHolder.setRoot(DumbComponent.builder(Component.Type.FILE, 1).setFileAttributes(new FileAttributes(true, null)).build()); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void no_measures_for_FILE_component_without_code() { + treeRootHolder.setRoot(DumbComponent.builder(Component.Type.FILE, 1).setFileAttributes(new FileAttributes(false, null)).build()); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void no_measures_for_FILE_component_without_CoverageData() { + DumbComponent fileComponent = DumbComponent.builder(Component.Type.FILE, 1).setFileAttributes(new FileAttributes(false, null)).build(); + + treeRootHolder.setRoot(fileComponent); + reportReader.putChangesets(Changesets.newBuilder() + .setComponentRef(fileComponent.getRef()) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2008-05-18").getTime()) + .build()) + .addChangesetIndexByLine(0) + .build()); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void verify_computation_of_measures_for_new_lines() { + treeRootHolder.setRoot(FILE_COMPONENT); + reportReader.putChangesets(BatchReport.Changesets.newBuilder() + .setComponentRef(1) + .addChangeset(Changesets.Changeset.newBuilder().build()) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2007-01-15").getTime()) + .build()) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2011-01-01").getTime()) + .build()) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(1) + .addChangesetIndexByLine(2) + .build()); + measureRepository.addRawMeasure(FILE_COMPONENT.getRef(), COVERAGE_LINE_HITS_DATA_KEY, newMeasureBuilder().create("10=2;11=3")); + + underTest.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(FILE_COMPONENT.getRef()))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1d, null)), + entryOf(NEW_UNCOVERED_LINES_KEY, createMeasure(0d, null)), + entryOf(NEW_CONDITIONS_TO_COVER_KEY, createMeasure(0d, null)), + entryOf(NEW_UNCOVERED_CONDITIONS_KEY, createMeasure(0d, null)) + ); + } + + @Test + public void verify_computation_of_measures_for_new_conditions() { + treeRootHolder.setRoot(FILE_COMPONENT); + reportReader.putChangesets(Changesets.newBuilder() + .setComponentRef(1) + .addChangeset(Changesets.Changeset.newBuilder().build()) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2007-01-15").getTime()) + .build()) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2011-01-01").getTime()) + .build()) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(1) + .addChangesetIndexByLine(2) + .build() + ); + measureRepository.addRawMeasure(FILE_COMPONENT.getRef(), COVERAGE_LINE_HITS_DATA_KEY, newMeasureBuilder().create("10=2;11=3")); + measureRepository.addRawMeasure(FILE_COMPONENT.getRef(), CONDITIONS_BY_LINE_KEY, newMeasureBuilder().create("11=4")); + measureRepository.addRawMeasure(FILE_COMPONENT.getRef(), COVERED_CONDITIONS_BY_LINE_KEY, newMeasureBuilder().create("11=1")); + + underTest.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(FILE_COMPONENT.getRef()))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, createMeasure(1d, null)), + entryOf(NEW_UNCOVERED_LINES_KEY, createMeasure(0d, null)), + entryOf(NEW_CONDITIONS_TO_COVER_KEY, createMeasure(4d, null)), + entryOf(NEW_UNCOVERED_CONDITIONS_KEY, createMeasure(3d, null)) + ); + } + + @Test + public void verify_measure_of_condition_not_computed_if_there_is_none() { + treeRootHolder.setRoot(FILE_COMPONENT); + reportReader.putChangesets(Changesets.newBuilder() + .setComponentRef(1) + .addChangeset(Changesets.Changeset.newBuilder().build()) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2007-01-15").getTime()) + .build()) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2011-01-01").getTime()) + .build()) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(1) + .addChangesetIndexByLine(2) + .build() + ); + + underTest.execute(); + + assertThat(measureRepository.isEmpty()).isTrue(); + } + + @Test + public void verify_no_measure_when_nothing_has_changed() { + treeRootHolder.setRoot(FILE_COMPONENT); + reportReader.putChangesets(BatchReport.Changesets.newBuilder() + .setComponentRef(1) + .addChangeset(Changesets.Changeset.newBuilder() + .setDate(parseDate("2008-08-02").getTime()) + .build()) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .build()); + measureRepository.addRawMeasure(FILE_COMPONENT.getRef(), COVERAGE_LINE_HITS_DATA_KEY, newMeasureBuilder().create("2=1;3=1")); + measureRepository.addRawMeasure(FILE_COMPONENT.getRef(), CONDITIONS_BY_LINE_KEY, newMeasureBuilder().create("2=1")); + measureRepository.addRawMeasure(FILE_COMPONENT.getRef(), COVERED_CONDITIONS_BY_LINE_KEY, newMeasureBuilder().create("2=1")); + + underTest.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(FILE_COMPONENT.getRef()))).containsOnly( + entryOf(NEW_LINES_TO_COVER_KEY, newMeasureBuilder().createNoValue()), + entryOf(NEW_UNCOVERED_LINES_KEY, newMeasureBuilder().createNoValue()), + entryOf(NEW_CONDITIONS_TO_COVER_KEY, newMeasureBuilder().createNoValue()), + entryOf(NEW_UNCOVERED_CONDITIONS_KEY, newMeasureBuilder().createNoValue()) + ); + } + + private static Measure createMeasure(@Nullable Double variationPeriod2, @Nullable Double variationPeriod5) { + MeasureVariations.Builder variationBuilder = newMeasureVarationsBuilder(); + if (variationPeriod2 != null) { + variationBuilder.setVariation(2, variationPeriod2); + } + if (variationPeriod5 != null) { + variationBuilder.setVariation(5, variationPeriod5); + } + return newMeasureBuilder() + .setVariations(variationBuilder.build()) + .createNoValue(); + } + + private static class SomeNewCoverageMetricKeys implements NewCoverageMetricKeys { + @Override + public String coverageLineHitsData() { + return COVERAGE_LINE_HITS_DATA_KEY; + } + + @Override + public String conditionsByLine() { + return CONDITIONS_BY_LINE_KEY; + } + + @Override + public String coveredConditionsByLine() { + return COVERED_CONDITIONS_BY_LINE_KEY; + } + + @Override + public String newLinesToCover() { + return NEW_LINES_TO_COVER_KEY; + } + + @Override + public String newUncoveredLines() { + return NEW_UNCOVERED_LINES_KEY; + } + + @Override + public String newConditionsToCover() { + return NEW_CONDITIONS_TO_COVER_KEY; + } + + @Override + public String newUncoveredConditions() { + return NEW_UNCOVERED_CONDITIONS_KEY; + } + } +} -- 2.39.5