aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Marion <steve.marion@sonarsource.com>2025-02-28 16:38:04 +0100
committerMatteo Mara <matteo.mara@sonarsource.com>2025-03-17 22:23:55 +0100
commita087eb237d62b3280f1fc5d3540b1b57afccd4bd (patch)
treee7cbc5dfa9581be63b0cdb9d8e1a2657a0f187f5
parente496fb60dd1fbf1fd90b6ca08b9499ba8035a5c0 (diff)
downloadsonarqube-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.
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java3
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/ArchitectureSensor.java60
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/package-info.java23
-rw-r--r--plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/architecture/ArchitectureSensorTest.java87
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java25
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java11
-rw-r--r--sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorageTest.java49
-rw-r--r--sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/analysisdata/AnalysisDataIT.java68
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java12
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/LocalPlugin.java8
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java36
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java10
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java25
-rw-r--r--sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/AnalysisResult.java4
-rw-r--r--sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java7
-rw-r--r--sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java10
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java4
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java12
-rw-r--r--sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java7
-rw-r--r--sonar-scanner-protocol/src/main/protobuf/scanner_report.proto6
20 files changed, 429 insertions, 38 deletions
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
index 7eed410abd7..b32bca06bda 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
@@ -24,6 +24,7 @@ import org.sonar.api.PropertyType;
import org.sonar.api.SonarProduct;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinition.ConfigScope;
+import org.sonar.xoo.architecture.ArchitectureSensor;
import org.sonar.xoo.coverage.ItCoverageSensor;
import org.sonar.xoo.coverage.OverallCoverageSensor;
import org.sonar.xoo.coverage.UtCoverageSensor;
@@ -197,6 +198,8 @@ public class XooPlugin implements Plugin {
HotspotWithSingleContextSensor.class,
HotspotWithCodeVariantsSensor.class,
+ ArchitectureSensor.class,
+
// Coverage
UtCoverageSensor.class,
ItCoverageSensor.class,
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/ArchitectureSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/ArchitectureSensor.java
new file mode 100644
index 00000000000..e8fbc623c87
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/ArchitectureSensor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.xoo.architecture;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.StreamSupport;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.scanner.sensor.ProjectSensor;
+import org.sonar.xoo.Xoo;
+
+public class ArchitectureSensor implements ProjectSensor {
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("architecture-xoo-sensor")
+ .onlyOnLanguage(Xoo.KEY);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ final String mimeType = "application/file-graph+json;version=1.0;source=xoo";
+
+ long count = StreamSupport.stream(
+ context.fileSystem().inputFiles(
+ context.fileSystem().predicates().hasLanguage(Xoo.KEY)).spliterator(), false)
+ .count();
+
+ context.addAnalysisData(
+ Xoo.NAME + ".class_file_graph",
+ mimeType,
+ new ByteArrayInputStream(("{graph:\"data\", \"classCount\":" + count + "}")
+ .getBytes(StandardCharsets.UTF_8))
+ );
+
+ context.addAnalysisData(
+ Xoo.NAME + ".file_graph",
+ mimeType,
+ new ByteArrayInputStream(("{graph:\"data\", \"fileCount\":" + count + "}")
+ .getBytes(StandardCharsets.UTF_8))
+ );
+ }
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/package-info.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/package-info.java
new file mode 100644
index 00000000000..45a09887dc7
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/architecture/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.xoo.architecture;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/architecture/ArchitectureSensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/architecture/ArchitectureSensorTest.java
new file mode 100644
index 00000000000..09e46c9a21d
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/architecture/ArchitectureSensorTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.xoo.architecture;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.xoo.Xoo;
+
+public class ArchitectureSensorTest {
+
+ @Test
+ public void whenDescribeCalled_thenNameAndLanguageAreSet() {
+ // given
+ SensorDescriptor descriptor = mock(SensorDescriptor.class);
+ when(descriptor.name(anyString())).thenReturn(descriptor);
+ when(descriptor.onlyOnLanguage(anyString())).thenReturn(descriptor);
+
+ ArchitectureSensor sensor = new ArchitectureSensor();
+
+ // when
+ sensor.describe(descriptor);
+
+ // then
+ verify(descriptor).onlyOnLanguage(Xoo.KEY);
+ verify(descriptor).name(anyString());
+ }
+
+ @Test
+ public void whenExecuteCalled_thenArchitectureDataIsSaved() {
+ // given
+ final int nbFileSensor = 5;
+ SensorContext context = mock(SensorContext.class, RETURNS_DEEP_STUBS);
+
+ when(context.fileSystem().inputFiles(any())).thenReturn(
+ Stream.generate(() -> mock(InputFile.class)).limit(nbFileSensor).toList()
+ );
+
+ ArchitectureSensor sensor = new ArchitectureSensor();
+
+ // when
+ sensor.execute(context);
+
+ // then
+ ArgumentCaptor<InputStream> inputStreamCaptor = ArgumentCaptor.forClass(InputStream.class);
+ verify(context).addAnalysisData(eq(Xoo.NAME + ".file_graph"), contains("application/file-graph+json"), inputStreamCaptor.capture());
+ try {
+ String capturedData = new String(inputStreamCaptor.getValue().readAllBytes(), StandardCharsets.UTF_8);
+ assertThat(capturedData).contains("\"fileCount\":" + nbFileSensor);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java
index b62b77831b4..09157ef9420 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java
@@ -19,12 +19,17 @@
*/
package org.sonar.api.batch.sensor.internal;
+import static org.sonar.api.utils.Preconditions.checkArgument;
+
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
import org.sonar.api.batch.sensor.code.NewSignificantCode;
import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode;
import org.sonar.api.batch.sensor.coverage.NewCoverage;
@@ -41,8 +46,6 @@ import org.sonar.api.batch.sensor.rule.AdHocRule;
import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
-import static org.sonar.api.utils.Preconditions.checkArgument;
-
class InMemorySensorStorage implements SensorStorage {
Map<String, Map<String, Measure>> measuresByComponentAndMetric = new HashMap<>();
@@ -58,6 +61,7 @@ class InMemorySensorStorage implements SensorStorage {
Map<String, DefaultSymbolTable> symbolsPerComponent = new HashMap<>();
Map<String, String> contextProperties = new HashMap<>();
Map<String, String> telemetryEntries = new HashMap<>();
+ Map<String, AnalysisData> analysisDataEntries = new HashMap<>();
Map<String, DefaultSignificantCode> significantCodePerComponent = new HashMap<>();
@Override
@@ -157,4 +161,21 @@ class InMemorySensorStorage implements SensorStorage {
}
significantCodePerComponent.put(fileKey, significantCode);
}
+
+ public void storeAnalysisData(String key, String mimeType, InputStream data) {
+ checkArgument(!StringUtils.isBlank(key), "Key must not be null");
+ checkArgument(!StringUtils.isBlank(mimeType), "MimeType must not be null");
+ checkArgument(data != null, "Data must not be null");
+ if (analysisDataEntries.containsKey(key)) {
+ throw new UnsupportedOperationException("Trying to save analysis data twice for the same key is not supported: " + key);
+ }
+ try (data) {
+ byte[] bytes = data.readAllBytes();
+ analysisDataEntries.put(key, new AnalysisData(key, mimeType, bytes));
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to read data from InputStream", e);
+ }
+ }
+
+ record AnalysisData(String key, String mimeType, byte[] data) { }
}
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
index 02c5fa5191f..b4fd5512335 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
@@ -19,7 +19,10 @@
*/
package org.sonar.api.batch.sensor.internal;
+import static java.util.Collections.unmodifiableMap;
+
import java.io.File;
+import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.Path;
@@ -87,8 +90,6 @@ import org.sonar.api.scanner.fs.InputProject;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.Version;
-import static java.util.Collections.unmodifiableMap;
-
/**
* Utility class to help testing {@link Sensor}. This is not an API and method signature may evolve.
* <p>
@@ -447,6 +448,12 @@ public class SensorContextTester implements SensorContext {
sensorStorage.storeTelemetry(key, value);
}
+ @Override
+ public void addAnalysisData(String key, String mimeType, InputStream data) {
+ //No Need to check the source of the plugin in the tester
+ sensorStorage.storeAnalysisData(key,mimeType, data);
+ }
+
public void setCacheEnabled(boolean enabled) {
this.cacheEnabled = enabled;
}
diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorageTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorageTest.java
index d4ca8ee5865..85b073639b6 100644
--- a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorageTest.java
+++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorageTest.java
@@ -19,12 +19,16 @@
*/
package org.sonar.api.batch.sensor.internal;
-import org.junit.Test;
-
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 java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.Test;
+
public class InMemorySensorStorageTest {
InMemorySensorStorage underTest = new InMemorySensorStorage();
@@ -50,4 +54,45 @@ public class InMemorySensorStorageTest {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Value of context property must not be null");
}
+
+ @Test
+ public void test_storeAnalysisData() {
+ // Given
+ String key = "analysisKey";
+ String mimeType = "mimeType";
+ String dataString = "analysisData";
+ ByteArrayInputStream dataStream = new ByteArrayInputStream(dataString.getBytes(StandardCharsets.UTF_8));
+
+ // When
+ underTest.storeAnalysisData(key, mimeType, dataStream);
+
+ // Then
+ assertThat(underTest.analysisDataEntries).containsKey(key);
+ assertThat(new String(underTest.analysisDataEntries.get(key).data(), StandardCharsets.UTF_8)).isEqualTo(dataString);
+ }
+
+ @Test
+ public void storeAnalysisData_throws_UOE_if_operation_not_supported() {
+ underTest.storeAnalysisData("unsupportedKey", "mimeType", new ByteArrayInputStream("dummyData".getBytes(StandardCharsets.UTF_8)));
+ ByteArrayInputStream dataStream = new ByteArrayInputStream("newData".getBytes(StandardCharsets.UTF_8));
+
+ assertThatThrownBy(() -> underTest.storeAnalysisData("unsupportedKey", "mimeType", dataStream))
+ .isInstanceOf(UnsupportedOperationException.class);
+ }
+
+ @Test
+ public void storeAnalysisData_throws_IOE_on_data_handling_error() {
+ InputStream faultyStream = new InputStream() {
+ @Override
+ public int read() throws IOException {
+ throw new IOException("Simulated IO Exception");
+ }
+ };
+
+ assertThatThrownBy(() -> underTest.storeAnalysisData("validKey", "mimeType", faultyStream))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("Failed to read data from InputStream");
+ }
+
+
}
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;
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java
index 3c0375698b7..fb10916aeac 100644
--- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java
@@ -98,6 +98,10 @@ public class FileStructure {
return new File(dir, "analysis-warnings.pb");
}
+ public File analysisData() {
+ return new File(dir, "analysis-data.pb");
+ }
+
public File root() {
return dir;
}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java
index cdb7edb2f96..797c6ecae74 100644
--- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportReader.java
@@ -19,6 +19,8 @@
*/
package org.sonar.scanner.protocol.output;
+import static org.sonar.core.util.CloseableIterator.emptyCloseableIterator;
+
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -28,8 +30,6 @@ import javax.annotation.CheckForNull;
import org.sonar.core.util.CloseableIterator;
import org.sonar.core.util.Protobuf;
-import static org.sonar.core.util.CloseableIterator.emptyCloseableIterator;
-
public class ScannerReportReader {
private final FileStructure fileStructure;
@@ -241,4 +241,12 @@ public class ScannerReportReader {
}
return Protobuf.readStream(file, ScannerReport.TelemetryEntry.parser());
}
+
+ public CloseableIterator<ScannerReport.AnalysisData> readAnalysisData() {
+ File file = fileStructure.analysisData();
+ if (!fileExists(file)) {
+ return emptyCloseableIterator();
+ }
+ return Protobuf.readStream(file, ScannerReport.AnalysisData.parser());
+ }
}
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java
index 2a27e0b2b3f..bbdb6d753fc 100644
--- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java
+++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java
@@ -26,7 +26,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
-
import org.apache.commons.io.FileUtils;
import org.sonar.core.util.ContextException;
import org.sonar.core.util.Protobuf;
@@ -167,6 +166,12 @@ public class ScannerReportWriter {
return file;
}
+ public File appendAnalysisData(ScannerReport.AnalysisData analysisData) {
+ File file = fileStructure.analysisData();
+ appendDelimitedTo(file, analysisData, "analysis data for " + analysisData.getKey());
+ return file;
+ }
+
public File getSourceFile(int componentRef) {
return fileStructure.fileFor(FileStructure.Domain.SOURCE, componentRef);
}
diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
index 1357ba53499..aaf5f025ee9 100644
--- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
+++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
@@ -92,6 +92,12 @@ message TelemetryEntry {
string value = 2;
}
+message AnalysisData {
+ string key = 1;
+ string mime_type = 2;
+ bytes data = 3;
+}
+
message ActiveRule {
string rule_repository = 1;
string rule_key = 2;