3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info 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.ce.task.projectanalysis.step;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.Iterables;
24 import java.util.Collections;
25 import java.util.List;
27 import java.util.Optional;
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;
55 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
58 * Computes measures related to the New Coverage. These measures do not have values, only variations.
60 public class NewCoverageMeasuresStep implements ComputationStep {
62 private static final List<Formula> FORMULAS = ImmutableList.of(
64 new NewCoverageFormula(),
65 new NewBranchCoverageFormula(),
66 new NewLineCoverageFormula());
68 private final TreeRootHolder treeRootHolder;
69 private final MetricRepository metricRepository;
70 private final MeasureRepository measureRepository;
72 private final NewLinesRepository newLinesRepository;
75 * Constructor used when processing a Report (ie. a {@link NewLinesRepository} instance is available in the container)
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;
86 * Constructor used when processing Views (ie. no {@link NewLinesRepository} instance is available in the container)
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;
96 public void execute(ComputationStep.Context context) {
97 new PathAwareCrawler<>(
98 FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
100 Iterables.concat(NewLinesAndConditionsCoverageFormula.from(newLinesRepository), FORMULAS)))
101 .visit(treeRootHolder.getRoot());
105 public String getDescription() {
106 return "Compute new coverage";
109 private static class NewLinesAndConditionsCoverageFormula extends NewLinesAndConditionsFormula {
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);
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),
123 public static Iterable<Formula<?>> from(@Nullable NewLinesRepository newLinesRepository) {
124 if (newLinesRepository == null) {
125 return VIEWS_FORMULAS;
127 return Collections.singleton(new NewLinesAndConditionsCoverageFormula(newLinesRepository));
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.
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()));
143 private static class NewCoverageFormula extends LinesAndConditionsWithUncoveredVariationFormula {
144 public NewCoverageFormula() {
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);
153 private static class NewBranchCoverageFormula extends SingleWithUncoveredVariationFormula {
154 public NewBranchCoverageFormula() {
156 new SingleWithUncoveredMetricKeys(CoreMetrics.NEW_CONDITIONS_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_CONDITIONS_KEY),
157 CoreMetrics.NEW_BRANCH_COVERAGE_KEY);
161 private static class NewLineCoverageFormula extends SingleWithUncoveredVariationFormula {
162 public NewLineCoverageFormula() {
164 new SingleWithUncoveredMetricKeys(CoreMetrics.NEW_LINES_TO_COVER_KEY, CoreMetrics.NEW_UNCOVERED_LINES_KEY),
165 CoreMetrics.NEW_LINE_COVERAGE_KEY);
169 public static class NewLinesAndConditionsFormula implements Formula<NewCoverageCounter> {
170 private final NewLinesRepository newLinesRepository;
171 private final NewCoverageInputMetricKeys inputMetricKeys;
172 private final NewCoverageOutputMetricKeys outputMetricKeys;
174 public NewLinesAndConditionsFormula(NewLinesRepository newLinesRepository, NewCoverageInputMetricKeys inputMetricKeys, NewCoverageOutputMetricKeys outputMetricKeys) {
175 this.newLinesRepository = newLinesRepository;
176 this.inputMetricKeys = inputMetricKeys;
177 this.outputMetricKeys = outputMetricKeys;
181 public NewCoverageCounter createNewCounter() {
182 return new NewCoverageCounter(newLinesRepository, inputMetricKeys);
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());
191 return Optional.empty();
194 private int computeValueForMetric(NewCoverageCounter counter, Metric metric) {
195 if (metric.getKey().equals(outputMetricKeys.getNewLinesToCover())) {
196 return counter.getNewLines();
198 if (metric.getKey().equals(outputMetricKeys.getNewUncoveredLines())) {
199 return counter.getNewLines() - counter.getNewCoveredLines();
201 if (metric.getKey().equals(outputMetricKeys.getNewConditionsToCover())) {
202 return counter.getNewConditions();
204 if (metric.getKey().equals(outputMetricKeys.getNewUncoveredConditions())) {
205 return counter.getNewConditions() - counter.getNewCoveredConditions();
207 throw new IllegalArgumentException("Unsupported metric " + metric.getKey());
211 public String[] getOutputMetricKeys() {
212 return new String[] {
213 outputMetricKeys.getNewLinesToCover(),
214 outputMetricKeys.getNewUncoveredLines(),
215 outputMetricKeys.getNewConditionsToCover(),
216 outputMetricKeys.getNewUncoveredConditions()
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;
229 public NewCoverageCounter(NewLinesRepository newLinesRepository, NewCoverageInputMetricKeys metricKeys) {
230 this.newLinesRepository = newLinesRepository;
231 this.metricKeys = metricKeys;
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);
243 public void initialize(CounterInitializationContext context) {
244 Component component = context.getLeaf();
245 if (component.getType() != Component.Type.FILE) {
248 Optional<Set<Integer>> newLinesSet = newLinesRepository.getNewLines(component);
249 if (!newLinesSet.isPresent()) {
253 newLines.increment(0);
254 newCoveredLines.increment(0);
255 newConditions.increment(0);
256 newCoveredConditions.increment(0);
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()));
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);
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());
278 return Collections.emptyMap();
281 void analyze(int hits, int conditions, int coveredConditions) {
282 incrementLines(hits);
283 incrementConditions(conditions, coveredConditions);
286 private void incrementLines(int hits) {
287 newLines.increment(1);
289 newCoveredLines.increment(1);
293 private void incrementConditions(int conditions, int coveredConditions) {
294 newConditions.increment(conditions);
295 if (conditions > 0) {
296 newCoveredConditions.increment(coveredConditions);
300 boolean hasNewCode() {
301 return newLines.isSet();
305 return newLines.getValue();
308 int getNewCoveredLines() {
309 return newCoveredLines.getValue();
312 int getNewConditions() {
313 return newConditions.getValue();
316 int getNewCoveredConditions() {
317 return newCoveredConditions.getValue();
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;
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;
335 public String getNewLinesToCover() {
336 return newLinesToCover;
339 public String getNewUncoveredLines() {
340 return newUncoveredLines;
343 public String getNewConditionsToCover() {
344 return newConditionsToCover;
347 public String getNewUncoveredConditions() {
348 return newUncoveredConditions;
353 public static class NewCoverageInputMetricKeys {
354 private final String coverageLineHitsData;
355 private final String conditionsByLine;
356 private final String coveredConditionsByLine;
358 public NewCoverageInputMetricKeys(String coverageLineHitsData, String conditionsByLine, String coveredConditionsByLine) {
359 this.coverageLineHitsData = coverageLineHitsData;
360 this.conditionsByLine = conditionsByLine;
361 this.coveredConditionsByLine = coveredConditionsByLine;
364 public String getCoverageLineHitsData() {
365 return coverageLineHitsData;
368 public String getConditionsByLine() {
369 return conditionsByLine;
372 public String getCoveredConditionsByLine() {
373 return coveredConditionsByLine;