Skip to content

Commit

Permalink
SONARPY-2457 Collect data for the Python version
Browse files Browse the repository at this point in the history
  • Loading branch information
ghislainpiot committed Dec 16, 2024
1 parent bf32141 commit 2e3fc3d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,32 @@ public final class PythonSensor implements Sensor {
static final String UNSET_VERSION_WARNING = "Your code is analyzed as compatible with all Python 3 versions by default." +
" You can get a more precise analysis by setting the exact Python version in your configuration via the parameter \"sonar.python.version\"";

private final SensorTelemetryStorage sensorTelemetryStorage;

/**
* Constructor to be used by pico if neither PythonCustomRuleRepository nor PythonIndexer are to be found and injected.
*/
public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory,
NoSonarFilter noSonarFilter, AnalysisWarningsWrapper analysisWarnings) {
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, null, null, analysisWarnings);
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, null, null, analysisWarnings, new SensorTelemetryStorage());
}

public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
PythonCustomRuleRepository[] customRuleRepositories, AnalysisWarningsWrapper analysisWarnings) {
this(fileLinesContextFactory, checkFactory, noSonarFilter, customRuleRepositories, null, null, analysisWarnings);
this(fileLinesContextFactory, checkFactory, noSonarFilter, customRuleRepositories, null, null, analysisWarnings, new SensorTelemetryStorage());
}

public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
PythonIndexer indexer, SonarLintCache sonarLintCache, AnalysisWarningsWrapper analysisWarnings) {
// ^^ This constructor implicitly assumes that a PythonIndexer and a SonarLintCache are always available at the same time.
// In practice, this is currently the case, since both are provided by PythonPlugin under the same conditions.
// See also PythonPlugin::SonarLintPluginAPIManager::addSonarlintPythonIndexer.
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, indexer, sonarLintCache, analysisWarnings);
this(fileLinesContextFactory, checkFactory, noSonarFilter, null, indexer, sonarLintCache, analysisWarnings, new SensorTelemetryStorage());
}

public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter noSonarFilter,
@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer,
@Nullable SonarLintCache sonarLintCache, AnalysisWarningsWrapper analysisWarnings) {
@Nullable SonarLintCache sonarLintCache, AnalysisWarningsWrapper analysisWarnings, SensorTelemetryStorage sensorTelemetryStorage) {
this.checks = new PythonChecks(checkFactory)
.addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks())
.addCustomChecks(customRuleRepositories);
Expand All @@ -100,6 +102,12 @@ public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactor
this.indexer = indexer;
this.sonarLintCache = sonarLintCache;
this.analysisWarnings = analysisWarnings;
this.sensorTelemetryStorage = sensorTelemetryStorage;
}

public PythonSensor(FileLinesContextFactory fileLinesContextFactory, CheckFactory checkFactory, NoSonarFilter mock, PythonCustomRuleRepository[] customRuleRepositories,
AnalysisWarningsWrapper analysisWarnings, SensorTelemetryStorage sensorTelemetryStorage) {
this(fileLinesContextFactory, checkFactory, mock, customRuleRepositories, null, null, analysisWarnings, sensorTelemetryStorage);
}

@Override
Expand All @@ -121,15 +129,25 @@ public void execute(SensorContext context) {
if (pythonVersionParameter.length != 0){
ProjectPythonVersion.setCurrentVersions(PythonVersionUtils.fromStringArray(pythonVersionParameter));
}
setTelemetry(context, pythonVersionParameter);
CacheContext cacheContext = CacheContextImpl.of(context);
PythonIndexer pythonIndexer = this.indexer != null ? this.indexer : new SonarQubePythonIndexer(pythonFiles, cacheContext, context);
pythonIndexer.setSonarLintCache(sonarLintCache);
TypeShed.setProjectLevelSymbolTable(pythonIndexer.projectLevelSymbolTable());
PythonScanner scanner = new PythonScanner(context, checks, fileLinesContextFactory, noSonarFilter, PythonParser.create(), pythonIndexer);
scanner.execute(pythonFiles, context);
sensorTelemetryStorage.send(context);
durationReport.stop();
}

private void setTelemetry(SensorContext context, String[] pythonVersionParameter) {
var isVersionSet = pythonVersionParameter.length != 0 || context.runtime().getProduct() == SonarProduct.SONARLINT;
if (pythonVersionParameter.length != 0) {
sensorTelemetryStorage.updateMetric(TelemetryMetricKey.PYTHON_VERSION_KEY, String.join(",", pythonVersionParameter));
}
sensorTelemetryStorage.updateMetric(TelemetryMetricKey.PYTHON_VERSION_SET_KEY, isVersionSet ? "1" : "0");
}

private static List<PythonInputFile> getInputFiles(SensorContext context) {
FilePredicates p = context.fileSystem().predicates();
Iterable<InputFile> it = context.fileSystem().inputFiles(p.and(p.hasLanguage(Python.KEY)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public enum TelemetryMetricKey {
NOTEBOOK_PRESENT_KEY("python.notebook.present"),
NOTEBOOK_TOTAL_KEY("python.notebook.total"),
NOTEBOOK_RECOGNITION_ERROR_KEY("python.notebook.recognition_error"),
NOTEBOOK_EXCEPTION_KEY("python.notebook.exceptions");
NOTEBOOK_EXCEPTION_KEY("python.notebook.exceptions"),
PYTHON_VERSION_SET_KEY("python.version.set"),
PYTHON_VERSION_KEY("python.version");

private final String key;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -1408,6 +1409,40 @@ void test_scanner_isNotebook() {
assertThat(PythonScanner.isNotebook(notebookPythonFile)).isTrue();
}

@Test
void send_telemetry_with_version() {
activeRules = new ActiveRulesBuilder()
.addRule(new NewActiveRule.Builder()
.setRuleKey(RuleKey.of(CheckList.REPOSITORY_KEY, "S930"))
.build())
.build();

context.setSettings(new MapSettings().setProperty("sonar.python.version", "3.10,3.13"));

SensorTelemetryStorage sensorTelemetryStorage = spy(new SensorTelemetryStorage());
PythonSensor sensor = sensor(sensorTelemetryStorage);
sensor.execute(context);
verify(sensorTelemetryStorage, times(1)).send(context);
assertThat(sensorTelemetryStorage.data()).containsExactlyInAnyOrderEntriesOf(Map.of(TelemetryMetricKey.PYTHON_VERSION_KEY, "3.10,3.13",
TelemetryMetricKey.PYTHON_VERSION_SET_KEY, "1"));
}

@Test
void send_telemetry_no_version() {
activeRules = new ActiveRulesBuilder()
.addRule(new NewActiveRule.Builder()
.setRuleKey(RuleKey.of(CheckList.REPOSITORY_KEY, "S930"))
.build())
.build();

SensorTelemetryStorage sensorTelemetryStorage = spy(new SensorTelemetryStorage());
PythonSensor sensor = sensor(sensorTelemetryStorage);
sensor.execute(context);
verify(sensorTelemetryStorage, times(1)).send(context);
assertThat(sensorTelemetryStorage.data()).containsExactlyInAnyOrderEntriesOf(Map.of(TelemetryMetricKey.PYTHON_VERSION_SET_KEY, "0"));

}

private com.sonar.sslr.api.Token passToken(URI uri) {
return com.sonar.sslr.api.Token.builder()
.setType(PythonKeyword.PASS)
Expand All @@ -1422,7 +1457,16 @@ private PythonSensor sensor() {
return sensor(CUSTOM_RULES, null, analysisWarning);
}

private PythonSensor sensor(SensorTelemetryStorage sensorTelemetryStorage) {
return sensor(CUSTOM_RULES, null, analysisWarning, sensorTelemetryStorage);
}

private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, AnalysisWarningsWrapper analysisWarnings) {
return sensor(customRuleRepositories, indexer, analysisWarnings, new SensorTelemetryStorage());
}

private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRepositories, @Nullable PythonIndexer indexer, AnalysisWarningsWrapper analysisWarnings,
SensorTelemetryStorage sensorTelemetryStorage) {
FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class);
FileLinesContext fileLinesContext = mock(FileLinesContext.class);
when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext);
Expand All @@ -1431,12 +1475,13 @@ private PythonSensor sensor(@Nullable PythonCustomRuleRepository[] customRuleRep
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), analysisWarnings);
}
if (indexer == null) {
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, analysisWarnings);
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, analysisWarnings, sensorTelemetryStorage);
}
if (customRuleRepositories == null) {
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), indexer, new SonarLintCache(), analysisWarnings);
}
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, indexer, new SonarLintCache(), analysisWarnings);
return new PythonSensor(fileLinesContextFactory, checkFactory, mock(NoSonarFilter.class), customRuleRepositories, indexer, new SonarLintCache(), analysisWarnings,
sensorTelemetryStorage);
}

private SonarLintPythonIndexer pythonIndexer(List<PythonInputFile> files) {
Expand Down

0 comments on commit 2e3fc3d

Please sign in to comment.