]> source.dussan.org Git - sonarqube.git/blob
6911e926691387e6bbdebb32cbe5126b3c2904e9
[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.server.measure.live;
21
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.project.ProjectDto;
53 import org.sonar.server.es.ProjectIndexer;
54 import org.sonar.server.es.TestProjectIndexers;
55 import org.sonar.server.measure.Rating;
56 import org.sonar.server.qualitygate.EvaluatedQualityGate;
57 import org.sonar.server.qualitygate.QualityGate;
58 import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
59 import org.sonar.server.setting.ProjectConfigurationLoader;
60 import org.sonar.server.setting.TestProjectConfigurationLoader;
61
62 import static java.util.Arrays.asList;
63 import static java.util.Collections.emptyList;
64 import static java.util.Collections.singleton;
65 import static org.assertj.core.api.Assertions.assertThat;
66 import static org.mockito.ArgumentMatchers.any;
67 import static org.mockito.ArgumentMatchers.argThat;
68 import static org.mockito.ArgumentMatchers.eq;
69 import static org.mockito.ArgumentMatchers.same;
70 import static org.mockito.Mockito.mock;
71 import static org.mockito.Mockito.verify;
72 import static org.mockito.Mockito.when;
73 import static org.sonar.api.resources.Qualifiers.ORDERED_BOTTOM_UP;
74
75 @RunWith(DataProviderRunner.class)
76 public class LiveMeasureComputerImplTest {
77
78   @Rule
79   public DbTester db = DbTester.create();
80   @Rule
81   public ExpectedException expectedException = ExpectedException.none();
82
83   private final TestProjectIndexers projectIndexer = new TestProjectIndexers();
84   private MetricDto intMetric;
85   private MetricDto ratingMetric;
86   private MetricDto alertStatusMetric;
87   private ComponentDto project;
88   private ProjectDto projectDto;
89   private ComponentDto dir;
90   private ComponentDto file1;
91   private ComponentDto file2;
92   private ComponentDto branch;
93   private ComponentDto branchFile;
94   private final LiveQualityGateComputer qGateComputer = mock(LiveQualityGateComputer.class);
95   private final QualityGate qualityGate = mock(QualityGate.class);
96   private final EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
97
98   @Before
99   public void setUp() {
100     intMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.INT.name()));
101     ratingMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.RATING.name()));
102     alertStatusMetric = db.measures().insertMetric(m -> m.setKey(CoreMetrics.ALERT_STATUS_KEY));
103     project = db.components().insertPublicProject();
104     projectDto = db.components().getProjectDto(project);
105     dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java"));
106     file1 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
107     file2 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
108     branch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
109     branchFile = db.components().insertComponent(ComponentTesting.newFileDto(branch));
110   }
111
112   @Test
113   public void compute_and_insert_measures_if_they_do_not_exist_yet() {
114     markProjectAsAnalyzed(project);
115
116     List<QGChangeEvent> result = run(asList(file1, file2), newQualifierBasedIntFormula(), newRatingConstantFormula(Rating.C));
117
118     // 2 measures per component have been created
119     // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
120     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(8);
121     assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
122     assertThatRatingMeasureHasValue(file1, Rating.C);
123     assertThatIntMeasureHasValue(file2, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
124     assertThatRatingMeasureHasValue(file2, Rating.C);
125     assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
126     assertThatRatingMeasureHasValue(dir, Rating.C);
127     assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
128     assertThatRatingMeasureHasValue(project, Rating.C);
129     assertThatProjectChanged(result, project);
130   }
131
132   @Test
133   public void compute_and_update_measures_if_they_already_exist() {
134     markProjectAsAnalyzed(project);
135     db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0));
136     db.measures().insertLiveMeasure(dir, intMetric, m -> m.setValue(42.0));
137     db.measures().insertLiveMeasure(file1, intMetric, m -> m.setValue(42.0));
138     db.measures().insertLiveMeasure(file2, intMetric, m -> m.setValue(42.0));
139
140     // generates values 1, 2, 3
141     List<QGChangeEvent> result = run(file1, newQualifierBasedIntFormula());
142
143     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
144     assertThatProjectChanged(result, project);
145
146     // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
147     assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
148     assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
149     assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
150     // untouched
151     assertThatIntMeasureHasValue(file2, 42.0);
152   }
153
154   @Test
155   public void variation_is_refreshed_when_int_value_is_changed() {
156     markProjectAsAnalyzed(project);
157     // value is:
158     // 42 on last analysis
159     // 42-12=30 on beginning of leak period
160     db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0).setVariation(12.0));
161
162     // new value is 44, so variation on leak period is 44-30=14
163     List<QGChangeEvent> result = run(file1, newIntConstantFormula(44.0));
164
165     LiveMeasureDto measure = assertThatIntMeasureHasValue(project, 44.0);
166     assertThat(measure.getVariation()).isEqualTo(14.0);
167     assertThatProjectChanged(result, project);
168   }
169
170   @Test
171   public void variation_is_refreshed_when_rating_value_is_changed() {
172     markProjectAsAnalyzed(project);
173     // value is:
174     // B on last analysis
175     // D on beginning of leak period --> variation is -2
176     db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
177
178     // new value is C, so variation on leak period is D to C = -1
179     List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.C));
180
181     LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.C);
182     assertThat(measure.getVariation()).isEqualTo(-1.0);
183     assertThatProjectChanged(result, project);
184   }
185
186   @Test
187   public void variation_does_not_change_if_rating_value_does_not_change() {
188     markProjectAsAnalyzed(project);
189     // value is:
190     // B on last analysis
191     // D on beginning of leak period --> variation is -2
192     db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
193
194     // new value is still B, so variation on leak period is still -2
195     List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.B));
196
197     LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.B);
198     assertThat(measure.getVariation()).isEqualTo(-2.0);
199     assertThatProjectChanged(result, project);
200   }
201
202   @Test
203   public void refresh_leak_measures() {
204     markProjectAsAnalyzed(project);
205     db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
206     db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setVariation((double) Rating.E.getIndex()));
207     db.measures().insertLiveMeasure(dir, intMetric, m -> m.setVariation(42.0).setValue(null));
208     db.measures().insertLiveMeasure(dir, ratingMetric, m -> m.setVariation((double) Rating.D.getIndex()));
209     db.measures().insertLiveMeasure(file1, intMetric, m -> m.setVariation(42.0).setValue(null));
210     db.measures().insertLiveMeasure(file1, ratingMetric, m -> m.setVariation((double) Rating.C.getIndex()));
211
212     // generates values 1, 2, 3 on leak measures
213     List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
214
215     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(6);
216
217     // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
218     assertThatIntMeasureHasLeakValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
219     assertThatRatingMeasureHasLeakValue(file1, Rating.B);
220     assertThatIntMeasureHasLeakValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
221     assertThatRatingMeasureHasLeakValue(dir, Rating.B);
222     assertThatIntMeasureHasLeakValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
223     assertThatRatingMeasureHasLeakValue(project, Rating.B);
224     assertThatProjectChanged(result, project);
225   }
226
227   @Test
228   public void calculate_new_metrics_if_it_is_pr_or_branch() {
229     markProjectAsAnalyzed(branch, null);
230     db.measures().insertLiveMeasure(branch, intMetric, m -> m.setVariation(42.0).setValue(null));
231     db.measures().insertLiveMeasure(branchFile, intMetric, m -> m.setVariation(42.0).setValue(null));
232
233     // generates values 1, 2, 3 on leak measures
234     List<QGChangeEvent> result = run(branchFile, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
235
236     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
237
238     // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
239     assertThatIntMeasureHasLeakValue(branchFile, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
240     assertThatRatingMeasureHasLeakValue(branchFile, Rating.B);
241     assertThatIntMeasureHasLeakValue(branch, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
242     assertThatRatingMeasureHasLeakValue(branch, Rating.B);
243     assertThatProjectChanged(result, branch);
244   }
245
246   @Test
247   public void do_nothing_if_project_has_not_been_analyzed() {
248     // project has no snapshots
249     List<QGChangeEvent> result = run(file1, newIncrementalFormula());
250     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
251     assertThatProjectNotChanged(result, project);
252   }
253
254   @Test
255   public void do_nothing_if_input_components_are_empty() {
256     List<QGChangeEvent> result = run(emptyList(), newIncrementalFormula());
257
258     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
259     assertThatProjectNotChanged(result, project);
260   }
261
262   @Test
263   public void refresh_multiple_projects_at_the_same_time() {
264     markProjectAsAnalyzed(project);
265     ComponentDto project2 = db.components().insertPublicProject();
266     ComponentDto fileInProject2 = db.components().insertComponent(ComponentTesting.newFileDto(project2));
267     markProjectAsAnalyzed(project2);
268
269     List<QGChangeEvent> result = run(asList(file1, fileInProject2), newQualifierBasedIntFormula());
270
271     // generated values depend on position of qualifier in Qualifiers.ORDERED_BOTTOM_UP (see formula)
272     assertThatIntMeasureHasValue(file1, 0);
273     assertThatIntMeasureHasValue(dir, 2);
274     assertThatIntMeasureHasValue(project, 4);
275     assertThatIntMeasureHasValue(fileInProject2, 0);
276     assertThatIntMeasureHasValue(project2, 4);
277
278     // no other measures generated
279     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(5);
280     assertThatProjectChanged(result, project, project2);
281   }
282
283   @Test
284   public void refresh_multiple_branches_at_the_same_time() {
285     // FIXME
286   }
287
288   @Test
289   public void event_contains_no_previousStatus_if_measure_does_not_exist() {
290     markProjectAsAnalyzed(project);
291
292     List<QGChangeEvent> result = run(file1);
293
294     assertThat(result)
295       .extracting(QGChangeEvent::getPreviousStatus)
296       .containsExactly(Optional.empty());
297   }
298
299   @Test
300   public void event_contains_no_previousStatus_if_measure_exists_and_has_no_value() {
301     markProjectAsAnalyzed(project);
302     db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData((String) null));
303
304     List<QGChangeEvent> result = run(file1);
305
306     assertThat(result)
307       .extracting(QGChangeEvent::getPreviousStatus)
308       .containsExactly(Optional.empty());
309   }
310
311   @Test
312   public void event_contains_no_previousStatus_if_measure_exists_and_is_empty() {
313     markProjectAsAnalyzed(project);
314     db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(""));
315
316     List<QGChangeEvent> result = run(file1);
317
318     assertThat(result)
319       .extracting(QGChangeEvent::getPreviousStatus)
320       .containsExactly(Optional.empty());
321   }
322
323   @Test
324   public void event_contains_no_previousStatus_if_measure_exists_and_is_not_a_level() {
325     markProjectAsAnalyzed(project);
326     db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData("fooBar"));
327
328     List<QGChangeEvent> result = run(file1);
329
330     assertThat(result)
331       .extracting(QGChangeEvent::getPreviousStatus)
332       .containsExactly(Optional.empty());
333   }
334
335   @Test
336   @UseDataProvider("metricLevels")
337   public void event_contains_previousStatus_if_measure_exists(Metric.Level level) {
338     markProjectAsAnalyzed(project);
339     db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(level.name()));
340     db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
341
342     List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
343
344     assertThat(result)
345       .extracting(QGChangeEvent::getPreviousStatus)
346       .containsExactly(Optional.of(level));
347   }
348
349   @DataProvider
350   public static Object[][] metricLevels() {
351     return Arrays.stream(Metric.Level.values())
352       .map(l -> new Object[] {l})
353       .toArray(Object[][]::new);
354   }
355
356   @Test
357   public void event_contains_newQualityGate_computed_by_LiveQualityGateComputer() {
358     markProjectAsAnalyzed(project);
359     db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(Metric.Level.ERROR.name()));
360     db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
361     BranchDto branch = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), project.projectUuid(), "master")
362       .orElseThrow(() -> new IllegalStateException("Can't find master branch"));
363
364     List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
365
366     assertThat(result)
367       .extracting(QGChangeEvent::getQualityGateSupplier)
368       .extracting(Supplier::get)
369       .containsExactly(Optional.of(newQualityGate));
370     verify(qGateComputer).loadQualityGate(any(DbSession.class), argThat(p -> p.getUuid().equals(projectDto.getUuid())), eq(branch));
371     verify(qGateComputer).getMetricsRelatedTo(qualityGate);
372     verify(qGateComputer).refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class));
373   }
374
375   @Test
376   public void exception_describes_context_when_a_formula_fails() {
377     markProjectAsAnalyzed(project);
378     Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
379
380     expectedException.expect(IllegalStateException.class);
381     expectedException.expectMessage("Fail to compute " + metric.getKey() + " on " + project.getDbKey());
382
383     run(project, new IssueMetricFormula(metric, false, (context, issueCounter) -> {
384       throw new NullPointerException("BOOM");
385     }));
386   }
387
388   private List<QGChangeEvent> run(ComponentDto component, IssueMetricFormula... formulas) {
389     return run(singleton(component), formulas);
390   }
391
392   private List<QGChangeEvent> run(Collection<ComponentDto> components, IssueMetricFormula... formulas) {
393     IssueMetricFormulaFactory formulaFactory = new TestIssueMetricFormulaFactory(asList(formulas));
394
395     when(qGateComputer.loadQualityGate(any(DbSession.class), any(ProjectDto.class), any(BranchDto.class)))
396       .thenReturn(qualityGate);
397     when(qGateComputer.getMetricsRelatedTo(qualityGate)).thenReturn(singleton(CoreMetrics.ALERT_STATUS_KEY));
398     when(qGateComputer.refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class)))
399       .thenReturn(newQualityGate);
400     MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, CorePropertyDefinitions.all()));
401     ProjectConfigurationLoader configurationLoader = new TestProjectConfigurationLoader(settings.asConfig());
402
403     LiveMeasureComputerImpl underTest = new LiveMeasureComputerImpl(db.getDbClient(), formulaFactory, qGateComputer, configurationLoader, projectIndexer);
404
405     return underTest.refresh(db.getSession(), components);
406   }
407
408   private void markProjectAsAnalyzed(ComponentDto p) {
409     markProjectAsAnalyzed(p, 1_490_000_000L);
410   }
411
412   private void markProjectAsAnalyzed(ComponentDto p, @Nullable Long periodDate) {
413     assertThat(p.qualifier()).isEqualTo(Qualifiers.PROJECT);
414     db.components().insertSnapshot(p, s -> s.setPeriodDate(periodDate));
415   }
416
417   private LiveMeasureDto assertThatIntMeasureHasValue(ComponentDto component, double expectedValue) {
418     LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
419     assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
420     assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
421     assertThat(measure.getMetricUuid()).isEqualTo(intMetric.getUuid());
422     assertThat(measure.getValue()).isEqualTo(expectedValue);
423     return measure;
424   }
425
426   private LiveMeasureDto assertThatRatingMeasureHasValue(ComponentDto component, Rating expectedRating) {
427     LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
428     assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
429     assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
430     assertThat(measure.getMetricUuid()).isEqualTo(ratingMetric.getUuid());
431     assertThat(measure.getValue()).isEqualTo(expectedRating.getIndex());
432     assertThat(measure.getDataAsString()).isEqualTo(expectedRating.name());
433     return measure;
434   }
435
436   private void assertThatIntMeasureHasLeakValue(ComponentDto component, double expectedValue) {
437     LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
438     assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
439     assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
440     assertThat(measure.getMetricUuid()).isEqualTo(intMetric.getUuid());
441     assertThat(measure.getValue()).isNull();
442     assertThat(measure.getVariation()).isEqualTo(expectedValue);
443   }
444
445   private void assertThatRatingMeasureHasLeakValue(ComponentDto component, Rating expectedValue) {
446     LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
447     assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
448     assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
449     assertThat(measure.getMetricUuid()).isEqualTo(ratingMetric.getUuid());
450     assertThat(measure.getVariation()).isEqualTo(expectedValue.getIndex());
451   }
452
453   private IssueMetricFormula newIncrementalFormula() {
454     Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
455     AtomicInteger counter = new AtomicInteger();
456     return new IssueMetricFormula(metric, false, (ctx, issues) -> {
457       ctx.setValue(counter.incrementAndGet());
458     });
459   }
460
461   private IssueMetricFormula newIntConstantFormula(double constant) {
462     Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
463     return new IssueMetricFormula(metric, false, (ctx, issues) -> {
464       ctx.setValue(constant);
465     });
466   }
467
468   private IssueMetricFormula newRatingConstantFormula(Rating constant) {
469     Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
470     return new IssueMetricFormula(metric, false, (ctx, issues) -> {
471       ctx.setValue(constant);
472     });
473   }
474
475   private IssueMetricFormula newRatingLeakFormula(Rating rating) {
476     Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
477     return new IssueMetricFormula(metric, true, (ctx, issues) -> {
478       ctx.setLeakValue(rating);
479     });
480   }
481
482   private IssueMetricFormula newQualifierBasedIntFormula() {
483     Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
484     return new IssueMetricFormula(metric, false, (ctx, issues) -> {
485       ctx.setValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier()));
486     });
487   }
488
489   private IssueMetricFormula newQualifierBasedIntLeakFormula() {
490     Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
491     return new IssueMetricFormula(metric, true, (ctx, issues) -> {
492       ctx.setLeakValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier()));
493     });
494   }
495
496   private void assertThatProjectChanged(List<QGChangeEvent> events, ComponentDto... projects) {
497     for (ComponentDto p : projects) {
498       assertThat(projectIndexer.hasBeenCalled(p.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isTrue();
499     }
500
501     assertThat(events).extracting(e -> e.getBranch().getUuid())
502       .containsExactlyInAnyOrder(Arrays.stream(projects).map(ComponentDto::uuid).toArray(String[]::new));
503   }
504
505   private void assertThatProjectNotChanged(List<QGChangeEvent> events, ComponentDto project) {
506     assertThat(projectIndexer.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isFalse();
507     assertThat(events).isEmpty();
508   }
509 }