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
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);
}
@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) {
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;
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;
// context
ContextPropertiesCache.class,
+ TelemetryCache.class,
MutableProjectSettings.class,
SonarGlobalPropertiesFilter.class,
ActiveRulesPublisher.class,
ComponentsPublisher.class,
ContextPropertiesPublisher.class,
+ TelemetryPublisher.class,
AnalysisCachePublisher.class,
TestExecutionPublisher.class,
SourcePublisher.class,
--- /dev/null
+/*
+ * 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());
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
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;
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;
}
}
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;
}
}
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;
}
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())));
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));
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) {
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;
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;
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;
}
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;
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;
this.analysisCacheEnabled = analysisCacheEnabled;
this.skipUnchangedFiles = branchConfiguration.isPullRequest();
this.unchangedFilesHandler = unchangedFilesHandler;
+ this.executingSensorContext = executingSensorContext;
+ this.pluginRepo = pluginRepo;
}
@Override
}
@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
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;
+ }
}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+
+}
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;
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);
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);
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);
}
@Test
- public void shouldFailIfUnknownMetric() {
+ void shouldFailIfUnknownMetric() {
InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build();
assertThatThrownBy(() -> underTest.store(new DefaultMeasure()
}
@Test
- public void shouldIgnoreMeasuresOnFolders() {
+ void shouldIgnoreMeasuresOnFolders() {
underTest.store(new DefaultMeasure()
.on(new DefaultInputDir("foo", "bar"))
.forMetric(CoreMetrics.LINES)
}
@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))
}
@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));
}
@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));
}
@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);
}
@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();
}
@Test
- public void should_save_highlighting() {
+ void should_save_highlighting() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.setContents("// comment").build();
}
@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();
}
@Test
- public void should_save_file_measure() {
+ void should_save_file_measure() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.build();
}
@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);
}
@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")
}
@Test
- public void should_save_significant_code() {
+ void should_save_significant_code() {
DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php")
.setContents("foo")
.build();
}
@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)
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)
}
@Test
- public void store_whenAdhocRuleIsSpecifiedWithOptionalFieldEmpty_shouldWriteAdhocRuleWithDefaultImpactsToReport() {
+ void store_whenAdhocRuleIsSpecifiedWithOptionalFieldEmpty_shouldWriteAdhocRuleWithDefaultImpactsToReport() {
underTest.store(new DefaultAdHocRule().ruleId("ruleId").engineId("engineId")
.name("name")
.description("description"));
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;
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
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();
}
--- /dev/null
+/*
+ * 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);
+ }
+}
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");
}
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);
}
string value = 2;
}
+message TelemetryEntry {
+ string key = 1;
+ string value = 2;
+}
+
message ActiveRule {
string rule_repository = 1;
string rule_key = 2;
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;
@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();
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);
+ }
}
// 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)
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());
+ }
+ }
}