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.

DefaultSensorStorageTest.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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.scanner.sensor;
  21. import java.io.File;
  22. import java.io.IOException;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import org.assertj.core.groups.Tuple;
  26. import org.junit.Before;
  27. import org.junit.Rule;
  28. import org.junit.Test;
  29. import org.junit.rules.TemporaryFolder;
  30. import org.mockito.ArgumentCaptor;
  31. import org.sonar.api.batch.bootstrap.ProjectDefinition;
  32. import org.sonar.api.batch.fs.InputFile;
  33. import org.sonar.api.batch.fs.internal.DefaultInputDir;
  34. import org.sonar.api.batch.fs.internal.DefaultInputFile;
  35. import org.sonar.api.batch.fs.internal.DefaultInputModule;
  36. import org.sonar.api.batch.fs.internal.DefaultInputProject;
  37. import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
  38. import org.sonar.api.batch.measure.MetricFinder;
  39. import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode;
  40. import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
  41. import org.sonar.api.batch.sensor.highlighting.TypeOfText;
  42. import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
  43. import org.sonar.api.batch.sensor.issue.ExternalIssue;
  44. import org.sonar.api.batch.sensor.issue.Issue;
  45. import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue;
  46. import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
  47. import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation;
  48. import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
  49. import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule;
  50. import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
  51. import org.sonar.api.config.internal.MapSettings;
  52. import org.sonar.api.issue.impact.Severity;
  53. import org.sonar.api.issue.impact.SoftwareQuality;
  54. import org.sonar.api.measures.CoreMetrics;
  55. import org.sonar.api.rules.CleanCodeAttribute;
  56. import org.sonar.api.rules.RuleType;
  57. import org.sonar.core.metric.ScannerMetrics;
  58. import org.sonar.core.util.CloseableIterator;
  59. import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
  60. import org.sonar.scanner.issue.IssuePublisher;
  61. import org.sonar.scanner.protocol.Constants;
  62. import org.sonar.scanner.protocol.output.FileStructure;
  63. import org.sonar.scanner.protocol.output.ScannerReport;
  64. import org.sonar.scanner.protocol.output.ScannerReportReader;
  65. import org.sonar.scanner.protocol.output.ScannerReportWriter;
  66. import org.sonar.scanner.report.ReportPublisher;
  67. import org.sonar.scanner.repository.ContextPropertiesCache;
  68. import org.sonar.scanner.scan.branch.BranchConfiguration;
  69. import static org.assertj.core.api.Assertions.assertThat;
  70. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  71. import static org.assertj.core.data.MapEntry.entry;
  72. import static org.mockito.Mockito.mock;
  73. import static org.mockito.Mockito.verify;
  74. import static org.mockito.Mockito.verifyNoInteractions;
  75. import static org.mockito.Mockito.verifyNoMoreInteractions;
  76. import static org.mockito.Mockito.when;
  77. public class DefaultSensorStorageTest {
  78. @Rule
  79. public TemporaryFolder temp = new TemporaryFolder();
  80. private DefaultSensorStorage underTest;
  81. private MapSettings settings;
  82. private IssuePublisher moduleIssues;
  83. private ScannerReportWriter reportWriter;
  84. private ContextPropertiesCache contextPropertiesCache = new ContextPropertiesCache();
  85. private BranchConfiguration branchConfiguration;
  86. private DefaultInputProject project;
  87. private ScannerReportReader reportReader;
  88. private ReportPublisher reportPublisher;
  89. @Before
  90. public void prepare() throws Exception {
  91. MetricFinder metricFinder = mock(MetricFinder.class);
  92. when(metricFinder.<Integer>findByKey(CoreMetrics.NCLOC_KEY)).thenReturn(CoreMetrics.NCLOC);
  93. when(metricFinder.<String>findByKey(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY)).thenReturn(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION);
  94. when(metricFinder.<Integer>findByKey(CoreMetrics.LINES_TO_COVER_KEY)).thenReturn(CoreMetrics.LINES_TO_COVER);
  95. settings = new MapSettings();
  96. moduleIssues = mock(IssuePublisher.class);
  97. reportPublisher = mock(ReportPublisher.class);
  98. final File reportDir = temp.newFolder();
  99. FileStructure fileStructure = new FileStructure(reportDir);
  100. reportWriter = new ScannerReportWriter(fileStructure);
  101. reportReader = new ScannerReportReader(fileStructure);
  102. when(reportPublisher.getWriter()).thenReturn(reportWriter);
  103. when(reportPublisher.getReader()).thenReturn(reportReader);
  104. branchConfiguration = mock(BranchConfiguration.class);
  105. underTest = new DefaultSensorStorage(metricFinder,
  106. moduleIssues, settings.asConfig(), reportPublisher, mock(SonarCpdBlockIndex.class), contextPropertiesCache, new ScannerMetrics(), branchConfiguration);
  107. project = new DefaultInputProject(ProjectDefinition.create()
  108. .setKey("foo")
  109. .setBaseDir(temp.newFolder())
  110. .setWorkDir(temp.newFolder()));
  111. }
  112. @Test
  113. public void should_merge_coverage() {
  114. DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setLines(5).build();
  115. DefaultCoverage coverage = new DefaultCoverage(underTest);
  116. coverage.onFile(file).lineHits(3, 1);
  117. DefaultCoverage coverage2 = new DefaultCoverage(underTest);
  118. coverage2.onFile(file).lineHits(1, 1);
  119. underTest.store(coverage);
  120. underTest.store(coverage2);
  121. List<ScannerReport.LineCoverage> lineCoverage = new ArrayList<>();
  122. reportReader.readComponentCoverage(file.scannerId()).forEachRemaining(lineCoverage::add);
  123. assertThat(lineCoverage).containsExactly(
  124. // should be sorted by line
  125. ScannerReport.LineCoverage.newBuilder().setLine(1).setHits(true).build(),
  126. ScannerReport.LineCoverage.newBuilder().setLine(3).setHits(true).build());
  127. }
  128. @Test
  129. public void shouldFailIfUnknownMetric() {
  130. InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build();
  131. assertThatThrownBy(() -> underTest.store(new DefaultMeasure()
  132. .on(file)
  133. .forMetric(CoreMetrics.LINES)
  134. .withValue(10)))
  135. .isInstanceOf(UnsupportedOperationException.class)
  136. .hasMessage("Unknown metric: lines");
  137. }
  138. @Test
  139. public void shouldIgnoreMeasuresOnFolders() {
  140. underTest.store(new DefaultMeasure()
  141. .on(new DefaultInputDir("foo", "bar"))
  142. .forMetric(CoreMetrics.LINES)
  143. .withValue(10));
  144. verifyNoMoreInteractions(reportPublisher);
  145. }
  146. @Test
  147. public void shouldIgnoreMeasuresOnModules() throws IOException {
  148. ProjectDefinition module = ProjectDefinition.create().setBaseDir(temp.newFolder()).setWorkDir(temp.newFolder());
  149. ProjectDefinition root = ProjectDefinition.create().addSubProject(module);
  150. underTest.store(new DefaultMeasure()
  151. .on(new DefaultInputModule(module))
  152. .forMetric(CoreMetrics.LINES)
  153. .withValue(10));
  154. verifyNoMoreInteractions(reportPublisher);
  155. }
  156. @Test
  157. public void should_save_issue() {
  158. InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build();
  159. DefaultIssue issue = new DefaultIssue(project).at(new DefaultIssueLocation().on(file));
  160. underTest.store(issue);
  161. ArgumentCaptor<Issue> argumentCaptor = ArgumentCaptor.forClass(Issue.class);
  162. verify(moduleIssues).initAndAddIssue(argumentCaptor.capture());
  163. assertThat(argumentCaptor.getValue()).isEqualTo(issue);
  164. }
  165. @Test
  166. public void should_save_external_issue() {
  167. InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build();
  168. DefaultExternalIssue externalIssue = new DefaultExternalIssue(project).at(new DefaultIssueLocation().on(file));
  169. underTest.store(externalIssue);
  170. ArgumentCaptor<ExternalIssue> argumentCaptor = ArgumentCaptor.forClass(ExternalIssue.class);
  171. verify(moduleIssues).initAndAddExternalIssue(argumentCaptor.capture());
  172. assertThat(argumentCaptor.getValue()).isEqualTo(externalIssue);
  173. }
  174. @Test
  175. public void should_skip_issue_on_pr_when_file_status_is_SAME() {
  176. InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build();
  177. when(branchConfiguration.isPullRequest()).thenReturn(true);
  178. DefaultIssue issue = new DefaultIssue(project).at(new DefaultIssueLocation().on(file));
  179. underTest.store(issue);
  180. verifyNoInteractions(moduleIssues);
  181. }
  182. @Test
  183. public void has_issues_delegates_to_report_publisher() {
  184. DefaultInputFile file1 = new TestInputFileBuilder("foo", "src/Foo1.php").setStatus(InputFile.Status.SAME).build();
  185. DefaultInputFile file2 = new TestInputFileBuilder("foo", "src/Foo2.php").setStatus(InputFile.Status.SAME).build();
  186. reportWriter.writeComponentIssues(file1.scannerId(), List.of(ScannerReport.Issue.newBuilder().build()));
  187. assertThat(underTest.hasIssues(file1)).isTrue();
  188. assertThat(underTest.hasIssues(file2)).isFalse();
  189. }
  190. @Test
  191. public void should_save_highlighting() {
  192. DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
  193. .setContents("// comment").build();
  194. DefaultHighlighting highlighting = new DefaultHighlighting(underTest).onFile(file).highlight(1, 0, 1, 1, TypeOfText.KEYWORD);
  195. underTest.store(highlighting);
  196. assertThat(reportWriter.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, file.scannerId())).isTrue();
  197. }
  198. @Test
  199. public void should_skip_highlighting_on_pr_when_file_status_is_SAME() {
  200. DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
  201. .setContents("// comment")
  202. .setStatus(InputFile.Status.SAME).build();
  203. when(branchConfiguration.isPullRequest()).thenReturn(true);
  204. DefaultHighlighting highlighting = new DefaultHighlighting(underTest).onFile(file).highlight(1, 0, 1, 1, TypeOfText.KEYWORD);
  205. underTest.store(highlighting);
  206. assertThat(reportWriter.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, file.scannerId())).isFalse();
  207. }
  208. @Test
  209. public void should_save_file_measure() {
  210. DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
  211. .build();
  212. underTest.store(new DefaultMeasure()
  213. .on(file)
  214. .forMetric(CoreMetrics.NCLOC)
  215. .withValue(10));
  216. ScannerReport.Measure m = reportReader.readComponentMeasures(file.scannerId()).next();
  217. assertThat(m.getIntValue().getValue()).isEqualTo(10);
  218. assertThat(m.getMetricKey()).isEqualTo(CoreMetrics.NCLOC_KEY);
  219. }
  220. @Test
  221. public void should_not_skip_file_measures_on_pull_request_when_file_status_is_SAME() {
  222. DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build();
  223. when(branchConfiguration.isPullRequest()).thenReturn(true);
  224. underTest.store(new DefaultMeasure()
  225. .on(file)
  226. .forMetric(CoreMetrics.NCLOC)
  227. .withValue(10));
  228. ScannerReport.Measure m = reportReader.readComponentMeasures(file.scannerId()).next();
  229. assertThat(m.getIntValue().getValue()).isEqualTo(10);
  230. assertThat(m.getMetricKey()).isEqualTo(CoreMetrics.NCLOC_KEY);
  231. }
  232. @Test
  233. public void should_skip_significant_code_on_pull_request_when_file_status_is_SAME() {
  234. DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
  235. .setStatus(InputFile.Status.SAME)
  236. .setContents("foo")
  237. .build();
  238. when(branchConfiguration.isPullRequest()).thenReturn(true);
  239. underTest.store(new DefaultSignificantCode()
  240. .onFile(file)
  241. .addRange(file.selectLine(1)));
  242. assertThat(reportWriter.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, file.scannerId())).isFalse();
  243. }
  244. @Test
  245. public void should_save_significant_code() {
  246. DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
  247. .setContents("foo")
  248. .build();
  249. underTest.store(new DefaultSignificantCode()
  250. .onFile(file)
  251. .addRange(file.selectLine(1)));
  252. assertThat(reportWriter.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, file.scannerId())).isTrue();
  253. }
  254. @Test
  255. public void should_save_project_measure() throws IOException {
  256. String projectKey = "myProject";
  257. DefaultInputModule module = new DefaultInputModule(ProjectDefinition.create().setKey(projectKey).setBaseDir(temp.newFolder()).setWorkDir(temp.newFolder()));
  258. underTest.store(new DefaultMeasure()
  259. .on(module)
  260. .forMetric(CoreMetrics.NCLOC)
  261. .withValue(10));
  262. ScannerReport.Measure m = reportReader.readComponentMeasures(module.scannerId()).next();
  263. assertThat(m.getIntValue().getValue()).isEqualTo(10);
  264. assertThat(m.getMetricKey()).isEqualTo(CoreMetrics.NCLOC_KEY);
  265. }
  266. @Test(expected = UnsupportedOperationException.class)
  267. public void duplicateHighlighting() throws Exception {
  268. InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java")
  269. .setModuleBaseDir(temp.newFolder().toPath()).build();
  270. DefaultHighlighting h = new DefaultHighlighting(null)
  271. .onFile(inputFile);
  272. underTest.store(h);
  273. underTest.store(h);
  274. }
  275. @Test(expected = UnsupportedOperationException.class)
  276. public void duplicateSignificantCode() throws Exception {
  277. InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java")
  278. .setModuleBaseDir(temp.newFolder().toPath()).build();
  279. DefaultSignificantCode h = new DefaultSignificantCode(null)
  280. .onFile(inputFile);
  281. underTest.store(h);
  282. underTest.store(h);
  283. }
  284. @Test(expected = UnsupportedOperationException.class)
  285. public void duplicateSymbolTable() throws Exception {
  286. InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java")
  287. .setModuleBaseDir(temp.newFolder().toPath()).build();
  288. DefaultSymbolTable st = new DefaultSymbolTable(null)
  289. .onFile(inputFile);
  290. underTest.store(st);
  291. underTest.store(st);
  292. }
  293. @Test
  294. public void shouldStoreContextProperty() {
  295. underTest.storeProperty("foo", "bar");
  296. assertThat(contextPropertiesCache.getAll()).containsOnly(entry("foo", "bar"));
  297. }
  298. @Test
  299. public void store_whenAdhocRuleIsSpecified_shouldWriteAdhocRuleToReport() throws IOException {
  300. underTest.store(new DefaultAdHocRule().ruleId("ruleId").engineId("engineId")
  301. .name("name")
  302. .addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
  303. .addDefaultImpact(SoftwareQuality.RELIABILITY, Severity.MEDIUM)
  304. .cleanCodeAttribute(CleanCodeAttribute.CLEAR)
  305. .severity(org.sonar.api.batch.rule.Severity.MAJOR)
  306. .type(RuleType.CODE_SMELL)
  307. .description("description"));
  308. try (CloseableIterator<ScannerReport.AdHocRule> adhocRuleIt = reportReader.readAdHocRules()) {
  309. ScannerReport.AdHocRule adhocRule = adhocRuleIt.next();
  310. assertThat(adhocRule).extracting(r -> r.getRuleId(), r -> r.getName(), r -> r.getSeverity(), r -> r.getType(), r -> r.getDescription())
  311. .containsExactlyInAnyOrder("ruleId", "name", Constants.Severity.MAJOR, ScannerReport.IssueType.CODE_SMELL, "description");
  312. assertThat(adhocRule.getDefaultImpactsList()).hasSize(2).extracting(i -> i.getSoftwareQuality(), i -> i.getSeverity())
  313. .containsExactlyInAnyOrder(
  314. Tuple.tuple(SoftwareQuality.MAINTAINABILITY.name(), Severity.HIGH.name()),
  315. Tuple.tuple(SoftwareQuality.RELIABILITY.name(), Severity.MEDIUM.name()));
  316. assertThat(adhocRule.getCleanCodeAttribute())
  317. .isEqualTo(CleanCodeAttribute.CLEAR.name());
  318. }
  319. }
  320. @Test
  321. public void store_whenAdhocRuleIsSpecifiedWithOptionalFieldEmpty_shouldWriteAdhocRuleToReport() throws IOException {
  322. underTest.store(new DefaultAdHocRule().ruleId("ruleId").engineId("engineId")
  323. .name("name")
  324. .description("description"));
  325. try (CloseableIterator<ScannerReport.AdHocRule> adhocRuleIt = reportReader.readAdHocRules()) {
  326. ScannerReport.AdHocRule adhocRule = adhocRuleIt.next();
  327. assertThat(adhocRule).extracting(r -> r.getSeverity(), r -> r.getType())
  328. .containsExactlyInAnyOrder(Constants.Severity.UNSET_SEVERITY, ScannerReport.IssueType.UNSET);
  329. assertThat(adhocRule.getDefaultImpactsList()).isEmpty();
  330. assertThat(adhocRule.getCleanCodeAttribute()).isNullOrEmpty();
  331. }
  332. }
  333. }