]> source.dussan.org Git - sonarqube.git/blob
64bce16d146441dd921461e5e03f45d323734bc6
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2019 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.task.projectanalysis.step;
21
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.Iterables;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.Set;
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.ce.task.projectanalysis.component.Component;
35 import org.sonar.ce.task.projectanalysis.component.PathAwareCrawler;
36 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
37 import org.sonar.ce.task.projectanalysis.formula.Counter;
38 import org.sonar.ce.task.projectanalysis.formula.CounterInitializationContext;
39 import org.sonar.ce.task.projectanalysis.formula.CreateMeasureContext;
40 import org.sonar.ce.task.projectanalysis.formula.Formula;
41 import org.sonar.ce.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
42 import org.sonar.ce.task.projectanalysis.formula.VariationSumFormula;
43 import org.sonar.ce.task.projectanalysis.formula.counter.IntValue;
44 import org.sonar.ce.task.projectanalysis.formula.coverage.LinesAndConditionsWithUncoveredMetricKeys;
45 import org.sonar.ce.task.projectanalysis.formula.coverage.LinesAndConditionsWithUncoveredVariationFormula;
46 import org.sonar.ce.task.projectanalysis.formula.coverage.SingleWithUncoveredMetricKeys;
47 import org.sonar.ce.task.projectanalysis.formula.coverage.SingleWithUncoveredVariationFormula;
48 import org.sonar.ce.task.projectanalysis.measure.Measure;
49 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
50 import org.sonar.ce.task.projectanalysis.metric.Metric;
51 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
52 import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
53 import org.sonar.ce.task.step.ComputationStep;
54
55 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
56
57 /**
58  * Computes measures related to the New Coverage. These measures do not have values, only variations.
59  */
60 public class NewCoverageMeasuresStep implements ComputationStep {
61
62   private static final List<Formula> FORMULAS = ImmutableList.of(
63     // UT coverage
64     new NewCoverageFormula(),
65     new NewBranchCoverageFormula(),
66     new NewLineCoverageFormula());
67
68   private final TreeRootHolder treeRootHolder;
69   private final MetricRepository metricRepository;
70   private final MeasureRepository measureRepository;
71   @Nullable
72   private final NewLinesRepository newLinesRepository;
73
74   /**
75    * Constructor used when processing a Report (ie. a {@link NewLinesRepository} instance is available in the container)
76    */
77   public NewCoverageMeasuresStep(TreeRootHolder treeRootHolder,
78     MeasureRepository measureRepository, MetricRepository metricRepository, NewLinesRepository newLinesRepository) {
79     this.treeRootHolder = treeRootHolder;
80     this.metricRepository = metricRepository;
81     this.measureRepository = measureRepository;
82     this.newLinesRepository = newLinesRepository;
83   }
84
85   /**
86    * Constructor used when processing Views (ie. no {@link NewLinesRepository} instance is available in the container)
87    */
88   public NewCoverageMeasuresStep(TreeRootHolder treeRootHolder, MeasureRepository measureRepository, MetricRepository metricRepository) {
89     this.treeRootHolder = treeRootHolder;
90     this.metricRepository = metricRepository;
91     this.measureRepository = measureRepository;
92     this.newLinesRepository = null;
93   }
94
95   @Override
96   public void execute(ComputationStep.Context context) {
97     new PathAwareCrawler<>(
98       FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
99         .buildFor(
100           Iterables.concat(NewLinesAndConditionsCoverageFormula.from(newLinesRepository), FORMULAS)))
101       .visit(treeRootHolder.getRoot());
102   }
103
104   @Override
105   public String getDescription() {
106     return "Compute new coverage";
107   }
108
109   private static class NewLinesAndConditionsCoverageFormula extends NewLinesAndConditionsFormula {
110
111     private static final NewCoverageOutputMetricKeys OUTPUT_METRIC_KEYS = new NewCoverageOutputMetricKeys(
112       CoreMetrics.NEW_LINES_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_LINES_KEY,
113       CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY);
114     private static final Iterable<Formula<?>> VIEWS_FORMULAS = variationSumFormulas(OUTPUT_METRIC_KEYS);
115
116     private NewLinesAndConditionsCoverageFormula(NewLinesRepository newLinesRepository) {
117       super(newLinesRepository,
118         new NewCoverageInputMetricKeys(
119           CoreMetrics.COVERAGE_LINE_HITS_DATA_KEY, CoreMetrics.CONDITIONS_BY_LINE_KEY, CoreMetrics.COVERED_CONDITIONS_BY_LINE_KEY),
120         OUTPUT_METRIC_KEYS);
121     }
122
123     public static Iterable<Formula<?>> from(@Nullable NewLinesRepository newLinesRepository) {
124       if (newLinesRepository == null) {
125         return VIEWS_FORMULAS;
126       }
127       return Collections.singleton(new NewLinesAndConditionsCoverageFormula(newLinesRepository));
128     }
129
130     /**
131      * Creates a List of {@link org.sonar.ce.task.projectanalysis.formula.SumFormula.IntSumFormula} for each
132      * metric key of the specified {@link NewCoverageOutputMetricKeys} instance.
133      */
134     private static Iterable<Formula<?>> variationSumFormulas(NewCoverageOutputMetricKeys outputMetricKeys) {
135       return ImmutableList.of(
136         new VariationSumFormula(outputMetricKeys.getNewLinesToCover()),
137         new VariationSumFormula(outputMetricKeys.getNewUncoveredLines()),
138         new VariationSumFormula(outputMetricKeys.getNewConditionsToCover()),
139         new VariationSumFormula(outputMetricKeys.getNewUncoveredConditions()));
140     }
141   }
142
143   private static class NewCoverageFormula extends LinesAndConditionsWithUncoveredVariationFormula {
144     public NewCoverageFormula() {
145       super(
146         new LinesAndConditionsWithUncoveredMetricKeys(
147           CoreMetrics.NEW_LINES_TO_COVER_KEY, CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY,
148           CoreMetrics.NEW_UNCOVERED_LINES_KEY, CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY),
149         CoreMetrics.NEW_COVERAGE_KEY);
150     }
151   }
152
153   private static class NewBranchCoverageFormula extends SingleWithUncoveredVariationFormula {
154     public NewBranchCoverageFormula() {
155       super(
156         new SingleWithUncoveredMetricKeys(CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY),
157         CoreMetrics.NEW_BRANCH_COVERAGE_KEY);
158     }
159   }
160
161   private static class NewLineCoverageFormula extends SingleWithUncoveredVariationFormula {
162     public NewLineCoverageFormula() {
163       super(
164         new SingleWithUncoveredMetricKeys(CoreMetrics.NEW_LINES_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_LINES_KEY),
165         CoreMetrics.NEW_LINE_COVERAGE_KEY);
166     }
167   }
168
169   public static class NewLinesAndConditionsFormula implements Formula<NewCoverageCounter> {
170     private final NewLinesRepository newLinesRepository;
171     private final NewCoverageInputMetricKeys inputMetricKeys;
172     private final NewCoverageOutputMetricKeys outputMetricKeys;
173
174     public NewLinesAndConditionsFormula(NewLinesRepository newLinesRepository, NewCoverageInputMetricKeys inputMetricKeys, NewCoverageOutputMetricKeys outputMetricKeys) {
175       this.newLinesRepository = newLinesRepository;
176       this.inputMetricKeys = inputMetricKeys;
177       this.outputMetricKeys = outputMetricKeys;
178     }
179
180     @Override
181     public NewCoverageCounter createNewCounter() {
182       return new NewCoverageCounter(newLinesRepository, inputMetricKeys);
183     }
184
185     @Override
186     public Optional<Measure> createMeasure(NewCoverageCounter counter, CreateMeasureContext context) {
187       if (counter.hasNewCode()) {
188         int value = computeValueForMetric(counter, context.getMetric());
189         return Optional.of(newMeasureBuilder().setVariation(value).createNoValue());
190       }
191       return Optional.empty();
192     }
193
194     private int computeValueForMetric(NewCoverageCounter counter, Metric metric) {
195       if (metric.getKey().equals(outputMetricKeys.getNewLinesToCover())) {
196         return counter.getNewLines();
197       }
198       if (metric.getKey().equals(outputMetricKeys.getNewUncoveredLines())) {
199         return counter.getNewLines() - counter.getNewCoveredLines();
200       }
201       if (metric.getKey().equals(outputMetricKeys.getNewConditionsToCover())) {
202         return counter.getNewConditions();
203       }
204       if (metric.getKey().equals(outputMetricKeys.getNewUncoveredConditions())) {
205         return counter.getNewConditions() - counter.getNewCoveredConditions();
206       }
207       throw new IllegalArgumentException("Unsupported metric " + metric.getKey());
208     }
209
210     @Override
211     public String[] getOutputMetricKeys() {
212       return new String[] {
213         outputMetricKeys.getNewLinesToCover(),
214         outputMetricKeys.getNewUncoveredLines(),
215         outputMetricKeys.getNewConditionsToCover(),
216         outputMetricKeys.getNewUncoveredConditions()
217       };
218     }
219   }
220
221   public static final class NewCoverageCounter implements Counter<NewCoverageCounter> {
222     private final IntValue newLines = new IntValue();
223     private final IntValue newCoveredLines = new IntValue();
224     private final IntValue newConditions = new IntValue();
225     private final IntValue newCoveredConditions = new IntValue();
226     private final NewLinesRepository newLinesRepository;
227     private final NewCoverageInputMetricKeys metricKeys;
228
229     public NewCoverageCounter(NewLinesRepository newLinesRepository, NewCoverageInputMetricKeys metricKeys) {
230       this.newLinesRepository = newLinesRepository;
231       this.metricKeys = metricKeys;
232     }
233
234     @Override
235     public void aggregate(NewCoverageCounter counter) {
236       newLines.increment(counter.newLines);
237       newCoveredLines.increment(counter.newCoveredLines);
238       newConditions.increment(counter.newConditions);
239       newCoveredConditions.increment(counter.newCoveredConditions);
240     }
241
242     @Override
243     public void initialize(CounterInitializationContext context) {
244       Component component = context.getLeaf();
245       if (component.getType() != Component.Type.FILE) {
246         return;
247       }
248       Optional<Set<Integer>> newLinesSet = newLinesRepository.getNewLines(component);
249       if (!newLinesSet.isPresent()) {
250         return;
251       }
252
253       newLines.increment(0);
254       newCoveredLines.increment(0);
255       newConditions.increment(0);
256       newCoveredConditions.increment(0);
257
258       Optional<Measure> hitsByLineMeasure = context.getMeasure(metricKeys.getCoverageLineHitsData());
259       Map<Integer, Integer> hitsByLine = parseCountByLine(hitsByLineMeasure);
260       Map<Integer, Integer> conditionsByLine = parseCountByLine(context.getMeasure(metricKeys.getConditionsByLine()));
261       Map<Integer, Integer> coveredConditionsByLine = parseCountByLine(context.getMeasure(metricKeys.getCoveredConditionsByLine()));
262
263       for (Map.Entry<Integer, Integer> entry : hitsByLine.entrySet()) {
264         int lineId = entry.getKey();
265         int hits = entry.getValue();
266         int conditions = (Integer) ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0);
267         int coveredConditions = (Integer) ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0);
268         if (newLinesSet.get().contains(lineId)) {
269           analyze(hits, conditions, coveredConditions);
270         }
271       }
272     }
273
274     private static Map<Integer, Integer> parseCountByLine(Optional<Measure> measure) {
275       if (measure.isPresent() && measure.get().getValueType() != Measure.ValueType.NO_VALUE) {
276         return KeyValueFormat.parseIntInt(measure.get().getStringValue());
277       }
278       return Collections.emptyMap();
279     }
280
281     void analyze(int hits, int conditions, int coveredConditions) {
282       incrementLines(hits);
283       incrementConditions(conditions, coveredConditions);
284     }
285
286     private void incrementLines(int hits) {
287       newLines.increment(1);
288       if (hits > 0) {
289         newCoveredLines.increment(1);
290       }
291     }
292
293     private void incrementConditions(int conditions, int coveredConditions) {
294       newConditions.increment(conditions);
295       if (conditions > 0) {
296         newCoveredConditions.increment(coveredConditions);
297       }
298     }
299
300     boolean hasNewCode() {
301       return newLines.isSet();
302     }
303
304     int getNewLines() {
305       return newLines.getValue();
306     }
307
308     int getNewCoveredLines() {
309       return newCoveredLines.getValue();
310     }
311
312     int getNewConditions() {
313       return newConditions.getValue();
314     }
315
316     int getNewCoveredConditions() {
317       return newCoveredConditions.getValue();
318     }
319   }
320
321   @Immutable
322   public static final class NewCoverageOutputMetricKeys {
323     private final String newLinesToCover;
324     private final String newUncoveredLines;
325     private final String newConditionsToCover;
326     private final String newUncoveredConditions;
327
328     public NewCoverageOutputMetricKeys(String newLinesToCover, String newUncoveredLines, String newConditionsToCover, String newUncoveredConditions) {
329       this.newLinesToCover = newLinesToCover;
330       this.newUncoveredLines = newUncoveredLines;
331       this.newConditionsToCover = newConditionsToCover;
332       this.newUncoveredConditions = newUncoveredConditions;
333     }
334
335     public String getNewLinesToCover() {
336       return newLinesToCover;
337     }
338
339     public String getNewUncoveredLines() {
340       return newUncoveredLines;
341     }
342
343     public String getNewConditionsToCover() {
344       return newConditionsToCover;
345     }
346
347     public String getNewUncoveredConditions() {
348       return newUncoveredConditions;
349     }
350   }
351
352   @Immutable
353   public static class NewCoverageInputMetricKeys {
354     private final String coverageLineHitsData;
355     private final String conditionsByLine;
356     private final String coveredConditionsByLine;
357
358     public NewCoverageInputMetricKeys(String coverageLineHitsData, String conditionsByLine, String coveredConditionsByLine) {
359       this.coverageLineHitsData = coverageLineHitsData;
360       this.conditionsByLine = conditionsByLine;
361       this.coveredConditionsByLine = coveredConditionsByLine;
362     }
363
364     public String getCoverageLineHitsData() {
365       return coverageLineHitsData;
366     }
367
368     public String getConditionsByLine() {
369       return conditionsByLine;
370     }
371
372     public String getCoveredConditionsByLine() {
373       return coveredConditionsByLine;
374     }
375   }
376 }