diff options
author | Steve Marion <steve.marion@sonarsource.com> | 2025-02-28 16:38:04 +0100 |
---|---|---|
committer | Matteo Mara <matteo.mara@sonarsource.com> | 2025-03-17 22:23:55 +0100 |
commit | a087eb237d62b3280f1fc5d3540b1b57afccd4bd (patch) | |
tree | e7cbc5dfa9581be63b0cdb9d8e1a2657a0f187f5 /sonar-scanner-engine | |
parent | e496fb60dd1fbf1fd90b6ca08b9499ba8035a5c0 (diff) | |
download | sonarqube-a087eb237d62b3280f1fc5d3540b1b57afccd4bd.tar.gz sonarqube-a087eb237d62b3280f1fc5d3540b1b57afccd4bd.zip |
SONAR-24521 Implements sonar plugin API addAnalysisData method in the scanner-engine.
Add Xoo plugin sensor that utilizes the analysisData method.
Diffstat (limited to 'sonar-scanner-engine')
9 files changed, 151 insertions, 29 deletions
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/analysisdata/AnalysisDataIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/analysisdata/AnalysisDataIT.java new file mode 100644 index 00000000000..732d759264c --- /dev/null +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/analysisdata/AnalysisDataIT.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.mediumtest.analysisdata; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.testfixtures.log.LogTester; +import org.sonar.core.platform.PluginInfo; +import org.sonar.scanner.mediumtest.AnalysisResult; +import org.sonar.scanner.mediumtest.ScannerMediumTester; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.xoo.Xoo; +import org.sonar.xoo.XooPlugin; + +public class AnalysisDataIT { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public ScannerMediumTester tester = new ScannerMediumTester() + .registerPlugin(new PluginInfo("xoo").setOrganizationName("SonarSource"), new XooPlugin()) + .addDefaultQProfile("xoo", "Sonar Way"); + + @Test + public void whenScanningWithXoo_thenArchitectureGraphIsInReport() throws Exception { + // given + File projectDir = new File("test-resources/mediumtest/xoo/sample"); + File tmpDir = temp.newFolder(); + FileUtils.copyDirectory(projectDir, tmpDir); + + // when + AnalysisResult result = tester + .newAnalysis(new File(tmpDir, "sonar-project.properties")) + .execute(); + + // then + List<ScannerReport.AnalysisData> analysisData = result.analysisData(); + assertThat(analysisData) + .anyMatch(data -> data.getKey().equals(Xoo.NAME + ".file_graph")); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java index 3187c2e9aa3..194c1e17ffa 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java @@ -19,6 +19,10 @@ */ package org.sonar.scanner.bootstrap; +import static java.util.stream.Collectors.toMap; +import static org.sonar.api.utils.Preconditions.checkState; +import static org.sonar.core.config.ScannerProperties.PLUGIN_LOADING_OPTIMIZATION_KEY; + import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -38,10 +42,6 @@ import org.sonar.core.platform.PluginRepository; import org.sonar.core.plugin.PluginType; import org.sonar.scanner.mediumtest.LocalPlugin; -import static java.util.stream.Collectors.toMap; -import static org.sonar.api.utils.Preconditions.checkState; -import static org.sonar.core.config.ScannerProperties.PLUGIN_LOADING_OPTIMIZATION_KEY; - /** * Orchestrates the installation and loading of plugins */ @@ -83,7 +83,7 @@ public class ScannerPluginRepository implements PluginRepository, Startable { // this part is only used by medium tests for (LocalPlugin localPlugin : installer.installLocals()) { ScannerPlugin scannerPlugin = localPlugin.toScannerPlugin(); - String pluginKey = localPlugin.pluginKey(); + String pluginKey = localPlugin.pluginInfo().getKey(); pluginsByKeys.put(pluginKey, scannerPlugin); pluginInstancesByKeys.put(pluginKey, localPlugin.pluginInstance()); } @@ -112,7 +112,7 @@ public class ScannerPluginRepository implements PluginRepository, Startable { // this part is only used by medium tests for (LocalPlugin localPlugin : installer.installOptionalLocals(languageKeys)) { ScannerPlugin scannerPlugin = localPlugin.toScannerPlugin(); - String pluginKey = localPlugin.pluginKey(); + String pluginKey = localPlugin.pluginInfo().getKey(); languagePluginsByKeys.put(pluginKey, scannerPlugin); pluginsByKeys.put(pluginKey, scannerPlugin); pluginInstancesByKeys.put(pluginKey, localPlugin.pluginInstance()); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/LocalPlugin.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/LocalPlugin.java index 0a3d42979f7..70df99a4f3d 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/LocalPlugin.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/LocalPlugin.java @@ -25,9 +25,13 @@ import org.sonar.core.platform.PluginInfo; import org.sonar.core.plugin.PluginType; import org.sonar.scanner.bootstrap.ScannerPlugin; -public record LocalPlugin(String pluginKey, Plugin pluginInstance, Set<String> requiredForLanguages) { +public record LocalPlugin(PluginInfo pluginInfo, Plugin pluginInstance, Set<String> requiredForLanguages) { + + public LocalPlugin(String pluginKey, Plugin pluginInstance, Set<String> requiredForLanguages) { + this(new PluginInfo(pluginKey).setOrganizationName("SonarSource"), pluginInstance, requiredForLanguages); + } public ScannerPlugin toScannerPlugin() { - return new ScannerPlugin(pluginKey, 1L, PluginType.BUNDLED, new PluginInfo(pluginKey)); + return new ScannerPlugin(pluginInfo.getKey(), 1L, PluginType.BUNDLED, pluginInfo); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java index 5fa6e33aac6..dfc6df5050c 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java @@ -19,6 +19,16 @@ */ package org.sonar.scanner.sensor; +import static java.lang.Math.max; +import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_DATA_KEY; +import static org.sonar.api.measures.CoreMetrics.LINES_KEY; +import static org.sonar.api.measures.CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY_KEY; +import static org.sonar.api.measures.CoreMetrics.TEST_SUCCESS_DENSITY_KEY; +import static org.sonar.api.utils.Preconditions.checkArgument; + +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; import java.util.HashSet; import java.util.List; @@ -28,6 +38,7 @@ import java.util.SortedMap; import java.util.TreeMap; import java.util.stream.Collectors; import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputComponent; @@ -80,12 +91,6 @@ import org.sonar.scanner.repository.ContextPropertiesCache; import org.sonar.scanner.repository.TelemetryCache; import org.sonar.scanner.scan.branch.BranchConfiguration; -import static java.lang.Math.max; -import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_DATA_KEY; -import static org.sonar.api.measures.CoreMetrics.LINES_KEY; -import static org.sonar.api.measures.CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY_KEY; -import static org.sonar.api.measures.CoreMetrics.TEST_SUCCESS_DENSITY_KEY; - public class DefaultSensorStorage implements SensorStorage { private static final Logger LOG = LoggerFactory.getLogger(DefaultSensorStorage.class); @@ -122,6 +127,7 @@ public class DefaultSensorStorage implements SensorStorage { private final ScannerMetrics scannerMetrics; private final BranchConfiguration branchConfiguration; private final Set<String> alreadyLogged = new HashSet<>(); + private final Set<String> alreadyAddedData = new HashSet<>(); public DefaultSensorStorage(MetricFinder metricFinder, IssuePublisher moduleIssues, Configuration settings, ReportPublisher reportPublisher, SonarCpdBlockIndex index, ContextPropertiesCache contextPropertiesCache, TelemetryCache telemetryCache, ScannerMetrics scannerMetrics, BranchConfiguration branchConfiguration) { @@ -472,4 +478,22 @@ public class DefaultSensorStorage implements SensorStorage { writer.writeComponentSignificantCode(componentRef, protobuf); } + + public void storeAnalysisData(String key, String mimeType, InputStream data) { + checkArgument(!StringUtils.isBlank(key), "Key must not be null"); + checkArgument(!alreadyAddedData.contains(key), "A data with this key already exists"); + checkArgument(!StringUtils.isBlank(mimeType), "MimeType must not be null"); + checkArgument(data != null, "Data must not be null"); + alreadyAddedData.add(key); + try (data) { + ScannerReport.AnalysisData analysisData = ScannerReport.AnalysisData.newBuilder() + .setKey(key) + .setData(ByteString.readFrom(data)) + .build(); + ScannerReportWriter writer = reportPublisher.getWriter(); + writer.appendAnalysisData(analysisData); + } catch (IOException e) { + throw new IllegalArgumentException("Failed to read data InputStream", e); + } + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java index bac06a38645..855fd945109 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java @@ -19,6 +19,7 @@ */ package org.sonar.scanner.sensor; +import java.io.InputStream; import java.io.Serializable; import javax.annotation.concurrent.ThreadSafe; import org.sonar.api.SonarRuntime; @@ -233,6 +234,15 @@ public class ProjectSensorContext implements SensorContext { } @Override + public void addAnalysisData(String key, String mimeType, InputStream data) { + if (isSonarSourcePlugin()) { + this.sensorStorage.storeAnalysisData(key, mimeType, data); + } else { + throw new IllegalStateException("Analysis data can only be added by SonarSource plugins"); + } + } + + @Override public NewSignificantCode newSignificantCode() { return new DefaultSignificantCode(sensorStorage); } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java index a6cabe242fb..e769e0d6451 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java @@ -19,6 +19,16 @@ */ package org.sonar.scanner.sensor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.data.MapEntry.entry; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + import java.io.File; import java.util.ArrayList; import java.util.List; @@ -67,16 +77,6 @@ import org.sonar.scanner.repository.ContextPropertiesCache; import org.sonar.scanner.repository.TelemetryCache; import org.sonar.scanner.scan.branch.BranchConfiguration; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.data.MapEntry.entry; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - class DefaultSensorStorageTest { @TempDir @@ -112,8 +112,9 @@ class DefaultSensorStorageTest { branchConfiguration = mock(BranchConfiguration.class); - underTest = new DefaultSensorStorage(metricFinder, - moduleIssues, settings.asConfig(), reportPublisher, mock(SonarCpdBlockIndex.class), contextPropertiesCache, telemetryCache, new ScannerMetrics(), branchConfiguration); + underTest = new DefaultSensorStorage( + metricFinder, moduleIssues, settings.asConfig(), reportPublisher, mock(SonarCpdBlockIndex.class), contextPropertiesCache, telemetryCache, new ScannerMetrics(), + branchConfiguration); project = new DefaultInputProject(ProjectDefinition.create() .setKey("foo") diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java index 25b27dbb5ae..57843c8abd7 100644 --- a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java +++ b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java @@ -199,6 +199,10 @@ public class AnalysisResult implements AnalysisObserver { return readFromReport(ScannerReportReader::readAdHocRules); } + public List<ScannerReport.AnalysisData> analysisData() { + return readFromReport(ScannerReportReader::readAnalysisData); + } + @NotNull private <G> List<G> readFromReport(InputComponent component, BiFunction<ScannerReportReader, Integer, CloseableIterator<G>> readerMethod) { int ref = ((DefaultInputComponent) component).scannerId(); diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java index 60a8348f778..5934c9da0bc 100644 --- a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java +++ b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java @@ -19,13 +19,13 @@ */ package org.sonar.scanner.mediumtest; +import jakarta.annotation.Priority; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import jakarta.annotation.Priority; import org.sonar.api.Plugin; import org.sonar.core.platform.PluginInfo; import org.sonar.core.plugin.PluginType; @@ -49,6 +49,11 @@ public class FakePluginInstaller implements PluginInstaller { return this; } + public FakePluginInstaller add(PluginInfo pluginInfo, Plugin instance) { + mediumTestPlugins.add(new LocalPlugin(pluginInfo, instance, Set.of())); + return this; + } + public FakePluginInstaller addOptional(String pluginKey, Set<String> requiredForLanguages, Plugin instance) { optionalMediumTestPlugins.add(new LocalPlugin(pluginKey, instance, requiredForLanguages)); return this; diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java index 1f6738ec99c..f2edfe609d4 100644 --- a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java +++ b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java @@ -19,6 +19,8 @@ */ package org.sonar.scanner.mediumtest; +import static java.util.Collections.emptySet; + import jakarta.annotation.Priority; import java.io.File; import java.io.FileInputStream; @@ -60,6 +62,7 @@ import org.sonar.api.utils.Version; import org.sonar.batch.bootstrapper.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonar.batch.bootstrapper.LogOutput; +import org.sonar.core.platform.PluginInfo; import org.sonar.scanner.bootstrap.GlobalAnalysisMode; import org.sonar.scanner.cache.AnalysisCacheLoader; import org.sonar.scanner.protocol.internal.SensorCacheData; @@ -86,8 +89,6 @@ import org.sonarqube.ws.NewCodePeriods; import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; import org.sonarqube.ws.Rules.Rule; -import static java.util.Collections.emptySet; - /** * Main utility class for writing scanner medium tests. */ @@ -146,6 +147,11 @@ public class ScannerMediumTester extends ExternalResource implements BeforeTestE return this; } + public ScannerMediumTester registerPlugin(PluginInfo pluginInfo, Plugin instance) { + pluginInstaller.add(pluginInfo, instance); + return this; + } + public ScannerMediumTester registerOptionalPlugin(String pluginKey, Set<String> requiredForLanguages, Plugin instance) { pluginInstaller.addOptional(pluginKey, requiredForLanguages, instance); return this; |