3 * Copyright (C) 2009-2016 SonarSource SA
4 * mailto:contact AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.computation.task.projectanalysis.step;
22 import com.google.common.base.Optional;
23 import com.google.common.collect.ImmutableList;
24 import com.google.common.collect.Iterables;
25 import java.util.Collections;
26 import java.util.List;
28 import javax.annotation.CheckForNull;
29 import javax.annotation.Nullable;
30 import javax.annotation.concurrent.Immutable;
31 import org.apache.commons.lang.ObjectUtils;
32 import org.sonar.api.measures.CoreMetrics;
33 import org.sonar.api.utils.KeyValueFormat;
34 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
35 import org.sonar.server.computation.task.projectanalysis.component.Component;
36 import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
37 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
38 import org.sonar.server.computation.task.projectanalysis.formula.CounterInitializationContext;
39 import org.sonar.server.computation.task.projectanalysis.formula.CreateMeasureContext;
40 import org.sonar.server.computation.task.projectanalysis.formula.Formula;
41 import org.sonar.server.computation.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
42 import org.sonar.server.computation.task.projectanalysis.formula.VariationSumFormula;
43 import org.sonar.server.computation.task.projectanalysis.formula.counter.IntVariationValue;
44 import org.sonar.server.computation.task.projectanalysis.formula.coverage.LinesAndConditionsWithUncoveredMetricKeys;
45 import org.sonar.server.computation.task.projectanalysis.formula.coverage.LinesAndConditionsWithUncoveredVariationFormula;
46 import org.sonar.server.computation.task.projectanalysis.formula.coverage.SingleWithUncoveredMetricKeys;
47 import org.sonar.server.computation.task.projectanalysis.formula.coverage.SingleWithUncoveredVariationFormula;
48 import org.sonar.server.computation.task.projectanalysis.measure.Measure;
49 import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
50 import org.sonar.server.computation.task.projectanalysis.measure.MeasureVariations;
51 import org.sonar.server.computation.task.projectanalysis.metric.Metric;
52 import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
53 import org.sonar.server.computation.task.projectanalysis.period.Period;
54 import org.sonar.server.computation.task.projectanalysis.period.PeriodsHolder;
55 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfo;
56 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepository;
57 import org.sonar.server.computation.task.step.ComputationStep;
59 import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
60 import static org.sonar.server.computation.task.projectanalysis.period.PeriodPredicates.viewsRestrictedPeriods;
63 * Computes measures related to the New Coverage. These measures do not have values, only variations.
65 public class NewCoverageMeasuresStep implements ComputationStep {
67 private static final List<Formula> FORMULAS = ImmutableList.of(
69 new NewCoverageFormula(),
70 new NewBranchCoverageFormula(),
71 new NewLineCoverageFormula());
73 private final TreeRootHolder treeRootHolder;
74 private final PeriodsHolder periodsHolder;
75 private final MetricRepository metricRepository;
76 private final MeasureRepository measureRepository;
78 private final ScmInfoRepository scmInfoRepository;
81 * Constructor used when processing a Report (ie. a {@link BatchReportReader} instance is available in the container)
83 public NewCoverageMeasuresStep(TreeRootHolder treeRootHolder, PeriodsHolder periodsHolder,
84 MeasureRepository measureRepository, final MetricRepository metricRepository, ScmInfoRepository scmInfoRepository) {
85 this.treeRootHolder = treeRootHolder;
86 this.periodsHolder = periodsHolder;
87 this.metricRepository = metricRepository;
88 this.measureRepository = measureRepository;
89 this.scmInfoRepository = scmInfoRepository;
93 * Constructor used when processing Views (ie. no {@link BatchReportReader} instance is available in the container)
95 public NewCoverageMeasuresStep(TreeRootHolder treeRootHolder, PeriodsHolder periodsHolder,
96 MeasureRepository measureRepository, final MetricRepository metricRepository) {
97 this.treeRootHolder = treeRootHolder;
98 this.periodsHolder = periodsHolder;
99 this.metricRepository = metricRepository;
100 this.measureRepository = measureRepository;
101 this.scmInfoRepository = null;
105 public void execute() {
106 new PathAwareCrawler<>(
107 FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
108 .withVariationSupport(periodsHolder)
110 Iterables.concat(NewLinesAndConditionsCoverageFormula.from(scmInfoRepository), FORMULAS)))
111 .visit(treeRootHolder.getRoot());
115 public String getDescription() {
116 return "Compute new coverage";
119 private static class NewLinesAndConditionsCoverageFormula extends NewLinesAndConditionsFormula {
121 private static final NewCoverageOutputMetricKeys OUTPUT_METRIC_KEYS = new NewCoverageOutputMetricKeys(
122 CoreMetrics.NEW_LINES_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_LINES_KEY,
123 CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY);
124 private static final Iterable<Formula<?>> VIEWS_FORMULAS = variationSumFormulas(OUTPUT_METRIC_KEYS);
126 private NewLinesAndConditionsCoverageFormula(ScmInfoRepository scmInfoRepository) {
127 super(scmInfoRepository,
128 new NewCoverageInputMetricKeys(
129 CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, CoreMetrics.CONDITIONS_BY_LINE_KEY, CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY),
133 public static Iterable<Formula<?>> from(@Nullable ScmInfoRepository scmInfoRepository) {
134 if (scmInfoRepository == null) {
135 return VIEWS_FORMULAS;
137 return Collections.<Formula<?>>singleton(new NewLinesAndConditionsCoverageFormula(scmInfoRepository));
141 * Creates a List of {@link org.sonar.server.computation.task.projectanalysis.formula.SumFormula.IntSumFormula} for each
142 * metric key of the specified {@link NewCoverageOutputMetricKeys} instance.
144 private static Iterable<Formula<?>> variationSumFormulas(NewCoverageOutputMetricKeys outputMetricKeys) {
145 return ImmutableList.of(
146 new VariationSumFormula(outputMetricKeys.getNewLinesToCover(), viewsRestrictedPeriods()),
147 new VariationSumFormula(outputMetricKeys.getNewUncoveredLines(), viewsRestrictedPeriods()),
148 new VariationSumFormula(outputMetricKeys.getNewConditionsToCover(), viewsRestrictedPeriods()),
149 new VariationSumFormula(outputMetricKeys.getNewUncoveredConditions(), viewsRestrictedPeriods()));
153 private static class NewCoverageFormula extends LinesAndConditionsWithUncoveredVariationFormula {
154 public NewCoverageFormula() {
156 new LinesAndConditionsWithUncoveredMetricKeys(
157 CoreMetrics.NEW_LINES_TO_COVER_KEY, CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY,
158 CoreMetrics.NEW_UNCOVERED_LINES_KEY, CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY),
159 CoreMetrics.NEW_COVERAGE_KEY);
163 private static class NewBranchCoverageFormula extends SingleWithUncoveredVariationFormula {
164 public NewBranchCoverageFormula() {
166 new SingleWithUncoveredMetricKeys(CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY),
167 CoreMetrics.NEW_BRANCH_COVERAGE_KEY);
171 private static class NewLineCoverageFormula extends SingleWithUncoveredVariationFormula {
172 public NewLineCoverageFormula() {
174 new SingleWithUncoveredMetricKeys(CoreMetrics.NEW_LINES_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_LINES_KEY),
175 CoreMetrics.NEW_LINE_COVERAGE_KEY);
179 public static class NewLinesAndConditionsFormula implements Formula<NewCoverageCounter> {
180 private final ScmInfoRepository scmInfoRepository;
181 private final NewCoverageInputMetricKeys inputMetricKeys;
182 private final NewCoverageOutputMetricKeys outputMetricKeys;
184 public NewLinesAndConditionsFormula(ScmInfoRepository scmInfoRepository, NewCoverageInputMetricKeys inputMetricKeys, NewCoverageOutputMetricKeys outputMetricKeys) {
185 this.scmInfoRepository = scmInfoRepository;
186 this.inputMetricKeys = inputMetricKeys;
187 this.outputMetricKeys = outputMetricKeys;
191 public NewCoverageCounter createNewCounter() {
192 return new NewCoverageCounter(scmInfoRepository, inputMetricKeys);
196 public Optional<Measure> createMeasure(NewCoverageCounter counter, CreateMeasureContext context) {
197 MeasureVariations.Builder builder = MeasureVariations.newMeasureVariationsBuilder();
198 for (Period period : context.getPeriods()) {
199 if (counter.hasNewCode(period)) {
200 int value = computeValueForMetric(counter, period, context.getMetric());
201 builder.setVariation(period, value);
204 if (builder.isEmpty()) {
205 return Optional.absent();
207 return Optional.of(newMeasureBuilder().setVariations(builder.build()).createNoValue());
210 private int computeValueForMetric(NewCoverageCounter counter, Period period, Metric metric) {
211 if (metric.getKey().equals(outputMetricKeys.getNewLinesToCover())) {
212 return counter.getNewLines(period);
214 if (metric.getKey().equals(outputMetricKeys.getNewUncoveredLines())) {
215 return counter.getNewLines(period) - counter.getNewCoveredLines(period);
217 if (metric.getKey().equals(outputMetricKeys.getNewConditionsToCover())) {
218 return counter.getNewConditions(period);
220 if (metric.getKey().equals(outputMetricKeys.getNewUncoveredConditions())) {
221 return counter.getNewConditions(period) - counter.getNewCoveredConditions(period);
223 throw new IllegalArgumentException("Unsupported metric " + metric.getKey());
227 public String[] getOutputMetricKeys() {
228 return new String[] {
229 outputMetricKeys.getNewLinesToCover(),
230 outputMetricKeys.getNewUncoveredLines(),
231 outputMetricKeys.getNewConditionsToCover(),
232 outputMetricKeys.getNewUncoveredConditions()
237 public static final class NewCoverageCounter implements org.sonar.server.computation.task.projectanalysis.formula.Counter<NewCoverageCounter> {
238 private final IntVariationValue.Array newLines = IntVariationValue.newArray();
239 private final IntVariationValue.Array newCoveredLines = IntVariationValue.newArray();
240 private final IntVariationValue.Array newConditions = IntVariationValue.newArray();
241 private final IntVariationValue.Array newCoveredConditions = IntVariationValue.newArray();
242 private final ScmInfoRepository scmInfoRepository;
243 private final NewCoverageInputMetricKeys metricKeys;
245 public NewCoverageCounter(ScmInfoRepository scmInfoRepository, NewCoverageInputMetricKeys metricKeys) {
246 this.scmInfoRepository = scmInfoRepository;
247 this.metricKeys = metricKeys;
251 public void aggregate(NewCoverageCounter counter) {
252 newLines.incrementAll(counter.newLines);
253 newCoveredLines.incrementAll(counter.newCoveredLines);
254 newConditions.incrementAll(counter.newConditions);
255 newCoveredConditions.incrementAll(counter.newCoveredConditions);
259 public void initialize(CounterInitializationContext context) {
260 Component fileComponent = context.getLeaf();
261 Optional<ScmInfo> scmInfoOptional = scmInfoRepository.getScmInfo(fileComponent);
262 if (!scmInfoOptional.isPresent()) {
265 ScmInfo componentScm = scmInfoOptional.get();
267 context.getPeriods().forEach(period -> {
268 newLines.increment(period, 0);
269 newCoveredLines.increment(period, 0);
270 newConditions.increment(period, 0);
271 newCoveredConditions.increment(period, 0);
274 Optional<Measure> hitsByLineMeasure = context.getMeasure(metricKeys.getCoverageLineHitsData());
275 Map<Integer, Integer> hitsByLine = parseCountByLine(hitsByLineMeasure);
276 Map<Integer, Integer> conditionsByLine = parseCountByLine(context.getMeasure(metricKeys.getConditionsByLine()));
277 Map<Integer, Integer> coveredConditionsByLine = parseCountByLine(context.getMeasure(metricKeys.getCoveredConditionsByLine()));
279 for (Map.Entry<Integer, Integer> entry : hitsByLine.entrySet()) {
280 int lineId = entry.getKey();
281 int hits = entry.getValue();
282 int conditions = (Integer) ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0);
283 int coveredConditions = (Integer) ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0);
284 long date = componentScm.getChangesetForLine(lineId).getDate();
285 analyze(context.getPeriods(), date, hits, conditions, coveredConditions);
289 private static Map<Integer, Integer> parseCountByLine(Optional<Measure> measure) {
290 if (measure.isPresent() && measure.get().getValueType() != Measure.ValueType.NO_VALUE) {
291 return KeyValueFormat.parseIntInt(measure.get().getStringValue());
293 return Collections.emptyMap();
296 public void analyze(List<Period> periods, @Nullable Long lineDate, int hits, int conditions, int coveredConditions) {
297 if (lineDate == null) {
300 for (Period period : periods) {
301 if (isLineInPeriod(lineDate, period)) {
302 incrementLines(period, hits);
303 incrementConditions(period, conditions, coveredConditions);
309 * A line belongs to a Period if its date is older than the SNAPSHOT's date of the period.
311 private static boolean isLineInPeriod(long lineDate, Period period) {
312 return lineDate > period.getSnapshotDate();
315 private void incrementLines(Period period, int hits) {
316 newLines.increment(period, 1);
318 newCoveredLines.increment(period, 1);
322 private void incrementConditions(Period period, int conditions, int coveredConditions) {
323 newConditions.increment(period, conditions);
324 if (conditions > 0) {
325 newCoveredConditions.increment(period, coveredConditions);
329 public boolean hasNewCode(Period period) {
330 return newLines.get(period).isSet();
333 public int getNewLines(Period period) {
334 return newLines.get(period).getValue();
337 public int getNewCoveredLines(Period period) {
338 return newCoveredLines.get(period).getValue();
341 public int getNewConditions(Period period) {
342 return newConditions.get(period).getValue();
345 public int getNewCoveredConditions(Period period) {
346 return newCoveredConditions.get(period).getValue();
351 public static final class NewCoverageOutputMetricKeys {
352 private final String newLinesToCover;
353 private final String newUncoveredLines;
354 private final String newConditionsToCover;
355 private final String newUncoveredConditions;
357 public NewCoverageOutputMetricKeys(String newLinesToCover, String newUncoveredLines, String newConditionsToCover, String newUncoveredConditions) {
358 this.newLinesToCover = newLinesToCover;
359 this.newUncoveredLines = newUncoveredLines;
360 this.newConditionsToCover = newConditionsToCover;
361 this.newUncoveredConditions = newUncoveredConditions;
364 public String getNewLinesToCover() {
365 return newLinesToCover;
368 public String getNewUncoveredLines() {
369 return newUncoveredLines;
372 public String getNewConditionsToCover() {
373 return newConditionsToCover;
376 public String getNewUncoveredConditions() {
377 return newUncoveredConditions;
382 public static class NewCoverageInputMetricKeys {
383 private final String coverageLineHitsData;
384 private final String conditionsByLine;
385 private final String coveredConditionsByLine;
387 public NewCoverageInputMetricKeys(String coverageLineHitsData, String conditionsByLine, String coveredConditionsByLine) {
388 this.coverageLineHitsData = coverageLineHitsData;
389 this.conditionsByLine = conditionsByLine;
390 this.coveredConditionsByLine = coveredConditionsByLine;
393 public String getCoverageLineHitsData() {
394 return coverageLineHitsData;
397 public String getConditionsByLine() {
398 return conditionsByLine;
401 public String getCoveredConditionsByLine() {
402 return coveredConditionsByLine;