]> source.dussan.org Git - sonarqube.git/blob
3eab05cf3f3c232495b3c1350030c6674532cf6e
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 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 java.util.Optional;
24 import org.sonar.ce.task.projectanalysis.component.Component;
25 import org.sonar.ce.task.projectanalysis.component.PathAwareCrawler;
26 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
27 import org.sonar.ce.task.projectanalysis.formula.Counter;
28 import org.sonar.ce.task.projectanalysis.formula.CounterInitializationContext;
29 import org.sonar.ce.task.projectanalysis.formula.CreateMeasureContext;
30 import org.sonar.ce.task.projectanalysis.formula.Formula;
31 import org.sonar.ce.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
32 import org.sonar.ce.task.projectanalysis.formula.counter.IntSumCounter;
33 import org.sonar.ce.task.projectanalysis.formula.counter.LongSumCounter;
34 import org.sonar.ce.task.projectanalysis.measure.Measure;
35 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
36 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
37 import org.sonar.ce.task.step.ComputationStep;
38
39 import static org.sonar.api.measures.CoreMetrics.SKIPPED_TESTS_KEY;
40 import static org.sonar.api.measures.CoreMetrics.TESTS_KEY;
41 import static org.sonar.api.measures.CoreMetrics.TEST_ERRORS_KEY;
42 import static org.sonar.api.measures.CoreMetrics.TEST_EXECUTION_TIME_KEY;
43 import static org.sonar.api.measures.CoreMetrics.TEST_FAILURES_KEY;
44 import static org.sonar.api.measures.CoreMetrics.TEST_SUCCESS_DENSITY_KEY;
45
46 /**
47  * Computes unit test measures on files and then aggregates them on higher components.
48  */
49 public class UnitTestMeasuresStep implements ComputationStep {
50
51   private static final String[] METRICS = new String[] {TESTS_KEY, TEST_ERRORS_KEY, TEST_FAILURES_KEY, SKIPPED_TESTS_KEY, TEST_SUCCESS_DENSITY_KEY, TEST_EXECUTION_TIME_KEY};
52
53   private static final ImmutableList<Formula> FORMULAS = ImmutableList.of(new UnitTestsFormula());
54
55   private final TreeRootHolder treeRootHolder;
56   private final MetricRepository metricRepository;
57   private final MeasureRepository measureRepository;
58
59   public UnitTestMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository) {
60     this.treeRootHolder = treeRootHolder;
61     this.metricRepository = metricRepository;
62     this.measureRepository = measureRepository;
63   }
64
65   @Override
66   public void execute(ComputationStep.Context context) {
67     new PathAwareCrawler<>(
68       FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository).buildFor(FORMULAS))
69         .visit(treeRootHolder.getRoot());
70   }
71
72   private static class UnitTestsFormula implements Formula<UnitTestsCounter> {
73
74     @Override
75     public UnitTestsCounter createNewCounter() {
76       return new UnitTestsCounter();
77     }
78
79     @Override
80     public Optional<Measure> createMeasure(UnitTestsCounter counter, CreateMeasureContext context) {
81       String metricKey = context.getMetric().getKey();
82       Component leaf = counter.getLeaf();
83       switch (metricKey) {
84         case TESTS_KEY:
85           return createIntMeasure(context.getComponent(), leaf, counter.testsCounter.getValue());
86         case TEST_ERRORS_KEY:
87           return createIntMeasure(context.getComponent(), leaf, counter.testsErrorsCounter.getValue());
88         case TEST_FAILURES_KEY:
89           return createIntMeasure(context.getComponent(), leaf, counter.testsFailuresCounter.getValue());
90         case SKIPPED_TESTS_KEY:
91           return createIntMeasure(context.getComponent(), leaf, counter.skippedTestsCounter.getValue());
92         case TEST_EXECUTION_TIME_KEY:
93           return createLongMeasure(context.getComponent(), leaf, counter.testExecutionTimeCounter.getValue());
94         case TEST_SUCCESS_DENSITY_KEY:
95           return createDensityMeasure(counter, context.getMetric().getDecimalScale());
96         default:
97           throw new IllegalStateException(String.format("Metric '%s' is not supported", metricKey));
98       }
99     }
100
101     private static Optional<Measure> createIntMeasure(Component currentComponent, Component leafComponent, Optional<Integer> metricValue) {
102       if (metricValue.isPresent() && leafComponent.getType().isDeeperThan(currentComponent.getType())) {
103         return Optional.of(Measure.newMeasureBuilder().create(metricValue.get()));
104       }
105       return Optional.empty();
106     }
107
108     private static Optional<Measure> createLongMeasure(Component currentComponent, Component leafComponent, Optional<Long> metricValue) {
109       if (metricValue.isPresent() && leafComponent.getType().isDeeperThan(currentComponent.getType())) {
110         return Optional.of(Measure.newMeasureBuilder().create(metricValue.get()));
111       }
112       return Optional.empty();
113     }
114
115     private static Optional<Measure> createDensityMeasure(UnitTestsCounter counter, int decimalScale) {
116       if (isPositive(counter.testsCounter.getValue(), true)
117         && isPositive(counter.testsErrorsCounter.getValue(), false)
118         && isPositive(counter.testsFailuresCounter.getValue(), false)) {
119         int tests = counter.testsCounter.getValue().get();
120         int errors = counter.testsErrorsCounter.getValue().get();
121         int failures = counter.testsFailuresCounter.getValue().get();
122         double density = (errors + failures) * 100d / tests;
123         return Optional.of(Measure.newMeasureBuilder().create(100d - density, decimalScale));
124       }
125       return Optional.empty();
126     }
127
128     private static boolean isPositive(Optional<Integer> value, boolean isStrictComparison) {
129       return value.isPresent() && (isStrictComparison ? (value.get() > 0) : (value.get() >= 0));
130     }
131
132     @Override
133     public String[] getOutputMetricKeys() {
134       return METRICS;
135     }
136   }
137
138   private static class UnitTestsCounter implements Counter<UnitTestsCounter> {
139
140     private final IntSumCounter testsCounter = new IntSumCounter(TESTS_KEY);
141     private final IntSumCounter testsErrorsCounter = new IntSumCounter(TEST_ERRORS_KEY);
142     private final IntSumCounter testsFailuresCounter = new IntSumCounter(TEST_FAILURES_KEY);
143     private final IntSumCounter skippedTestsCounter = new IntSumCounter(SKIPPED_TESTS_KEY);
144     private final LongSumCounter testExecutionTimeCounter = new LongSumCounter(TEST_EXECUTION_TIME_KEY);
145
146     private Component leaf;
147
148     @Override
149     public void aggregate(UnitTestsCounter counter) {
150       testsCounter.aggregate(counter.testsCounter);
151       testsErrorsCounter.aggregate(counter.testsErrorsCounter);
152       testsFailuresCounter.aggregate(counter.testsFailuresCounter);
153       skippedTestsCounter.aggregate(counter.skippedTestsCounter);
154       testExecutionTimeCounter.aggregate(counter.testExecutionTimeCounter);
155     }
156
157     @Override
158     public void initialize(CounterInitializationContext context) {
159       this.leaf = context.getLeaf();
160       testsCounter.initialize(context);
161       testsErrorsCounter.initialize(context);
162       testsFailuresCounter.initialize(context);
163       skippedTestsCounter.initialize(context);
164       testExecutionTimeCounter.initialize(context);
165     }
166
167     Component getLeaf() {
168       return leaf;
169     }
170   }
171
172   @Override
173   public String getDescription() {
174     return "Compute test measures";
175   }
176 }