You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ReportFormulaExecutorComponentVisitorTest.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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.formula;
  21. import com.google.common.collect.ImmutableList;
  22. import java.util.Optional;
  23. import org.junit.Rule;
  24. import org.junit.Test;
  25. import org.sonar.api.measures.CoreMetrics;
  26. import org.sonar.ce.task.projectanalysis.component.Component;
  27. import org.sonar.ce.task.projectanalysis.component.PathAwareCrawler;
  28. import org.sonar.ce.task.projectanalysis.component.ReportComponent;
  29. import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
  30. import org.sonar.ce.task.projectanalysis.formula.counter.IntValue;
  31. import org.sonar.ce.task.projectanalysis.measure.Measure;
  32. import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
  33. import org.sonar.ce.task.projectanalysis.metric.Metric;
  34. import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
  35. import static org.assertj.core.api.Assertions.assertThat;
  36. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  37. import static org.sonar.api.measures.CoreMetrics.LINES_KEY;
  38. import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
  39. import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY;
  40. import static org.sonar.api.measures.CoreMetrics.NEW_LINES_TO_COVER_KEY;
  41. import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
  42. import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
  43. import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
  44. import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
  45. import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
  46. import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.toEntries;
  47. public class ReportFormulaExecutorComponentVisitorTest {
  48. private static final int ROOT_REF = 1;
  49. private static final int DIRECTORY_1_REF = 111;
  50. private static final int FILE_1_REF = 1111;
  51. private static final int FILE_2_REF = 1112;
  52. private static final int DIRECTORY_2_REF = 121;
  53. private static final int FILE_3_REF = 1211;
  54. private static final ReportComponent BALANCED_COMPONENT_TREE = ReportComponent.builder(PROJECT, ROOT_REF)
  55. .addChildren(
  56. ReportComponent.builder(DIRECTORY, DIRECTORY_1_REF)
  57. .addChildren(
  58. builder(Component.Type.FILE, FILE_1_REF).build(),
  59. builder(Component.Type.FILE, FILE_2_REF).build())
  60. .build(),
  61. ReportComponent.builder(DIRECTORY, DIRECTORY_2_REF)
  62. .addChildren(
  63. builder(Component.Type.FILE, FILE_3_REF).build())
  64. .build())
  65. .build();
  66. @Rule
  67. public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
  68. @Rule
  69. public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
  70. .add(CoreMetrics.LINES)
  71. .add(CoreMetrics.NCLOC)
  72. .add(CoreMetrics.NEW_LINES_TO_COVER)
  73. .add(CoreMetrics.NEW_COVERAGE);
  74. @Rule
  75. public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
  76. @Test
  77. public void verify_aggregation_on_value() {
  78. treeRootHolder.setRoot(BALANCED_COMPONENT_TREE);
  79. measureRepository.addRawMeasure(FILE_1_REF, LINES_KEY, newMeasureBuilder().create(10));
  80. measureRepository.addRawMeasure(FILE_2_REF, LINES_KEY, newMeasureBuilder().create(8));
  81. measureRepository.addRawMeasure(FILE_3_REF, LINES_KEY, newMeasureBuilder().create(2));
  82. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
  83. .visit(BALANCED_COMPONENT_TREE);
  84. assertAddedRawMeasure(ROOT_REF, 20);
  85. assertAddedRawMeasure(111, 18);
  86. assertAddedRawMeasure(FILE_1_REF, 10);
  87. assertAddedRawMeasure(FILE_2_REF, 8);
  88. assertAddedRawMeasure(DIRECTORY_2_REF, 2);
  89. assertAddedRawMeasure(FILE_3_REF, 2);
  90. }
  91. @Test
  92. public void verify_multi_metric_formula_support_and_aggregation() {
  93. treeRootHolder.setRoot(BALANCED_COMPONENT_TREE);
  94. measureRepository.addRawMeasure(FILE_1_REF, LINES_KEY, newMeasureBuilder().create(10));
  95. measureRepository.addRawMeasure(FILE_2_REF, LINES_KEY, newMeasureBuilder().create(8));
  96. measureRepository.addRawMeasure(FILE_3_REF, LINES_KEY, newMeasureBuilder().create(2));
  97. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeMultiMetricFormula()))
  98. .visit(BALANCED_COMPONENT_TREE);
  99. assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).containsOnly(
  100. entryOf(NEW_LINES_TO_COVER_KEY, newMeasureBuilder().create(30)),
  101. entryOf(NEW_COVERAGE_KEY, newMeasureBuilder().create(120)));
  102. assertThat(toEntries(measureRepository.getAddedRawMeasures(111))).containsOnly(
  103. entryOf(NEW_LINES_TO_COVER_KEY, newMeasureBuilder().create(28)),
  104. entryOf(NEW_COVERAGE_KEY, newMeasureBuilder().create(118)));
  105. assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_1_REF))).containsOnly(
  106. entryOf(NEW_LINES_TO_COVER_KEY, newMeasureBuilder().create(20)),
  107. entryOf(NEW_COVERAGE_KEY, newMeasureBuilder().create(110)));
  108. assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_2_REF))).containsOnly(
  109. entryOf(NEW_LINES_TO_COVER_KEY, newMeasureBuilder().create(18)),
  110. entryOf(NEW_COVERAGE_KEY, newMeasureBuilder().create(108)));
  111. assertThat(toEntries(measureRepository.getAddedRawMeasures(DIRECTORY_2_REF))).containsOnly(
  112. entryOf(NEW_LINES_TO_COVER_KEY, newMeasureBuilder().create(12)),
  113. entryOf(NEW_COVERAGE_KEY, newMeasureBuilder().create(102)));
  114. assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_3_REF))).containsOnly(
  115. entryOf(NEW_LINES_TO_COVER_KEY, newMeasureBuilder().create(12)),
  116. entryOf(NEW_COVERAGE_KEY, newMeasureBuilder().create(102)));
  117. }
  118. @Test
  119. public void verify_aggregation_on_variation() {
  120. treeRootHolder.setRoot(BALANCED_COMPONENT_TREE);
  121. measureRepository.addRawMeasure(FILE_1_REF, NEW_LINES_TO_COVER_KEY, createMeasureWithVariation(10));
  122. measureRepository.addRawMeasure(FILE_2_REF, NEW_LINES_TO_COVER_KEY, createMeasureWithVariation(8));
  123. measureRepository.addRawMeasure(FILE_3_REF, NEW_LINES_TO_COVER_KEY, createMeasureWithVariation(2));
  124. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeVariationFormula()))
  125. .visit(BALANCED_COMPONENT_TREE);
  126. assertAddedRawMeasureVariation(ROOT_REF, 20);
  127. assertAddedRawMeasureVariation(DIRECTORY_1_REF, 18);
  128. assertAddedRawMeasureVariation(FILE_1_REF, 10);
  129. assertAddedRawMeasureVariation(FILE_2_REF, 8);
  130. assertAddedRawMeasureVariation(DIRECTORY_2_REF, 2);
  131. assertAddedRawMeasureVariation(FILE_3_REF, 2);
  132. }
  133. @Test
  134. public void measures_are_0_when_there_is_no_input_measure() {
  135. ReportComponent project = ReportComponent.builder(PROJECT, ROOT_REF)
  136. .addChildren(
  137. ReportComponent.builder(DIRECTORY, DIRECTORY_1_REF)
  138. .addChildren(
  139. builder(Component.Type.FILE, FILE_1_REF).build())
  140. .build())
  141. .build();
  142. treeRootHolder.setRoot(project);
  143. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
  144. .visit(project);
  145. assertAddedRawMeasure(ROOT_REF, 0);
  146. assertAddedRawMeasure(DIRECTORY_1_REF, 0);
  147. assertAddedRawMeasure(FILE_1_REF, 0);
  148. }
  149. @Test
  150. public void add_measure_even_when_leaf_is_not_FILE() {
  151. ReportComponent project = ReportComponent.builder(PROJECT, ROOT_REF)
  152. .addChildren(
  153. ReportComponent.builder(DIRECTORY, 111).build())
  154. .build();
  155. treeRootHolder.setRoot(project);
  156. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
  157. .visit(project);
  158. assertAddedRawMeasure(DIRECTORY_1_REF, 0);
  159. }
  160. @Test
  161. public void compute_measure_on_project_without_children() {
  162. ReportComponent root = builder(PROJECT, ROOT_REF).build();
  163. treeRootHolder.setRoot(root);
  164. measureRepository.addRawMeasure(ROOT_REF, LINES_KEY, newMeasureBuilder().create(10));
  165. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
  166. .visit(root);
  167. assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).containsOnly(entryOf(NCLOC_KEY, newMeasureBuilder().create(10)));
  168. }
  169. @Test
  170. public void ignore_measure_defined_on_project_when_measure_is_defined_on_leaf() {
  171. ReportComponent root = builder(PROJECT, ROOT_REF)
  172. .addChildren(
  173. builder(Component.Type.FILE, FILE_1_REF).build())
  174. .build();
  175. treeRootHolder.setRoot(root);
  176. measureRepository.addRawMeasure(ROOT_REF, LINES_KEY, newMeasureBuilder().create(10));
  177. measureRepository.addRawMeasure(FILE_1_REF, LINES_KEY, newMeasureBuilder().create(2));
  178. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
  179. .visit(root);
  180. assertAddedRawMeasure(ROOT_REF, 2);
  181. assertAddedRawMeasure(FILE_1_REF, 2);
  182. }
  183. @Test
  184. public void fail_when_trying_to_compute_file_measure_already_existing_in_report() {
  185. ReportComponent root = builder(PROJECT, ROOT_REF)
  186. .addChildren(
  187. builder(Component.Type.FILE, FILE_1_REF).build())
  188. .build();
  189. treeRootHolder.setRoot(root);
  190. measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(2));
  191. // expectedException.expectCause(
  192. // hasType(UnsupportedOperationException.class)
  193. // .andMessage(String.format("A measure can only be set once for Component (ref=%s), Metric (key=%s)", FILE_1_REF, NCLOC_KEY))
  194. // );
  195. assertThatThrownBy(() -> new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula())).visit(root))
  196. .hasCause( new UnsupportedOperationException(String.format("A measure can only be set once for Component (ref=%s), Metric (key=%s)", FILE_1_REF, NCLOC_KEY)));
  197. }
  198. @Test
  199. public void fail_on_project_without_children_already_having_computed_measure() {
  200. ReportComponent root = builder(PROJECT, ROOT_REF).build();
  201. treeRootHolder.setRoot(root);
  202. measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(10));
  203. // expectedException.expectCause(hasType(UnsupportedOperationException.class)
  204. // .andMessage(String.format("A measure can only be set once for Component (ref=%s), Metric (key=%s)", ROOT_REF, NCLOC_KEY)));
  205. assertThatThrownBy(() -> {
  206. new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
  207. .visit(root);
  208. })
  209. .hasCause(new UnsupportedOperationException(String.format("A measure can only be set once for Component (ref=%s), Metric (key=%s)", ROOT_REF, NCLOC_KEY)));
  210. }
  211. private FormulaExecutorComponentVisitor formulaExecutorComponentVisitor(Formula formula) {
  212. return FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
  213. .buildFor(ImmutableList.of(formula));
  214. }
  215. private static Measure createMeasureWithVariation(double variation) {
  216. return newMeasureBuilder().setVariation(variation).createNoValue();
  217. }
  218. private void assertAddedRawMeasure(int componentRef, int expectedValue) {
  219. assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).containsOnly(entryOf(NCLOC_KEY, newMeasureBuilder().create(expectedValue)));
  220. }
  221. private void assertAddedRawMeasureVariation(int componentRef, int variation) {
  222. assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef)))
  223. .containsOnly(entryOf(NEW_COVERAGE_KEY, createMeasureWithVariation(variation)));
  224. }
  225. private class FakeFormula implements Formula<FakeCounter> {
  226. @Override
  227. public FakeCounter createNewCounter() {
  228. return new FakeCounter();
  229. }
  230. @Override
  231. public Optional<Measure> createMeasure(FakeCounter counter, CreateMeasureContext context) {
  232. // verify the context which is passed to the method
  233. assertThat(context.getComponent()).isNotNull();
  234. assertThat(context.getMetric()).isSameAs(metricRepository.getByKey(NCLOC_KEY));
  235. return Optional.of(Measure.newMeasureBuilder().create(counter.value));
  236. }
  237. @Override
  238. public String[] getOutputMetricKeys() {
  239. return new String[] {NCLOC_KEY};
  240. }
  241. }
  242. private class FakeMultiMetricFormula implements Formula<FakeCounter> {
  243. @Override
  244. public FakeCounter createNewCounter() {
  245. return new FakeCounter();
  246. }
  247. @Override
  248. public Optional<Measure> createMeasure(FakeCounter counter, CreateMeasureContext context) {
  249. // verify the context which is passed to the method
  250. assertThat(context.getComponent()).isNotNull();
  251. assertThat(context.getMetric())
  252. .isIn(metricRepository.getByKey(NEW_LINES_TO_COVER_KEY), metricRepository.getByKey(NEW_COVERAGE_KEY));
  253. return Optional.of(Measure.newMeasureBuilder().create(counter.value + metricOffset(context.getMetric())));
  254. }
  255. private int metricOffset(Metric metric) {
  256. if (metric.getKey().equals(NEW_LINES_TO_COVER_KEY)) {
  257. return 10;
  258. }
  259. if (metric.getKey().equals(NEW_COVERAGE_KEY)) {
  260. return 100;
  261. }
  262. throw new IllegalArgumentException("Unsupported metric " + metric);
  263. }
  264. @Override
  265. public String[] getOutputMetricKeys() {
  266. return new String[] {NEW_LINES_TO_COVER_KEY, NEW_COVERAGE_KEY};
  267. }
  268. }
  269. private static class FakeCounter implements Counter<FakeCounter> {
  270. private int value = 0;
  271. @Override
  272. public void aggregate(FakeCounter counter) {
  273. this.value += counter.value;
  274. }
  275. @Override
  276. public void initialize(CounterInitializationContext context) {
  277. // verify the context which is passed to the method
  278. assertThat(context.getLeaf().getChildren()).isEmpty();
  279. Optional<Measure> measureOptional = context.getMeasure(LINES_KEY);
  280. if (measureOptional.isPresent()) {
  281. value += measureOptional.get().getIntValue();
  282. }
  283. }
  284. }
  285. private class FakeVariationFormula implements Formula<FakeVariationCounter> {
  286. @Override
  287. public FakeVariationCounter createNewCounter() {
  288. return new FakeVariationCounter();
  289. }
  290. @Override
  291. public Optional<Measure> createMeasure(FakeVariationCounter counter, CreateMeasureContext context) {
  292. // verify the context which is passed to the method
  293. assertThat(context.getComponent()).isNotNull();
  294. assertThat(context.getMetric()).isSameAs(metricRepository.getByKey(NEW_COVERAGE_KEY));
  295. IntValue measureVariations = counter.values;
  296. if (measureVariations.isSet()) {
  297. return Optional.of(
  298. newMeasureBuilder()
  299. .setVariation(measureVariations.getValue())
  300. .createNoValue());
  301. }
  302. return Optional.empty();
  303. }
  304. @Override
  305. public String[] getOutputMetricKeys() {
  306. return new String[] {NEW_COVERAGE_KEY};
  307. }
  308. }
  309. private static class FakeVariationCounter implements Counter<FakeVariationCounter> {
  310. private final IntValue values = new IntValue();
  311. @Override
  312. public void aggregate(FakeVariationCounter counter) {
  313. values.increment(counter.values);
  314. }
  315. @Override
  316. public void initialize(CounterInitializationContext context) {
  317. // verify the context which is passed to the method
  318. assertThat(context.getLeaf().getChildren()).isEmpty();
  319. Optional<Measure> measureOptional = context.getMeasure(NEW_LINES_TO_COVER_KEY);
  320. if (!measureOptional.isPresent()) {
  321. return;
  322. }
  323. this.values.increment((int) measureOptional.get().getVariation());
  324. }
  325. }
  326. }