3 * Copyright (C) 2009-2020 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.server.measure.live;
22 import com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.concurrent.atomic.AtomicInteger;
30 import java.util.function.Supplier;
31 import javax.annotation.Nullable;
32 import org.junit.Before;
33 import org.junit.Rule;
34 import org.junit.Test;
35 import org.junit.rules.ExpectedException;
36 import org.junit.runner.RunWith;
37 import org.sonar.api.config.PropertyDefinitions;
38 import org.sonar.api.config.internal.MapSettings;
39 import org.sonar.api.measures.CoreMetrics;
40 import org.sonar.api.measures.Metric;
41 import org.sonar.api.resources.Qualifiers;
42 import org.sonar.api.utils.System2;
43 import org.sonar.core.config.CorePropertyDefinitions;
44 import org.sonar.db.DbSession;
45 import org.sonar.db.DbTester;
46 import org.sonar.db.component.BranchDto;
47 import org.sonar.db.component.BranchType;
48 import org.sonar.db.component.ComponentDto;
49 import org.sonar.db.component.ComponentTesting;
50 import org.sonar.db.measure.LiveMeasureDto;
51 import org.sonar.db.metric.MetricDto;
52 import org.sonar.db.organization.OrganizationDto;
53 import org.sonar.db.project.ProjectDto;
54 import org.sonar.server.es.ProjectIndexer;
55 import org.sonar.server.es.TestProjectIndexers;
56 import org.sonar.server.measure.Rating;
57 import org.sonar.server.qualitygate.EvaluatedQualityGate;
58 import org.sonar.server.qualitygate.QualityGate;
59 import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
60 import org.sonar.server.setting.ProjectConfigurationLoader;
61 import org.sonar.server.setting.TestProjectConfigurationLoader;
63 import static java.util.Arrays.asList;
64 import static java.util.Collections.emptyList;
65 import static java.util.Collections.singleton;
66 import static org.assertj.core.api.Assertions.assertThat;
67 import static org.mockito.ArgumentMatchers.any;
68 import static org.mockito.ArgumentMatchers.argThat;
69 import static org.mockito.ArgumentMatchers.eq;
70 import static org.mockito.ArgumentMatchers.same;
71 import static org.mockito.Mockito.mock;
72 import static org.mockito.Mockito.verify;
73 import static org.mockito.Mockito.when;
74 import static org.sonar.api.resources.Qualifiers.ORDERED_BOTTOM_UP;
76 @RunWith(DataProviderRunner.class)
77 public class LiveMeasureComputerImplTest {
80 public DbTester db = DbTester.create();
82 public ExpectedException expectedException = ExpectedException.none();
84 private final TestProjectIndexers projectIndexer = new TestProjectIndexers();
85 private MetricDto intMetric;
86 private MetricDto ratingMetric;
87 private MetricDto alertStatusMetric;
88 private OrganizationDto organization;
89 private ComponentDto project;
90 private ProjectDto projectDto;
91 private ComponentDto dir;
92 private ComponentDto file1;
93 private ComponentDto file2;
94 private ComponentDto branch;
95 private ComponentDto branchFile;
96 private final LiveQualityGateComputer qGateComputer = mock(LiveQualityGateComputer.class);
97 private final QualityGate qualityGate = mock(QualityGate.class);
98 private final EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
101 public void setUp() {
102 intMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.INT.name()));
103 ratingMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.RATING.name()));
104 alertStatusMetric = db.measures().insertMetric(m -> m.setKey(CoreMetrics.ALERT_STATUS_KEY));
105 organization = db.organizations().insert();
106 project = db.components().insertPublicProject(organization);
107 projectDto = db.components().getProjectDto(project);
108 dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java"));
109 file1 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
110 file2 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
111 branch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
112 branchFile = db.components().insertComponent(ComponentTesting.newFileDto(branch));
116 public void compute_and_insert_measures_if_they_do_not_exist_yet() {
117 markProjectAsAnalyzed(project);
119 List<QGChangeEvent> result = run(asList(file1, file2), newQualifierBasedIntFormula(), newRatingConstantFormula(Rating.C));
121 // 2 measures per component have been created
122 // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
123 assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(8);
124 assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
125 assertThatRatingMeasureHasValue(file1, Rating.C);
126 assertThatIntMeasureHasValue(file2, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
127 assertThatRatingMeasureHasValue(file2, Rating.C);
128 assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
129 assertThatRatingMeasureHasValue(dir, Rating.C);
130 assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
131 assertThatRatingMeasureHasValue(project, Rating.C);
132 assertThatProjectChanged(result, project);
136 public void compute_and_update_measures_if_they_already_exist() {
137 markProjectAsAnalyzed(project);
138 db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0));
139 db.measures().insertLiveMeasure(dir, intMetric, m -> m.setValue(42.0));
140 db.measures().insertLiveMeasure(file1, intMetric, m -> m.setValue(42.0));
141 db.measures().insertLiveMeasure(file2, intMetric, m -> m.setValue(42.0));
143 // generates values 1, 2, 3
144 List<QGChangeEvent> result = run(file1, newQualifierBasedIntFormula());
146 assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
147 assertThatProjectChanged(result, project);
149 // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
150 assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
151 assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
152 assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
154 assertThatIntMeasureHasValue(file2, 42.0);
158 public void variation_is_refreshed_when_int_value_is_changed() {
159 markProjectAsAnalyzed(project);
161 // 42 on last analysis
162 // 42-12=30 on beginning of leak period
163 db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0).setVariation(12.0));
165 // new value is 44, so variation on leak period is 44-30=14
166 List<QGChangeEvent> result = run(file1, newIntConstantFormula(44.0));
168 LiveMeasureDto measure = assertThatIntMeasureHasValue(project, 44.0);
169 assertThat(measure.getVariation()).isEqualTo(14.0);
170 assertThatProjectChanged(result, project);
174 public void variation_is_refreshed_when_rating_value_is_changed() {
175 markProjectAsAnalyzed(project);
177 // B on last analysis
178 // D on beginning of leak period --> variation is -2
179 db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
181 // new value is C, so variation on leak period is D to C = -1
182 List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.C));
184 LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.C);
185 assertThat(measure.getVariation()).isEqualTo(-1.0);
186 assertThatProjectChanged(result, project);
190 public void variation_does_not_change_if_rating_value_does_not_change() {
191 markProjectAsAnalyzed(project);
193 // B on last analysis
194 // D on beginning of leak period --> variation is -2
195 db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
197 // new value is still B, so variation on leak period is still -2
198 List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.B));
200 LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.B);
201 assertThat(measure.getVariation()).isEqualTo(-2.0);
202 assertThatProjectChanged(result, project);
206 public void refresh_leak_measures() {
207 markProjectAsAnalyzed(project);
208 db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
209 db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setVariation((double) Rating.E.getIndex()));
210 db.measures().insertLiveMeasure(dir, intMetric, m -> m.setVariation(42.0).setValue(null));
211 db.measures().insertLiveMeasure(dir, ratingMetric, m -> m.setVariation((double) Rating.D.getIndex()));
212 db.measures().insertLiveMeasure(file1, intMetric, m -> m.setVariation(42.0).setValue(null));
213 db.measures().insertLiveMeasure(file1, ratingMetric, m -> m.setVariation((double) Rating.C.getIndex()));
215 // generates values 1, 2, 3 on leak measures
216 List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
218 assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(6);
220 // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
221 assertThatIntMeasureHasLeakValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
222 assertThatRatingMeasureHasLeakValue(file1, Rating.B);
223 assertThatIntMeasureHasLeakValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
224 assertThatRatingMeasureHasLeakValue(dir, Rating.B);
225 assertThatIntMeasureHasLeakValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
226 assertThatRatingMeasureHasLeakValue(project, Rating.B);
227 assertThatProjectChanged(result, project);
231 public void calculate_new_metrics_if_it_is_pr_or_branch() {
232 markProjectAsAnalyzed(branch, null);
233 db.measures().insertLiveMeasure(branch, intMetric, m -> m.setVariation(42.0).setValue(null));
234 db.measures().insertLiveMeasure(branchFile, intMetric, m -> m.setVariation(42.0).setValue(null));
236 // generates values 1, 2, 3 on leak measures
237 List<QGChangeEvent> result = run(branchFile, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
239 assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
241 // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
242 assertThatIntMeasureHasLeakValue(branchFile, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
243 assertThatRatingMeasureHasLeakValue(branchFile, Rating.B);
244 assertThatIntMeasureHasLeakValue(branch, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
245 assertThatRatingMeasureHasLeakValue(branch, Rating.B);
246 assertThatProjectChanged(result, branch);
250 public void do_nothing_if_project_has_not_been_analyzed() {
251 // project has no snapshots
252 List<QGChangeEvent> result = run(file1, newIncrementalFormula());
253 assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
254 assertThatProjectNotChanged(result, project);
258 public void do_nothing_if_input_components_are_empty() {
259 List<QGChangeEvent> result = run(emptyList(), newIncrementalFormula());
261 assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
262 assertThatProjectNotChanged(result, project);
266 public void refresh_multiple_projects_at_the_same_time() {
267 markProjectAsAnalyzed(project);
268 ComponentDto project2 = db.components().insertPublicProject();
269 ComponentDto fileInProject2 = db.components().insertComponent(ComponentTesting.newFileDto(project2));
270 markProjectAsAnalyzed(project2);
272 List<QGChangeEvent> result = run(asList(file1, fileInProject2), newQualifierBasedIntFormula());
274 // generated values depend on position of qualifier in Qualifiers.ORDERED_BOTTOM_UP (see formula)
275 assertThatIntMeasureHasValue(file1, 0);
276 assertThatIntMeasureHasValue(dir, 2);
277 assertThatIntMeasureHasValue(project, 4);
278 assertThatIntMeasureHasValue(fileInProject2, 0);
279 assertThatIntMeasureHasValue(project2, 4);
281 // no other measures generated
282 assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(5);
283 assertThatProjectChanged(result, project, project2);
287 public void refresh_multiple_branches_at_the_same_time() {
292 public void event_contains_no_previousStatus_if_measure_does_not_exist() {
293 markProjectAsAnalyzed(project);
295 List<QGChangeEvent> result = run(file1);
298 .extracting(QGChangeEvent::getPreviousStatus)
299 .containsExactly(Optional.empty());
303 public void event_contains_no_previousStatus_if_measure_exists_and_has_no_value() {
304 markProjectAsAnalyzed(project);
305 db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData((String) null));
307 List<QGChangeEvent> result = run(file1);
310 .extracting(QGChangeEvent::getPreviousStatus)
311 .containsExactly(Optional.empty());
315 public void event_contains_no_previousStatus_if_measure_exists_and_is_empty() {
316 markProjectAsAnalyzed(project);
317 db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(""));
319 List<QGChangeEvent> result = run(file1);
322 .extracting(QGChangeEvent::getPreviousStatus)
323 .containsExactly(Optional.empty());
327 public void event_contains_no_previousStatus_if_measure_exists_and_is_not_a_level() {
328 markProjectAsAnalyzed(project);
329 db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData("fooBar"));
331 List<QGChangeEvent> result = run(file1);
334 .extracting(QGChangeEvent::getPreviousStatus)
335 .containsExactly(Optional.empty());
339 @UseDataProvider("metricLevels")
340 public void event_contains_previousStatus_if_measure_exists(Metric.Level level) {
341 markProjectAsAnalyzed(project);
342 db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(level.name()));
343 db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
345 List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
348 .extracting(QGChangeEvent::getPreviousStatus)
349 .containsExactly(Optional.of(level));
353 public static Object[][] metricLevels() {
354 return Arrays.stream(Metric.Level.values())
355 .map(l -> new Object[] {l})
356 .toArray(Object[][]::new);
360 public void event_contains_newQualityGate_computed_by_LiveQualityGateComputer() {
361 markProjectAsAnalyzed(project);
362 db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(Metric.Level.ERROR.name()));
363 db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
364 BranchDto branch = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), project.projectUuid(), "master")
365 .orElseThrow(() -> new IllegalStateException("Can't find master branch"));
367 List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
370 .extracting(QGChangeEvent::getQualityGateSupplier)
371 .extracting(Supplier::get)
372 .containsExactly(Optional.of(newQualityGate));
373 verify(qGateComputer).loadQualityGate(any(DbSession.class), argThat(p -> p.getUuid().equals(projectDto.getUuid())), eq(branch));
374 verify(qGateComputer).getMetricsRelatedTo(qualityGate);
375 verify(qGateComputer).refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class));
379 public void exception_describes_context_when_a_formula_fails() {
380 markProjectAsAnalyzed(project);
381 Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
383 expectedException.expect(IllegalStateException.class);
384 expectedException.expectMessage("Fail to compute " + metric.getKey() + " on " + project.getDbKey());
386 run(project, new IssueMetricFormula(metric, false, (context, issueCounter) -> {
387 throw new NullPointerException("BOOM");
391 private List<QGChangeEvent> run(ComponentDto component, IssueMetricFormula... formulas) {
392 return run(singleton(component), formulas);
395 private List<QGChangeEvent> run(Collection<ComponentDto> components, IssueMetricFormula... formulas) {
396 IssueMetricFormulaFactory formulaFactory = new TestIssueMetricFormulaFactory(asList(formulas));
398 when(qGateComputer.loadQualityGate(any(DbSession.class), any(ProjectDto.class), any(BranchDto.class)))
399 .thenReturn(qualityGate);
400 when(qGateComputer.getMetricsRelatedTo(qualityGate)).thenReturn(singleton(CoreMetrics.ALERT_STATUS_KEY));
401 when(qGateComputer.refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class)))
402 .thenReturn(newQualityGate);
403 MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, CorePropertyDefinitions.all()));
404 ProjectConfigurationLoader configurationLoader = new TestProjectConfigurationLoader(settings.asConfig());
406 LiveMeasureComputerImpl underTest = new LiveMeasureComputerImpl(db.getDbClient(), formulaFactory, qGateComputer, configurationLoader, projectIndexer);
408 return underTest.refresh(db.getSession(), components);
411 private void markProjectAsAnalyzed(ComponentDto p) {
412 markProjectAsAnalyzed(p, 1_490_000_000L);
415 private void markProjectAsAnalyzed(ComponentDto p, @Nullable Long periodDate) {
416 assertThat(p.qualifier()).isEqualTo(Qualifiers.PROJECT);
417 db.components().insertSnapshot(p, s -> s.setPeriodDate(periodDate));
420 private LiveMeasureDto assertThatIntMeasureHasValue(ComponentDto component, double expectedValue) {
421 LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
422 assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
423 assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
424 assertThat(measure.getMetricUuid()).isEqualTo(intMetric.getUuid());
425 assertThat(measure.getValue()).isEqualTo(expectedValue);
429 private LiveMeasureDto assertThatRatingMeasureHasValue(ComponentDto component, Rating expectedRating) {
430 LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
431 assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
432 assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
433 assertThat(measure.getMetricUuid()).isEqualTo(ratingMetric.getUuid());
434 assertThat(measure.getValue()).isEqualTo(expectedRating.getIndex());
435 assertThat(measure.getDataAsString()).isEqualTo(expectedRating.name());
439 private void assertThatIntMeasureHasLeakValue(ComponentDto component, double expectedValue) {
440 LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
441 assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
442 assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
443 assertThat(measure.getMetricUuid()).isEqualTo(intMetric.getUuid());
444 assertThat(measure.getValue()).isNull();
445 assertThat(measure.getVariation()).isEqualTo(expectedValue);
448 private void assertThatRatingMeasureHasLeakValue(ComponentDto component, Rating expectedValue) {
449 LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
450 assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
451 assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
452 assertThat(measure.getMetricUuid()).isEqualTo(ratingMetric.getUuid());
453 assertThat(measure.getVariation()).isEqualTo(expectedValue.getIndex());
456 private IssueMetricFormula newIncrementalFormula() {
457 Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
458 AtomicInteger counter = new AtomicInteger();
459 return new IssueMetricFormula(metric, false, (ctx, issues) -> {
460 ctx.setValue(counter.incrementAndGet());
464 private IssueMetricFormula newIntConstantFormula(double constant) {
465 Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
466 return new IssueMetricFormula(metric, false, (ctx, issues) -> {
467 ctx.setValue(constant);
471 private IssueMetricFormula newRatingConstantFormula(Rating constant) {
472 Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
473 return new IssueMetricFormula(metric, false, (ctx, issues) -> {
474 ctx.setValue(constant);
478 private IssueMetricFormula newRatingLeakFormula(Rating rating) {
479 Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
480 return new IssueMetricFormula(metric, true, (ctx, issues) -> {
481 ctx.setLeakValue(rating);
485 private IssueMetricFormula newQualifierBasedIntFormula() {
486 Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
487 return new IssueMetricFormula(metric, false, (ctx, issues) -> {
488 ctx.setValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier()));
492 private IssueMetricFormula newQualifierBasedIntLeakFormula() {
493 Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
494 return new IssueMetricFormula(metric, true, (ctx, issues) -> {
495 ctx.setLeakValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier()));
499 private void assertThatProjectChanged(List<QGChangeEvent> events, ComponentDto... projects) {
500 for (ComponentDto p : projects) {
501 assertThat(projectIndexer.hasBeenCalled(p.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isTrue();
504 assertThat(events).extracting(e -> e.getBranch().getUuid())
505 .containsExactlyInAnyOrder(Arrays.stream(projects).map(ComponentDto::uuid).toArray(String[]::new));
508 private void assertThatProjectNotChanged(List<QGChangeEvent> events, ComponentDto project) {
509 assertThat(projectIndexer.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isFalse();
510 assertThat(events).isEmpty();