]> source.dussan.org Git - sonarqube.git/blob
e626c7205d1f7fdd7c65230bafc07d3abf37f1be
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2020 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.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;
62
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;
75
76 @RunWith(DataProviderRunner.class)
77 public class LiveMeasureComputerImplTest {
78
79   @Rule
80   public DbTester db = DbTester.create();
81   @Rule
82   public ExpectedException expectedException = ExpectedException.none();
83
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);
99
100   @Before
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));
113   }
114
115   @Test
116   public void compute_and_insert_measures_if_they_do_not_exist_yet() {
117     markProjectAsAnalyzed(project);
118
119     List<QGChangeEvent> result = run(asList(file1, file2), newQualifierBasedIntFormula(), newRatingConstantFormula(Rating.C));
120
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);
133   }
134
135   @Test
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));
142
143     // generates values 1, 2, 3
144     List<QGChangeEvent> result = run(file1, newQualifierBasedIntFormula());
145
146     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
147     assertThatProjectChanged(result, project);
148
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));
153     // untouched
154     assertThatIntMeasureHasValue(file2, 42.0);
155   }
156
157   @Test
158   public void variation_is_refreshed_when_int_value_is_changed() {
159     markProjectAsAnalyzed(project);
160     // value is:
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));
164
165     // new value is 44, so variation on leak period is 44-30=14
166     List<QGChangeEvent> result = run(file1, newIntConstantFormula(44.0));
167
168     LiveMeasureDto measure = assertThatIntMeasureHasValue(project, 44.0);
169     assertThat(measure.getVariation()).isEqualTo(14.0);
170     assertThatProjectChanged(result, project);
171   }
172
173   @Test
174   public void variation_is_refreshed_when_rating_value_is_changed() {
175     markProjectAsAnalyzed(project);
176     // value is:
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));
180
181     // new value is C, so variation on leak period is D to C = -1
182     List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.C));
183
184     LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.C);
185     assertThat(measure.getVariation()).isEqualTo(-1.0);
186     assertThatProjectChanged(result, project);
187   }
188
189   @Test
190   public void variation_does_not_change_if_rating_value_does_not_change() {
191     markProjectAsAnalyzed(project);
192     // value is:
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));
196
197     // new value is still B, so variation on leak period is still -2
198     List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.B));
199
200     LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.B);
201     assertThat(measure.getVariation()).isEqualTo(-2.0);
202     assertThatProjectChanged(result, project);
203   }
204
205   @Test
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()));
214
215     // generates values 1, 2, 3 on leak measures
216     List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
217
218     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(6);
219
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);
228   }
229
230   @Test
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));
235
236     // generates values 1, 2, 3 on leak measures
237     List<QGChangeEvent> result = run(branchFile, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
238
239     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
240
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);
247   }
248
249   @Test
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);
255   }
256
257   @Test
258   public void do_nothing_if_input_components_are_empty() {
259     List<QGChangeEvent> result = run(emptyList(), newIncrementalFormula());
260
261     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
262     assertThatProjectNotChanged(result, project);
263   }
264
265   @Test
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);
271
272     List<QGChangeEvent> result = run(asList(file1, fileInProject2), newQualifierBasedIntFormula());
273
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);
280
281     // no other measures generated
282     assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(5);
283     assertThatProjectChanged(result, project, project2);
284   }
285
286   @Test
287   public void refresh_multiple_branches_at_the_same_time() {
288     // FIXME
289   }
290
291   @Test
292   public void event_contains_no_previousStatus_if_measure_does_not_exist() {
293     markProjectAsAnalyzed(project);
294
295     List<QGChangeEvent> result = run(file1);
296
297     assertThat(result)
298       .extracting(QGChangeEvent::getPreviousStatus)
299       .containsExactly(Optional.empty());
300   }
301
302   @Test
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));
306
307     List<QGChangeEvent> result = run(file1);
308
309     assertThat(result)
310       .extracting(QGChangeEvent::getPreviousStatus)
311       .containsExactly(Optional.empty());
312   }
313
314   @Test
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(""));
318
319     List<QGChangeEvent> result = run(file1);
320
321     assertThat(result)
322       .extracting(QGChangeEvent::getPreviousStatus)
323       .containsExactly(Optional.empty());
324   }
325
326   @Test
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"));
330
331     List<QGChangeEvent> result = run(file1);
332
333     assertThat(result)
334       .extracting(QGChangeEvent::getPreviousStatus)
335       .containsExactly(Optional.empty());
336   }
337
338   @Test
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));
344
345     List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
346
347     assertThat(result)
348       .extracting(QGChangeEvent::getPreviousStatus)
349       .containsExactly(Optional.of(level));
350   }
351
352   @DataProvider
353   public static Object[][] metricLevels() {
354     return Arrays.stream(Metric.Level.values())
355       .map(l -> new Object[] {l})
356       .toArray(Object[][]::new);
357   }
358
359   @Test
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"));
366
367     List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
368
369     assertThat(result)
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));
376   }
377
378   @Test
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();
382
383     expectedException.expect(IllegalStateException.class);
384     expectedException.expectMessage("Fail to compute " + metric.getKey() + " on " + project.getDbKey());
385
386     run(project, new IssueMetricFormula(metric, false, (context, issueCounter) -> {
387       throw new NullPointerException("BOOM");
388     }));
389   }
390
391   private List<QGChangeEvent> run(ComponentDto component, IssueMetricFormula... formulas) {
392     return run(singleton(component), formulas);
393   }
394
395   private List<QGChangeEvent> run(Collection<ComponentDto> components, IssueMetricFormula... formulas) {
396     IssueMetricFormulaFactory formulaFactory = new TestIssueMetricFormulaFactory(asList(formulas));
397
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());
405
406     LiveMeasureComputerImpl underTest = new LiveMeasureComputerImpl(db.getDbClient(), formulaFactory, qGateComputer, configurationLoader, projectIndexer);
407
408     return underTest.refresh(db.getSession(), components);
409   }
410
411   private void markProjectAsAnalyzed(ComponentDto p) {
412     markProjectAsAnalyzed(p, 1_490_000_000L);
413   }
414
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));
418   }
419
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);
426     return measure;
427   }
428
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());
436     return measure;
437   }
438
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);
446   }
447
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());
454   }
455
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());
461     });
462   }
463
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);
468     });
469   }
470
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);
475     });
476   }
477
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);
482     });
483   }
484
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()));
489     });
490   }
491
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()));
496     });
497   }
498
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();
502     }
503
504     assertThat(events).extracting(e -> e.getBranch().getUuid())
505       .containsExactlyInAnyOrder(Arrays.stream(projects).map(ComponentDto::uuid).toArray(String[]::new));
506   }
507
508   private void assertThatProjectNotChanged(List<QGChangeEvent> events, ComponentDto project) {
509     assertThat(projectIndexer.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isFalse();
510     assertThat(events).isEmpty();
511   }
512 }