aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java10
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java5
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TelemetryPublisher.java44
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/TelemetryCache.java59
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java31
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java6
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java34
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TelemetryPublisherTest.java66
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/TelemetryCacheTest.java83
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java107
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java7
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ProjectSensorContextTest.java92
-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/ScannerReportWriter.java6
-rw-r--r--sonar-scanner-protocol/src/main/protobuf/scanner_report.proto5
-rw-r--r--sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java21
-rw-r--r--sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java24
18 files changed, 532 insertions, 76 deletions
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 de7add875be..0b4d729aee8 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
@@ -57,6 +57,7 @@ class InMemorySensorStorage implements SensorStorage {
Map<String, List<DefaultCoverage>> coverageByComponent = new HashMap<>();
Map<String, DefaultSymbolTable> symbolsPerComponent = new HashMap<>();
Map<String, String> contextProperties = new HashMap<>();
+ Map<String, String> telemetryEntries = new HashMap<>();
Map<String, DefaultSignificantCode> significantCodePerComponent = new HashMap<>();
@Override
@@ -132,6 +133,15 @@ class InMemorySensorStorage implements SensorStorage {
contextProperties.put(key, value);
}
+ public void storeTelemetry(String key, String value) {
+ checkArgument(key != null, "Key of context property must not be null");
+ checkArgument(value != null, "Value of context property must not be null");
+
+ if (telemetryEntries.size() < 1000 || telemetryEntries.containsKey(key)) {
+ telemetryEntries.put(key, value);
+ }
+ }
+
@Override
public void store(ExternalIssue issue) {
allExternalIssues.add(issue);
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 ca8aae07c0f..c94a0ccaff0 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
@@ -442,8 +442,9 @@ public class SensorContextTester implements SensorContext {
}
@Override
- public void addTelemetryProperty(String s, String s1) {
- throw new UnsupportedOperationException("addTelemetryProperty");
+ public void addTelemetryProperty(String key, String value) {
+ //No Need to check the source of the plugin in the tester
+ sensorStorage.storeTelemetry(key, value);
}
public void setCacheEnabled(boolean enabled) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java
index 16d66d5b460..44afe06d7af 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java
@@ -79,6 +79,7 @@ import org.sonar.scanner.report.MetadataPublisher;
import org.sonar.scanner.report.ReportPublisher;
import org.sonar.scanner.report.ScannerFileStructureProvider;
import org.sonar.scanner.report.SourcePublisher;
+import org.sonar.scanner.report.TelemetryPublisher;
import org.sonar.scanner.report.TestExecutionPublisher;
import org.sonar.scanner.repository.ContextPropertiesCache;
import org.sonar.scanner.repository.DefaultProjectRepositoriesLoader;
@@ -86,6 +87,7 @@ import org.sonar.scanner.repository.DefaultQualityProfileLoader;
import org.sonar.scanner.repository.ProjectRepositoriesProvider;
import org.sonar.scanner.repository.QualityProfilesProvider;
import org.sonar.scanner.repository.ReferenceBranchSupplier;
+import org.sonar.scanner.repository.TelemetryCache;
import org.sonar.scanner.repository.language.DefaultLanguagesLoader;
import org.sonar.scanner.repository.language.DefaultLanguagesRepository;
import org.sonar.scanner.repository.settings.DefaultProjectSettingsLoader;
@@ -235,6 +237,7 @@ public class SpringScannerContainer extends SpringComponentContainer {
// context
ContextPropertiesCache.class,
+ TelemetryCache.class,
MutableProjectSettings.class,
SonarGlobalPropertiesFilter.class,
@@ -258,6 +261,7 @@ public class SpringScannerContainer extends SpringComponentContainer {
ActiveRulesPublisher.class,
ComponentsPublisher.class,
ContextPropertiesPublisher.class,
+ TelemetryPublisher.class,
AnalysisCachePublisher.class,
TestExecutionPublisher.class,
SourcePublisher.class,
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TelemetryPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TelemetryPublisher.java
new file mode 100644
index 00000000000..fdc2e96e9d6
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/TelemetryPublisher.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.report;
+
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReportWriter;
+import org.sonar.scanner.repository.TelemetryCache;
+
+public class TelemetryPublisher implements ReportPublisherStep {
+ private final TelemetryCache telemetryCache;
+
+ public TelemetryPublisher(TelemetryCache telemetryCache) {
+ this.telemetryCache = telemetryCache;
+ }
+
+ @Override
+ public void publish(ScannerReportWriter writer) {
+ writer.writeTelemetry(telemetryCache.getAll().entrySet()
+ .stream()
+ .map(e -> ScannerReport.TelemetryEntry.newBuilder()
+ .setKey(e.getKey())
+ .setValue(e.getValue())
+ .build())
+ .toList());
+ }
+
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/TelemetryCache.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/TelemetryCache.java
new file mode 100644
index 00000000000..7afeafb5858
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/TelemetryCache.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.repository;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.sonar.api.utils.Preconditions.checkArgument;
+
+public class TelemetryCache {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TelemetryCache.class);
+
+ private static final int MAX_ENTRIES = 1000;
+
+ private final Map<String, String> telemetryEntries = new HashMap<>();
+
+ /**
+ * Value is overridden if the key was already stored.
+ * Only the first {@link #MAX_ENTRIES} entries are stored.
+ * @throws IllegalArgumentException if key is null
+ * @throws IllegalArgumentException if value is null
+ * @since 10.8
+ */
+ public TelemetryCache put(String key, String value) {
+ checkArgument(key != null, "Key of the telemetry entry must not be null");
+ checkArgument(value != null, "Value of the telemetry entry must not be null");
+
+ if (telemetryEntries.size() < MAX_ENTRIES || telemetryEntries.containsKey(key)) {
+ telemetryEntries.put(key, value);
+ } else {
+ LOG.warn("Telemetry cache is full, dropping telemetry metric '{}'", key);
+ }
+ return this;
+ }
+
+ public Map<String, String> getAll() {
+ return telemetryEntries;
+ }
+}
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 d6011d56c74..e0930fc5372 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
@@ -76,6 +76,7 @@ import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonar.scanner.report.ReportPublisher;
import org.sonar.scanner.report.ScannerReportUtils;
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;
@@ -115,20 +116,21 @@ public class DefaultSensorStorage implements SensorStorage {
private final ReportPublisher reportPublisher;
private final SonarCpdBlockIndex index;
private final ContextPropertiesCache contextPropertiesCache;
+ private final TelemetryCache telemetryCache;
private final Configuration settings;
private final ScannerMetrics scannerMetrics;
private final BranchConfiguration branchConfiguration;
private final Set<String> alreadyLogged = new HashSet<>();
- public DefaultSensorStorage(MetricFinder metricFinder, IssuePublisher moduleIssues, Configuration settings,
- ReportPublisher reportPublisher, SonarCpdBlockIndex index,
- ContextPropertiesCache contextPropertiesCache, ScannerMetrics scannerMetrics, BranchConfiguration branchConfiguration) {
+ public DefaultSensorStorage(MetricFinder metricFinder, IssuePublisher moduleIssues, Configuration settings, ReportPublisher reportPublisher, SonarCpdBlockIndex index,
+ ContextPropertiesCache contextPropertiesCache, TelemetryCache telemetryCache, ScannerMetrics scannerMetrics, BranchConfiguration branchConfiguration) {
this.metricFinder = metricFinder;
this.moduleIssues = moduleIssues;
this.settings = settings;
this.reportPublisher = reportPublisher;
this.index = index;
this.contextPropertiesCache = contextPropertiesCache;
+ this.telemetryCache = telemetryCache;
this.scannerMetrics = scannerMetrics;
this.branchConfiguration = branchConfiguration;
}
@@ -151,7 +153,8 @@ public class DefaultSensorStorage implements SensorStorage {
}
if (component instanceof InputDir || (component instanceof DefaultInputModule defaultInputModule && defaultInputModule.definition().getParent() != null)) {
- logOnce(measure.metric().key(), "Storing measures on folders or modules is deprecated. Provided value of metric '{}' is ignored.", measure.metric().key());
+ logOnce(measure.metric().key(), "Storing measures on folders or modules is deprecated. Provided value of metric '{}' is ignored.",
+ measure.metric().key());
return;
}
@@ -166,7 +169,8 @@ public class DefaultSensorStorage implements SensorStorage {
}
if (!measure.isFromCore() && NEWLY_CORE_METRICS_KEYS.contains(measure.metric().key())) {
- logOnce(measure.metric().key(), "Metric '{}' is an internal metric computed by SonarQube. Provided value is ignored.", measure.metric().key());
+ logOnce(measure.metric().key(), "Metric '{}' is an internal metric computed by SonarQube. Provided value is ignored.",
+ measure.metric().key());
return;
}
@@ -361,8 +365,10 @@ public class DefaultSensorStorage implements SensorStorage {
SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine = reloadExistingCoverage(inputFile);
int lineCount = inputFile.lines();
- mergeLineCoverageValues(lineCount, defaultCoverage.hitsByLine(), coveragePerLine, (value, builder) -> builder.setHits(builder.getHits() || value > 0));
- mergeLineCoverageValues(lineCount, defaultCoverage.conditionsByLine(), coveragePerLine, (value, builder) -> builder.setConditions(max(value, builder.getConditions())));
+ mergeLineCoverageValues(lineCount, defaultCoverage.hitsByLine(), coveragePerLine,
+ (value, builder) -> builder.setHits(builder.getHits() || value > 0));
+ mergeLineCoverageValues(lineCount, defaultCoverage.conditionsByLine(), coveragePerLine,
+ (value, builder) -> builder.setConditions(max(value, builder.getConditions())));
mergeLineCoverageValues(lineCount, defaultCoverage.coveredConditionsByLine(), coveragePerLine,
(value, builder) -> builder.setCoveredConditions(max(value, builder.getCoveredConditions())));
@@ -373,7 +379,8 @@ public class DefaultSensorStorage implements SensorStorage {
private SortedMap<Integer, ScannerReport.LineCoverage.Builder> reloadExistingCoverage(DefaultInputFile inputFile) {
SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine = new TreeMap<>();
- try (CloseableIterator<ScannerReport.LineCoverage> lineCoverageCloseableIterator = reportPublisher.getReader().readComponentCoverage(inputFile.scannerId())) {
+ try (CloseableIterator<ScannerReport.LineCoverage> lineCoverageCloseableIterator =
+ reportPublisher.getReader().readComponentCoverage(inputFile.scannerId())) {
while (lineCoverageCloseableIterator.hasNext()) {
final ScannerReport.LineCoverage lineCoverage = lineCoverageCloseableIterator.next();
coveragePerLine.put(lineCoverage.getLine(), ScannerReport.LineCoverage.newBuilder(lineCoverage));
@@ -386,8 +393,8 @@ public class DefaultSensorStorage implements SensorStorage {
void apply(Integer value, ScannerReport.LineCoverage.Builder builder);
}
- private static void mergeLineCoverageValues(int lineCount, SortedMap<Integer, Integer> valueByLine, SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine,
- LineCoverageOperation op) {
+ private static void mergeLineCoverageValues(int lineCount, SortedMap<Integer, Integer> valueByLine, SortedMap<Integer,
+ ScannerReport.LineCoverage.Builder> coveragePerLine, LineCoverageOperation op) {
for (Map.Entry<Integer, Integer> lineMeasure : valueByLine.entrySet()) {
int lineIdx = lineMeasure.getKey();
if (lineIdx <= lineCount) {
@@ -437,6 +444,10 @@ public class DefaultSensorStorage implements SensorStorage {
contextPropertiesCache.put(key, value);
}
+ public void storeTelemetry(String key, String value) {
+ telemetryCache.put(key, value);
+ }
+
@Override
public void store(NewSignificantCode newSignificantCode) {
DefaultSignificantCode significantCode = (DefaultSignificantCode) newSignificantCode;
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java
index 3161da1e96d..6f27d1be09f 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java
@@ -29,6 +29,7 @@ import org.sonar.api.batch.sensor.cache.ReadCache;
import org.sonar.api.batch.sensor.cache.WriteCache;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.Settings;
+import org.sonar.scanner.bootstrap.ScannerPluginRepository;
import org.sonar.scanner.cache.AnalysisCacheEnabled;
import org.sonar.scanner.scan.branch.BranchConfiguration;
@@ -39,9 +40,10 @@ public class ModuleSensorContext extends ProjectSensorContext {
public ModuleSensorContext(DefaultInputProject project, InputModule module, Configuration config, Settings mutableModuleSettings, FileSystem fs, ActiveRules activeRules,
DefaultSensorStorage sensorStorage, SonarRuntime sonarRuntime, BranchConfiguration branchConfiguration,
- WriteCache writeCache, ReadCache readCache, AnalysisCacheEnabled analysisCacheEnabled, UnchangedFilesHandler unchangedFilesHandler) {
+ WriteCache writeCache, ReadCache readCache, AnalysisCacheEnabled analysisCacheEnabled, UnchangedFilesHandler unchangedFilesHandler,
+ ExecutingSensorContext executingSensorContext, ScannerPluginRepository pluginRepository) {
super(project, config, mutableModuleSettings, fs, activeRules, sensorStorage, sonarRuntime, branchConfiguration, writeCache, readCache, analysisCacheEnabled,
- unchangedFilesHandler);
+ unchangedFilesHandler, executingSensorContext, pluginRepository);
this.module = module;
}
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 ec0b9e59f26..7694b77e8b7 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
@@ -54,6 +54,8 @@ import org.sonar.api.config.Configuration;
import org.sonar.api.config.Settings;
import org.sonar.api.scanner.fs.InputProject;
import org.sonar.api.utils.Version;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.scanner.bootstrap.ScannerPluginRepository;
import org.sonar.scanner.cache.AnalysisCacheEnabled;
import org.sonar.scanner.scan.branch.BranchConfiguration;
import org.sonar.scanner.sensor.noop.NoOpNewAnalysisError;
@@ -75,10 +77,15 @@ public class ProjectSensorContext implements SensorContext {
private final WriteCache writeCache;
private final ReadCache readCache;
private final AnalysisCacheEnabled analysisCacheEnabled;
-
- public ProjectSensorContext(DefaultInputProject project, Configuration config, Settings mutableSettings, FileSystem fs, ActiveRules activeRules,
- DefaultSensorStorage sensorStorage, SonarRuntime sonarRuntime, BranchConfiguration branchConfiguration, WriteCache writeCache, ReadCache readCache,
- AnalysisCacheEnabled analysisCacheEnabled, UnchangedFilesHandler unchangedFilesHandler) {
+ private final ExecutingSensorContext executingSensorContext;
+ private final ScannerPluginRepository pluginRepo;
+
+ public ProjectSensorContext(DefaultInputProject project, Configuration config, Settings mutableSettings, FileSystem fs,
+ ActiveRules activeRules,
+ DefaultSensorStorage sensorStorage, SonarRuntime sonarRuntime, BranchConfiguration branchConfiguration,
+ WriteCache writeCache, ReadCache readCache,
+ AnalysisCacheEnabled analysisCacheEnabled, UnchangedFilesHandler unchangedFilesHandler,
+ ExecutingSensorContext executingSensorContext, ScannerPluginRepository pluginRepo) {
this.project = project;
this.config = config;
this.mutableSettings = mutableSettings;
@@ -91,6 +98,8 @@ public class ProjectSensorContext implements SensorContext {
this.analysisCacheEnabled = analysisCacheEnabled;
this.skipUnchangedFiles = branchConfiguration.isPullRequest();
this.unchangedFilesHandler = unchangedFilesHandler;
+ this.executingSensorContext = executingSensorContext;
+ this.pluginRepo = pluginRepo;
}
@Override
@@ -215,8 +224,12 @@ public class ProjectSensorContext implements SensorContext {
}
@Override
- public void addTelemetryProperty(String s, String s1) {
- //NOOP
+ public void addTelemetryProperty(String key, String value) {
+ if (isSonarSourcePlugin()) {
+ this.sensorStorage.storeTelemetry(key, value);
+ } else {
+ throw new IllegalStateException("Telemetry properties can only be added by SonarSource plugins");
+ }
}
@Override
@@ -228,4 +241,13 @@ public class ProjectSensorContext implements SensorContext {
public boolean canSkipUnchangedFiles() {
return this.skipUnchangedFiles;
}
+
+ private boolean isSonarSourcePlugin() {
+ SensorId sensorExecuting = executingSensorContext.getSensorExecuting();
+ if (sensorExecuting != null) {
+ PluginInfo pluginInfo = pluginRepo.getPluginInfo(sensorExecuting.getPluginKey());
+ return "sonarsource".equalsIgnoreCase(pluginInfo.getOrganizationName());
+ }
+ return false;
+ }
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TelemetryPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TelemetryPublisherTest.java
new file mode 100644
index 00000000000..f71e9a8f8c5
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/TelemetryPublisherTest.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.report;
+
+import com.google.common.collect.Lists;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReportWriter;
+import org.sonar.scanner.repository.TelemetryCache;
+
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+class TelemetryPublisherTest {
+ private final ScannerReportWriter writer = mock(ScannerReportWriter.class);
+ private final TelemetryCache telemetryCache = new TelemetryCache();
+ private final TelemetryPublisher underTest = new TelemetryPublisher(telemetryCache);
+
+ @Test
+ void publish_writes_telemetry_to_report() {
+ telemetryCache.put("key1", "value1");
+ telemetryCache.put("key2", "value2");
+
+ underTest.publish(writer);
+
+ List<ScannerReport.TelemetryEntry> expected = Arrays.asList(
+ newTelemetryEntry("key1", "value1"),
+ newTelemetryEntry("key2", "value2"));
+ expectWritten(expected);
+ }
+
+ private void expectWritten(List<ScannerReport.TelemetryEntry> expected) {
+ verify(writer).writeTelemetry(argThat(entries -> {
+ List<ScannerReport.TelemetryEntry> copy = Lists.newArrayList(entries);
+ copy.removeAll(expected);
+ return copy.isEmpty();
+ }));
+ }
+
+ private static ScannerReport.TelemetryEntry newTelemetryEntry(String key, String value) {
+ return ScannerReport.TelemetryEntry.newBuilder()
+ .setKey(key)
+ .setValue(value)
+ .build();
+ }
+}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/TelemetryCacheTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/TelemetryCacheTest.java
new file mode 100644
index 00000000000..cbdb518db7f
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/TelemetryCacheTest.java
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.repository;
+
+import org.junit.jupiter.api.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;
+
+class TelemetryCacheTest {
+
+ TelemetryCache underTest = new TelemetryCache();
+
+ @Test
+ void put_EntryIsAddedToCache() {
+ assertThat(underTest.getAll()).isEmpty();
+
+ underTest.put("key", "value");
+ assertThat(underTest.getAll()).containsOnly(entry("key", "value"));
+ }
+
+ @Test
+ void put_whenKeyIsAlreadyThere_EntryOverridesPreviousValue() {
+ underTest.put("key", "value");
+ underTest.put("key", "newValue");
+ assertThat(underTest.getAll()).containsOnly(entry("key", "newValue"));
+ }
+
+ @Test
+ void put_whenCacheIsAlreadyFull_newEntryIsNotAdded() {
+ for (int i = 0; i < 1000; i++) {
+ underTest.put("key" + i, "value" + i);
+ }
+ underTest.put("key", "value");
+ assertThat(underTest.getAll()).hasSize(1000);
+ assertThat(underTest.getAll()).doesNotContain(entry("key", "value"));
+ }
+
+ @Test
+ void put_whenCacheIsAlreadyFull_newEntryIsAddedIfKeyAlreadyThere() {
+ for (int i = 0; i < 1000; i++) {
+ underTest.put("key" + i, "value" + i);
+ }
+ underTest.put("key1", "newValue");
+ underTest.put("key", "newValue");
+
+ assertThat(underTest.getAll()).hasSize(1000);
+ assertThat(underTest.getAll()).contains(entry("key1", "newValue"));
+ }
+
+ @Test
+ void put_whenKeyIsNull_IAEIsThrown() {
+ assertThatThrownBy(() -> underTest.put(null, "value"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Key of the telemetry entry must not be null");
+ }
+
+ @Test
+ void put_whenValueIsNull_IAEIsThrown() {
+ assertThatThrownBy(() -> underTest.put("key", null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Value of the telemetry entry must not be null");
+ }
+
+}
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 1eeea7ede12..41680ce3a1d 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
@@ -20,14 +20,12 @@
package org.sonar.scanner.sensor;
import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.groups.Tuple;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.InputFile;
@@ -66,34 +64,37 @@ import org.sonar.scanner.protocol.output.ScannerReportReader;
import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonar.scanner.report.ReportPublisher;
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;
-public class DefaultSensorStorageTest {
+class DefaultSensorStorageTest {
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
+ @TempDir
+ public File temp;
private DefaultSensorStorage underTest;
private MapSettings settings;
private IssuePublisher moduleIssues;
private ScannerReportWriter reportWriter;
private ContextPropertiesCache contextPropertiesCache = new ContextPropertiesCache();
+ private TelemetryCache telemetryCache = new TelemetryCache();
private BranchConfiguration branchConfiguration;
private DefaultInputProject project;
private ScannerReportReader reportReader;
private ReportPublisher reportPublisher;
- @Before
- public void prepare() throws Exception {
+ @BeforeEach
+ public void prepare() {
MetricFinder metricFinder = mock(MetricFinder.class);
when(metricFinder.<Integer>findByKey(CoreMetrics.NCLOC_KEY)).thenReturn(CoreMetrics.NCLOC);
when(metricFinder.<String>findByKey(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY)).thenReturn(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION);
@@ -103,8 +104,7 @@ public class DefaultSensorStorageTest {
moduleIssues = mock(IssuePublisher.class);
reportPublisher = mock(ReportPublisher.class);
- final File reportDir = temp.newFolder();
- FileStructure fileStructure = new FileStructure(reportDir);
+ FileStructure fileStructure = new FileStructure(temp);
reportWriter = new ScannerReportWriter(fileStructure);
reportReader = new ScannerReportReader(fileStructure);
when(reportPublisher.getWriter()).thenReturn(reportWriter);
@@ -113,16 +113,16 @@ public class DefaultSensorStorageTest {
branchConfiguration = mock(BranchConfiguration.class);
underTest = new DefaultSensorStorage(metricFinder,
- moduleIssues, settings.asConfig(), reportPublisher, mock(SonarCpdBlockIndex.class), contextPropertiesCache, new ScannerMetrics(), branchConfiguration);
+ moduleIssues, settings.asConfig(), reportPublisher, mock(SonarCpdBlockIndex.class), contextPropertiesCache, telemetryCache, new ScannerMetrics(), branchConfiguration);
project = new DefaultInputProject(ProjectDefinition.create()
.setKey("foo")
- .setBaseDir(temp.newFolder())
- .setWorkDir(temp.newFolder()));
+ .setBaseDir(temp)
+ .setWorkDir(temp));
}
@Test
- public void should_merge_coverage() {
+ void should_merge_coverage() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setLines(5).build();
DefaultCoverage coverage = new DefaultCoverage(underTest);
@@ -144,7 +144,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void shouldFailIfUnknownMetric() {
+ void shouldFailIfUnknownMetric() {
InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build();
assertThatThrownBy(() -> underTest.store(new DefaultMeasure()
@@ -156,7 +156,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void shouldIgnoreMeasuresOnFolders() {
+ void shouldIgnoreMeasuresOnFolders() {
underTest.store(new DefaultMeasure()
.on(new DefaultInputDir("foo", "bar"))
.forMetric(CoreMetrics.LINES)
@@ -166,9 +166,9 @@ public class DefaultSensorStorageTest {
}
@Test
- public void shouldIgnoreMeasuresOnModules() throws IOException {
- ProjectDefinition module = ProjectDefinition.create().setBaseDir(temp.newFolder()).setWorkDir(temp.newFolder());
- ProjectDefinition root = ProjectDefinition.create().addSubProject(module);
+ void shouldIgnoreMeasuresOnModules() {
+ ProjectDefinition module = ProjectDefinition.create().setBaseDir(temp).setWorkDir(temp);
+ ProjectDefinition.create().addSubProject(module);
underTest.store(new DefaultMeasure()
.on(new DefaultInputModule(module))
@@ -179,7 +179,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_save_issue() {
+ void should_save_issue() {
InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build();
DefaultIssue issue = new DefaultIssue(project).at(new DefaultIssueLocation().on(file));
@@ -191,7 +191,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_save_external_issue() {
+ void should_save_external_issue() {
InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build();
DefaultExternalIssue externalIssue = new DefaultExternalIssue(project).at(new DefaultIssueLocation().on(file));
@@ -203,7 +203,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_skip_issue_on_pr_when_file_status_is_SAME() {
+ void should_skip_issue_on_pr_when_file_status_is_SAME() {
InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build();
when(branchConfiguration.isPullRequest()).thenReturn(true);
@@ -214,7 +214,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void has_issues_delegates_to_report_publisher() {
+ void has_issues_delegates_to_report_publisher() {
DefaultInputFile file1 = new TestInputFileBuilder("foo", "src/Foo1.php").setStatus(InputFile.Status.SAME).build();
DefaultInputFile file2 = new TestInputFileBuilder("foo", "src/Foo2.php").setStatus(InputFile.Status.SAME).build();
@@ -224,7 +224,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_save_highlighting() {
+ void should_save_highlighting() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.setContents("// comment").build();
@@ -235,7 +235,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_skip_highlighting_on_pr_when_file_status_is_SAME() {
+ void should_skip_highlighting_on_pr_when_file_status_is_SAME() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.setContents("// comment")
.setStatus(InputFile.Status.SAME).build();
@@ -248,7 +248,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_save_file_measure() {
+ void should_save_file_measure() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.build();
@@ -263,7 +263,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_not_skip_file_measures_on_pull_request_when_file_status_is_SAME() {
+ void should_not_skip_file_measures_on_pull_request_when_file_status_is_SAME() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build();
when(branchConfiguration.isPullRequest()).thenReturn(true);
@@ -278,7 +278,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_skip_significant_code_on_pull_request_when_file_status_is_SAME() {
+ void should_skip_significant_code_on_pull_request_when_file_status_is_SAME() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.setStatus(InputFile.Status.SAME)
.setContents("foo")
@@ -293,7 +293,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_save_significant_code() {
+ void should_save_significant_code() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.setContents("foo")
.build();
@@ -305,9 +305,9 @@ public class DefaultSensorStorageTest {
}
@Test
- public void should_save_project_measure() throws IOException {
+ void should_save_project_measure() {
String projectKey = "myProject";
- DefaultInputModule module = new DefaultInputModule(ProjectDefinition.create().setKey(projectKey).setBaseDir(temp.newFolder()).setWorkDir(temp.newFolder()));
+ DefaultInputModule module = new DefaultInputModule(ProjectDefinition.create().setKey(projectKey).setBaseDir(temp).setWorkDir(temp));
underTest.store(new DefaultMeasure()
.on(module)
@@ -319,45 +319,56 @@ public class DefaultSensorStorageTest {
assertThat(m.getMetricKey()).isEqualTo(CoreMetrics.NCLOC_KEY);
}
- @Test(expected = UnsupportedOperationException.class)
- public void duplicateHighlighting() throws Exception {
+ @Test
+ void duplicateHighlighting() {
InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java")
- .setModuleBaseDir(temp.newFolder().toPath()).build();
+ .setModuleBaseDir(temp.toPath()).build();
DefaultHighlighting h = new DefaultHighlighting(null)
.onFile(inputFile);
underTest.store(h);
- underTest.store(h);
+ assertThrows(UnsupportedOperationException.class, () -> {
+ underTest.store(h);
+ });
}
- @Test(expected = UnsupportedOperationException.class)
- public void duplicateSignificantCode() throws Exception {
+ @Test
+ void duplicateSignificantCode() {
InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java")
- .setModuleBaseDir(temp.newFolder().toPath()).build();
+ .setModuleBaseDir(temp.toPath()).build();
DefaultSignificantCode h = new DefaultSignificantCode(null)
.onFile(inputFile);
underTest.store(h);
- underTest.store(h);
+ assertThrows(UnsupportedOperationException.class, () -> {
+ underTest.store(h);
+ });
}
- @Test(expected = UnsupportedOperationException.class)
- public void duplicateSymbolTable() throws Exception {
+ @Test
+ void duplicateSymbolTable() {
InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java")
- .setModuleBaseDir(temp.newFolder().toPath()).build();
+ .setModuleBaseDir(temp.toPath()).build();
DefaultSymbolTable st = new DefaultSymbolTable(null)
.onFile(inputFile);
underTest.store(st);
- underTest.store(st);
+ assertThrows(UnsupportedOperationException.class, () -> {
+ underTest.store(st);
+ });
}
@Test
- public void shouldStoreContextProperty() {
+ void shouldStoreContextProperty() {
underTest.storeProperty("foo", "bar");
assertThat(contextPropertiesCache.getAll()).containsOnly(entry("foo", "bar"));
}
@Test
- public void store_whenAdhocRuleIsSpecified_shouldWriteAdhocRuleToReport() {
+ void shouldStoreTelemetryEntries() {
+ underTest.storeTelemetry("key", "value");
+ assertThat(telemetryCache.getAll()).containsOnly(entry("key", "value"));
+ }
+ @Test
+ void store_whenAdhocRuleIsSpecified_shouldWriteAdhocRuleToReport() {
underTest.store(new DefaultAdHocRule().ruleId("ruleId").engineId("engineId")
.name("name")
.addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
@@ -383,7 +394,7 @@ public class DefaultSensorStorageTest {
}
@Test
- public void store_whenAdhocRuleIsSpecifiedWithOptionalFieldEmpty_shouldWriteAdhocRuleWithDefaultImpactsToReport() {
+ void store_whenAdhocRuleIsSpecifiedWithOptionalFieldEmpty_shouldWriteAdhocRuleWithDefaultImpactsToReport() {
underTest.store(new DefaultAdHocRule().ruleId("ruleId").engineId("engineId")
.name("name")
.description("description"));
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java
index 94d7a2ce282..4ca7d33d642 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java
@@ -35,6 +35,7 @@ import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.internal.SonarRuntimeImpl;
import org.sonar.api.utils.Version;
+import org.sonar.scanner.bootstrap.ScannerPluginRepository;
import org.sonar.scanner.cache.AnalysisCacheEnabled;
import org.sonar.scanner.cache.ReadCacheImpl;
import org.sonar.scanner.cache.WriteCacheImpl;
@@ -61,12 +62,14 @@ public class ModuleSensorContextTest {
private final SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("5.5"), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY);
private DefaultFileSystem fs;
private ModuleSensorContext underTest;
+ private ExecutingSensorContext executingSensorContext = mock(ExecutingSensorContext.class);
+ private ScannerPluginRepository pluginRepository = mock(ScannerPluginRepository.class);
@Before
public void prepare() throws Exception {
fs = new DefaultFileSystem(temp.newFolder().toPath());
underTest = new ModuleSensorContext(mock(DefaultInputProject.class), mock(InputModule.class), settings.asConfig(), settings, fs, activeRules, sensorStorage, runtime,
- branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler);
+ branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler, executingSensorContext, pluginRepository);
}
@Test
@@ -102,7 +105,7 @@ public class ModuleSensorContextTest {
public void pull_request_can_skip_unchanged_files() {
when(branchConfiguration.isPullRequest()).thenReturn(true);
underTest = new ModuleSensorContext(mock(DefaultInputProject.class), mock(InputModule.class), settings.asConfig(), settings, fs, activeRules, sensorStorage, runtime,
- branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler);
+ branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler, executingSensorContext, pluginRepository);
assertThat(underTest.canSkipUnchangedFiles()).isTrue();
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ProjectSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ProjectSensorContextTest.java
new file mode 100644
index 00000000000..5d25250e8bb
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ProjectSensorContextTest.java
@@ -0,0 +1,92 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.sensor;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputProject;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.Version;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.scanner.bootstrap.ScannerPluginRepository;
+import org.sonar.scanner.cache.AnalysisCacheEnabled;
+import org.sonar.scanner.cache.ReadCacheImpl;
+import org.sonar.scanner.cache.WriteCacheImpl;
+import org.sonar.scanner.scan.branch.BranchConfiguration;
+
+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.when;
+
+class ProjectSensorContextTest {
+
+ private final ActiveRules activeRules = new ActiveRulesBuilder().build();
+ private final MapSettings settings = new MapSettings();
+ private final DefaultSensorStorage sensorStorage = mock(DefaultSensorStorage.class);
+ private final BranchConfiguration branchConfiguration = mock(BranchConfiguration.class);
+ private final WriteCacheImpl writeCache = mock(WriteCacheImpl.class);
+ private final ReadCacheImpl readCache = mock(ReadCacheImpl.class);
+ private final AnalysisCacheEnabled analysisCacheEnabled = mock(AnalysisCacheEnabled.class);
+ private final UnchangedFilesHandler unchangedFilesHandler = mock(UnchangedFilesHandler.class);
+ private final SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("5.5"), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY);
+ private DefaultFileSystem fs = mock(DefaultFileSystem.class);
+ private ExecutingSensorContext executingSensorContext = mock(ExecutingSensorContext.class);
+ private ScannerPluginRepository pluginRepository = mock(ScannerPluginRepository.class);
+
+ private ProjectSensorContext underTest = new ProjectSensorContext(mock(DefaultInputProject.class), settings.asConfig(), settings, fs, activeRules, sensorStorage, runtime,
+ branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler, executingSensorContext, pluginRepository);
+
+ private static final String PLUGIN_KEY = "org.sonarsource.pluginKey";
+
+ @BeforeEach
+ void prepare() {
+ when(executingSensorContext.getSensorExecuting()).thenReturn(new SensorId(PLUGIN_KEY, "sensorName"));
+ }
+
+
+ @Test
+ void addTelemetryProperty_whenTheOrganizationIsSonarSource_mustStoreTheTelemetry() {
+
+ when(pluginRepository.getPluginInfo(PLUGIN_KEY)).thenReturn(new PluginInfo(PLUGIN_KEY).setOrganizationName("sonarsource"));
+
+ underTest.addTelemetryProperty("key", "value");
+
+ //then verify that the defaultStorage is called with the telemetry property once
+ verify(sensorStorage).storeTelemetry("key", "value");
+ }
+
+ @Test
+ void addTelemetryProperty_whenTheOrganizationIsNotSonarSource_mustThrowExcaption() {
+ when(pluginRepository.getPluginInfo(PLUGIN_KEY)).thenReturn(new PluginInfo(PLUGIN_KEY).setOrganizationName("notSonarsource"));
+
+ assertThrows(IllegalStateException.class, () -> underTest.addTelemetryProperty("key", "value"));
+
+ verifyNoInteractions(sensorStorage);
+ }
+}
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 a3fb8835fba..686b7d20066 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
@@ -94,6 +94,10 @@ public class FileStructure {
return new File(dir, "context-props.pb");
}
+ public File telemetryEntries() {
+ return new File(dir, "telemetry-entries.pb");
+ }
+
public File analysisWarnings() {
return new File(dir, "analysis-warnings.pb");
}
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 20e5f30a1f0..ad4720cbb5f 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
@@ -163,6 +163,12 @@ public class ScannerReportWriter {
return file;
}
+ public File writeTelemetry(Iterable<ScannerReport.TelemetryEntry> telemetryEntries) {
+ File file = fileStructure.telemetryEntries();
+ Protobuf.writeStream(telemetryEntries, file, false);
+ 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 e4954383859..b7c1d3d5064 100644
--- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
+++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
@@ -87,6 +87,11 @@ message ContextProperty {
string value = 2;
}
+message TelemetryEntry {
+ string key = 1;
+ string value = 2;
+}
+
message ActiveRule {
string rule_repository = 1;
string rule_key = 2;
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java
index b2787934275..9c7ada548b3 100644
--- a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java
+++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/FileStructureTest.java
@@ -20,6 +20,7 @@
package org.sonar.scanner.protocol.output;
import java.io.File;
+import java.nio.charset.Charset;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
@@ -60,10 +61,10 @@ public class FileStructureTest {
@Test
public void locate_files() throws Exception {
File dir = temp.newFolder();
- FileUtils.write(new File(dir, "metadata.pb"), "metadata content");
- FileUtils.write(new File(dir, "issues-3.pb"), "external issues of component 3");
- FileUtils.write(new File(dir, "external-issues-3.pb"), "issues of component 3");
- FileUtils.write(new File(dir, "component-42.pb"), "details of component 42");
+ FileUtils.write(new File(dir, "metadata.pb"), "metadata content", Charset.defaultCharset());
+ FileUtils.write(new File(dir, "issues-3.pb"), "external issues of component 3", Charset.defaultCharset());
+ FileUtils.write(new File(dir, "external-issues-3.pb"), "issues of component 3", Charset.defaultCharset());
+ FileUtils.write(new File(dir, "component-42.pb"), "details of component 42", Charset.defaultCharset());
FileStructure structure = new FileStructure(dir);
assertThat(structure.metadataFile()).exists().isFile();
@@ -78,9 +79,19 @@ public class FileStructureTest {
public void contextProperties_file() throws Exception {
File dir = temp.newFolder();
File file = new File(dir, "context-props.pb");
- FileUtils.write(file, "content");
+ FileUtils.write(file, "content", Charset.defaultCharset());
FileStructure structure = new FileStructure(dir);
assertThat(structure.contextProperties()).exists().isFile().isEqualTo(file);
}
+
+ @Test
+ public void telemetryFile_hasTheCorrectName() throws Exception {
+ File dir = temp.newFolder();
+ File file = new File(dir, "telemetry-entries.pb");
+ FileUtils.write(file, "content", Charset.defaultCharset());
+
+ FileStructure structure = new FileStructure(dir);
+ assertThat(structure.telemetryEntries()).exists().isFile().isEqualTo(file);
+ }
}
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java
index 368886e2265..4b89c9a3db5 100644
--- a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java
+++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java
@@ -154,7 +154,8 @@ public class ScannerReportWriterTest {
// write data
ScannerReport.Cve cve = ScannerReport.Cve.newBuilder()
.setCveId("CVE-2023-20863")
- .setDescription("In spring framework versions prior to 5.2.24 release+ ,5.3.27+ and 6.0.8+ , it is possible for a user to provide a specially crafted SpEL expression that may cause a denial-of-service (DoS) condition.")
+ .setDescription("In spring framework versions prior to 5.2.24 release+ ,5.3.27+ and 6.0.8+ , it is possible for a user to provide a" +
+ " specially crafted SpEL expression that may cause a denial-of-service (DoS) condition.")
.setCvssScore(6.5f)
.setEpssScore(0.00306f)
.setEpssPercentile(0.70277f)
@@ -366,4 +367,25 @@ public class ScannerReportWriterTest {
assertThat(underTest.hasComponentData(FileStructure.Domain.COVERAGES, 1)).isTrue();
}
+ @Test
+ public void write_telemetry() {
+
+ List<ScannerReport.TelemetryEntry> input = List.of(
+ ScannerReport.TelemetryEntry.newBuilder()
+ .setKey("key")
+ .setValue("value").build(),
+ ScannerReport.TelemetryEntry.newBuilder()
+ .setKey("key2")
+ .setValue("value2").build());
+
+ underTest.writeTelemetry(input);
+
+ try (CloseableIterator<ScannerReport.TelemetryEntry> telemetryIterator =
+ Protobuf.readStream(underTest.getFileStructure().telemetryEntries(), ScannerReport.TelemetryEntry.parser())) {
+
+ assertThat(telemetryIterator).toIterable()
+ .containsExactlyElementsOf(input)
+ .hasSize(input.size());
+ }
+ }
}