]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6645 move New Coverage computation to Compute Engine
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 26 Jun 2015 16:56:40 +0000 (18:56 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Wed, 1 Jul 2015 09:07:22 +0000 (11:07 +0200)
13 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/FileCoverageMetricKeys.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/ItFileCoverageMetricKeys.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeys.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/NewCoverageMetricKeysModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/OverallFileCoverageMetricKeys.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/newcoverage/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageAggregationStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/NewCoverageMeasuresStep.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureRepositoryRule.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageAggregationStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/NewCoverageMeasuresStepTest.java [new file with mode: 0644]

index 2c7e62a7c88d57e078c58c7b3fc6dfc1c3f03ea4..dad331ca5ff2f5028d4a9a77967f34186fabc17b 100644 (file)
@@ -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 (file)
index 0000000..004abe6
--- /dev/null
@@ -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 (file)
index 0000000..e6676fa
--- /dev/null
@@ -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 (file)
index 0000000..7aa9894
--- /dev/null
@@ -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 (file)
index 0000000..35014bb
--- /dev/null
@@ -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 (file)
index 0000000..9297fa7
--- /dev/null
@@ -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 (file)
index 0000000..656c487
--- /dev/null
@@ -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;
index d4e6d6fa0ff17ef82bfd399748c337b09eb7549b..a40b9e94fda25520c02bfeaf0982b819b0e3a06d 100644 (file)
@@ -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 (file)
index 0000000..91f878d
--- /dev/null
@@ -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<String> 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<Metric> 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<String, Metric>() {
+        @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<VariationBuilders> {
+    public NewCoverageAggregationComponentVisitor() {
+      super(FILE, POST_ORDER, VARIATION_BUILDERS_FACTORY);
+    }
+
+    @Override
+    protected void visitProject(Component project, Path<VariationBuilders> path) {
+      addNewMeasures(project, path.current());
+    }
+
+    @Override
+    protected void visitModule(Component module, Path<VariationBuilders> path) {
+      addNewMeasures(module, path.current());
+      updateParentVariations(path);
+    }
+
+    @Override
+    protected void visitDirectory(Component directory, Path<VariationBuilders> path) {
+      addNewMeasures(directory, path.current());
+      updateParentVariations(path);
+    }
+
+    @Override
+    protected void visitFile(Component file, Path<VariationBuilders> path) {
+      initVariationBuilders(file, path.parent());
+    }
+  }
+
+  /**
+   * Creates a new VariationBuilders instance for all Component types except FILE.
+   */
+  private static class VariationBuildersFactory extends PathAwareVisitor.SimpleStackElementFactory<VariationBuilders> {
+    @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<Measure> 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<VariationBuilder> 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<VariationBuilders> 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<String, VariationBuilder> 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<VariationBuilder> getBuilder(String metricKey) {
+      return Optional.fromNullable(this.builders.get(metricKey));
+    }
+
+    public void add(VariationBuilders current) {
+      for (Map.Entry<String, VariationBuilder> 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 (file)
index 0000000..de70959
--- /dev/null
@@ -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<NewCoverageMetrics> 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<NewCoverageMetricKeys, NewCoverageMetrics>() {
+        @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<Measure> hitsByLineMeasure = measureRepository.getRawMeasure(fileComponent, newCoverageMetrics.coverageLineHitsData);
+    if (!hitsByLineMeasure.isPresent() || hitsByLineMeasure.get().getValueType() == Measure.ValueType.NO_VALUE) {
+      return false;
+    }
+
+    Map<Integer, Integer> hitsByLine = parseCountByLine(hitsByLineMeasure);
+    Map<Integer, Integer> conditionsByLine = parseCountByLine(measureRepository.getRawMeasure(fileComponent, newCoverageMetrics.conditionsByLine));
+    Map<Integer, Integer> coveredConditionsByLine = parseCountByLine(measureRepository.getRawMeasure(fileComponent, newCoverageMetrics.coveredConditionsByLine));
+
+    for (Map.Entry<Integer, Integer> 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<Integer, Integer> parseCountByLine(Optional<Measure> 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<Counter> 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<Counter> 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<Counter> 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<Period, Counter> {
+    INSTANCE;
+
+    @Override
+    @Nonnull
+    public Counter apply(@Nonnull Period input) {
+      return new Counter(input.getIndex(), new Date(input.getSnapshotDate()));
+    }
+  }
+
+  private enum CounterHasNewCode implements Predicate<Counter> {
+    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();
+    }
+  }
+}
index cd050090bdcc902f245666718c333bc10d22d53f..381579a9e1e68e4965f67bad31efb3399e5e0475 100644 (file)
@@ -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<String, Measure> builder = ImmutableSetMultimap.builder();
-    for (Map.Entry<InternalKey, Measure> entry : FluentIterable.from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(isNewMeasure)) {
+    for (Map.Entry<InternalKey, Measure> 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 (file)
index 0000000..1305e2a
--- /dev/null
@@ -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<Metric, MeasureRepoEntry>() {
+        @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 (file)
index 0000000..8dfe0b9
--- /dev/null
@@ -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;
+    }
+  }
+}