3 * Copyright (C) 2009-2022 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 java.util.Optional;
23 import org.junit.Before;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.sonar.api.measures.Metric;
27 import org.sonar.api.utils.System2;
28 import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
29 import org.sonar.ce.task.projectanalysis.component.Component;
30 import org.sonar.ce.task.projectanalysis.component.FileStatuses;
31 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
32 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
33 import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
34 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
35 import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
36 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
37 import org.sonar.ce.task.step.ComputationStep;
38 import org.sonar.ce.task.step.TestComputationStepContext;
39 import org.sonar.db.DbClient;
40 import org.sonar.db.DbTester;
41 import org.sonar.db.component.ComponentDto;
42 import org.sonar.db.measure.LiveMeasureDto;
43 import org.sonar.db.metric.MetricDto;
44 import org.sonar.server.project.Project;
46 import static java.util.Collections.emptyList;
47 import static org.assertj.core.api.Assertions.assertThat;
48 import static org.mockito.ArgumentMatchers.any;
49 import static org.mockito.Mockito.mock;
50 import static org.mockito.Mockito.when;
51 import static org.sonar.api.measures.CoreMetrics.BUGS;
52 import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
53 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
54 import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
55 import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT_VIEW;
56 import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW;
57 import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
58 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
59 import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
61 public class PersistLiveMeasuresStepTest extends BaseStepTest {
63 private static final Metric STRING_METRIC = new Metric.Builder("string-metric", "String metric", Metric.ValueType.STRING).create();
64 private static final Metric INT_METRIC = new Metric.Builder("int-metric", "int metric", Metric.ValueType.INT).create();
65 private static final Metric METRIC_WITH_BEST_VALUE = new Metric.Builder("best-value-metric", "best value metric", Metric.ValueType.INT)
67 .setOptimizedBestValue(true)
70 private static final int REF_1 = 1;
71 private static final int REF_2 = 2;
72 private static final int REF_3 = 3;
73 private static final int REF_4 = 4;
76 public DbTester db = DbTester.create(System2.INSTANCE);
78 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
80 public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
82 public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
84 public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
86 private final FileStatuses fileStatuses = mock(FileStatuses.class);
87 private final DbClient dbClient = db.getDbClient();
88 private final TestComputationStepContext context = new TestComputationStepContext();
92 MetricDto stringMetricDto = db.measures().insertMetric(m -> m.setKey(STRING_METRIC.getKey()).setValueType(Metric.ValueType.STRING.name()));
93 MetricDto intMetricDto = db.measures().insertMetric(m -> m.setKey(INT_METRIC.getKey()).setValueType(Metric.ValueType.INT.name()));
94 MetricDto bestValueMMetricDto = db.measures()
95 .insertMetric(m -> m.setKey(METRIC_WITH_BEST_VALUE.getKey()).setValueType(Metric.ValueType.INT.name()).setOptimizedBestValue(true).setBestValue(0.0));
96 MetricDto bugs = db.measures().insertMetric(m -> m.setKey(BUGS.getKey()));
97 metricRepository.add(stringMetricDto.getUuid(), STRING_METRIC);
98 metricRepository.add(intMetricDto.getUuid(), INT_METRIC);
99 metricRepository.add(bestValueMMetricDto.getUuid(), METRIC_WITH_BEST_VALUE);
100 metricRepository.add(bugs.getUuid(), BUGS);
104 public void persist_live_measures_of_project_analysis() {
107 // the computed measures
108 measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
109 measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("dir-value"));
110 measureRepository.addRawMeasure(REF_4, STRING_METRIC.getKey(), newMeasureBuilder().create("file-value"));
112 step().execute(context);
114 // all measures are persisted, from project to file
115 assertThat(db.countRowsOfTable("live_measures")).isEqualTo(3);
116 assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("project-value");
117 assertThat(selectMeasure("dir-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("dir-value");
118 assertThat(selectMeasure("file-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("file-value");
119 verifyStatistics(context, 3);
123 public void measures_without_value_are_not_persisted() {
125 measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().createNoValue());
126 measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().createNoValue());
128 step().execute(context);
130 assertThatMeasureIsNotPersisted("project-uuid", STRING_METRIC);
131 assertThatMeasureIsNotPersisted("project-uuid", INT_METRIC);
132 verifyStatistics(context, 0);
136 public void measures_on_new_code_period_are_persisted() {
138 measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().setVariation(42.0).createNoValue());
140 step().execute(context);
142 LiveMeasureDto persistedMeasure = selectMeasure("project-uuid", INT_METRIC).get();
143 assertThat(persistedMeasure.getValue()).isNull();
144 assertThat(persistedMeasure.getVariation()).isEqualTo(42.0);
145 verifyStatistics(context, 1);
149 public void delete_measures_from_db_if_no_longer_computed() {
151 // measure to be updated
152 LiveMeasureDto measureOnFileInProject = insertMeasure("file-uuid", "project-uuid", INT_METRIC);
153 // measure to be deleted because not computed anymore
154 LiveMeasureDto otherMeasureOnFileInProject = insertMeasure("file-uuid", "project-uuid", STRING_METRIC);
155 // measure in another project, not touched
156 LiveMeasureDto measureInOtherProject = insertMeasure("other-file-uuid", "other-project-uuid", INT_METRIC);
159 measureRepository.addRawMeasure(REF_4, INT_METRIC.getKey(), newMeasureBuilder().create(42));
161 step().execute(context);
163 assertThatMeasureHasValue(measureOnFileInProject, 42);
164 assertThatMeasureDoesNotExist(otherMeasureOnFileInProject);
165 assertThatMeasureHasValue(measureInOtherProject, (int) measureInOtherProject.getValue().doubleValue());
166 verifyStatistics(context, 1);
170 public void do_not_persist_file_measures_with_best_value() {
172 // measure to be deleted because new value matches the metric best value
173 LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", INT_METRIC);
176 // project measure with metric best value -> persist with value 0
177 measureRepository.addRawMeasure(REF_1, METRIC_WITH_BEST_VALUE.getKey(), newMeasureBuilder().create(0));
178 // file measure with metric best value -> do not persist
179 measureRepository.addRawMeasure(REF_4, METRIC_WITH_BEST_VALUE.getKey(), newMeasureBuilder().create(0));
181 step().execute(context);
183 assertThatMeasureDoesNotExist(oldMeasure);
184 assertThatMeasureHasValue("project-uuid", METRIC_WITH_BEST_VALUE, 0);
185 verifyStatistics(context, 1);
189 public void keep_measures_for_unchanged_files() {
191 LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", BUGS);
193 when(fileStatuses.isDataUnchanged(any(Component.class))).thenReturn(true);
194 // this new value won't be persisted
195 measureRepository.addRawMeasure(REF_4, BUGS.getKey(), newMeasureBuilder().create(oldMeasure.getValue() + 1, 0));
196 step().execute(context);
197 assertThat(selectMeasure("file-uuid", BUGS).get().getValue()).isEqualTo(oldMeasure.getValue());
201 public void dont_keep_measures_for_unchanged_files() {
203 LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", BUGS);
205 when(fileStatuses.isDataUnchanged(any(Component.class))).thenReturn(false);
206 // this new value will be persisted
207 measureRepository.addRawMeasure(REF_4, BUGS.getKey(), newMeasureBuilder().create(oldMeasure.getValue() + 1, 0));
208 step().execute(context);
209 assertThat(selectMeasure("file-uuid", BUGS).get().getValue()).isEqualTo(oldMeasure.getValue() + 1);
213 public void persist_live_measures_of_portfolio_analysis() {
216 // the computed measures
217 measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("view-value"));
218 measureRepository.addRawMeasure(REF_2, STRING_METRIC.getKey(), newMeasureBuilder().create("subview-value"));
219 measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
221 step().execute(context);
223 assertThat(db.countRowsOfTable("live_measures")).isEqualTo(3);
224 assertThat(selectMeasure("view-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("view-value");
225 assertThat(selectMeasure("subview-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("subview-value");
226 assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("project-value");
227 verifyStatistics(context, 3);
230 private LiveMeasureDto insertMeasure(String componentUuid, String projectUuid, Metric metric) {
231 LiveMeasureDto measure = newLiveMeasure()
232 .setComponentUuid(componentUuid)
233 .setProjectUuid(projectUuid)
234 .setMetricUuid(metricRepository.getByKey(metric.getKey()).getUuid());
235 dbClient.liveMeasureDao().insertOrUpdate(db.getSession(), measure);
239 private void assertThatMeasureHasValue(LiveMeasureDto template, int expectedValue) {
240 Optional<LiveMeasureDto> persisted = dbClient.liveMeasureDao().selectMeasure(db.getSession(),
241 template.getComponentUuid(), metricRepository.getByUuid(template.getMetricUuid()).getKey());
242 assertThat(persisted).isPresent();
243 assertThat(persisted.get().getValue()).isEqualTo(expectedValue);
246 private void assertThatMeasureHasValue(String componentUuid, Metric metric, int expectedValue) {
247 Optional<LiveMeasureDto> persisted = dbClient.liveMeasureDao().selectMeasure(db.getSession(),
248 componentUuid, metric.getKey());
249 assertThat(persisted).isPresent();
250 assertThat(persisted.get().getValue()).isEqualTo(expectedValue);
253 private void assertThatMeasureDoesNotExist(LiveMeasureDto template) {
254 assertThat(dbClient.liveMeasureDao().selectMeasure(db.getSession(),
255 template.getComponentUuid(), metricRepository.getByUuid(template.getMetricUuid()).getKey()))
259 private void prepareProject() {
260 // tree of components as defined by scanner report
261 Component project = ReportComponent.builder(PROJECT, REF_1).setUuid("project-uuid")
263 ReportComponent.builder(DIRECTORY, REF_3).setUuid("dir-uuid")
265 ReportComponent.builder(FILE, REF_4).setUuid("file-uuid")
269 treeRootHolder.setRoot(project);
270 analysisMetadataHolder.setProject(new Project(project.getUuid(), project.getDbKey(), project.getName(), project.getDescription(), emptyList()));
272 // components as persisted in db
273 ComponentDto projectDto = insertComponent("project-key", "project-uuid");
274 ComponentDto dirDto = insertComponent("dir-key", "dir-uuid");
275 ComponentDto fileDto = insertComponent("file-key", "file-uuid");
278 private void preparePortfolio() {
279 // tree of components
280 Component portfolio = ViewsComponent.builder(VIEW, REF_1).setUuid("view-uuid")
282 ViewsComponent.builder(SUBVIEW, REF_2).setUuid("subview-uuid")
284 ViewsComponent.builder(PROJECT_VIEW, REF_3).setUuid("project-uuid")
288 treeRootHolder.setRoot(portfolio);
290 // components as persisted in db
291 ComponentDto portfolioDto = insertComponent("view-key", "view-uuid");
292 ComponentDto subViewDto = insertComponent("subview-key", "subview-uuid");
293 ComponentDto projectDto = insertComponent("project-key", "project-uuid");
294 analysisMetadataHolder.setProject(Project.from(portfolioDto));
297 private void assertThatMeasureIsNotPersisted(String componentUuid, Metric metric) {
298 assertThat(selectMeasure(componentUuid, metric)).isEmpty();
301 private Optional<LiveMeasureDto> selectMeasure(String componentUuid, Metric metric) {
302 return dbClient.liveMeasureDao().selectMeasure(db.getSession(), componentUuid, metric.getKey());
305 private ComponentDto insertComponent(String key, String uuid) {
306 ComponentDto componentDto = new ComponentDto()
309 .setUuidPath(uuid + ".")
311 .setProjectUuid(uuid);
312 db.components().insertComponent(componentDto);
317 protected ComputationStep step() {
318 return new PersistLiveMeasuresStep(dbClient, metricRepository, new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder), treeRootHolder, measureRepository,
319 Optional.of(fileStatuses));
322 private static void verifyStatistics(TestComputationStepContext context, int expectedInsertsOrUpdates) {
323 context.getStatistics().assertValue("insertsOrUpdates", expectedInsertsOrUpdates);