aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlain Kermis <alain.kermis@sonarsource.com>2024-10-10 11:15:50 +0200
committersonartech <sonartech@sonarsource.com>2024-10-11 20:02:43 +0000
commit14780ed4cc64ada8cd286f13fe998a84cc8f69ee (patch)
treefaa32d89d050fd0e8203f5289f6045b60206c9a6
parent38a765efec578eadc53393e65f3b6b892ab83bcf (diff)
downloadsonarqube-14780ed4cc64ada8cd286f13fe998a84cc8f69ee.tar.gz
sonarqube-14780ed4cc64ada8cd286f13fe998a84cc8f69ee.zip
SONAR-23327 Add black box test for analyzer telemetry
-rw-r--r--plugins/sonar-xoo-plugin/build.gradle1
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java5
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensor.java115
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/SensorMetrics.java81
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/package-info.java23
-rw-r--r--plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensorTest.java92
6 files changed, 317 insertions, 0 deletions
diff --git a/plugins/sonar-xoo-plugin/build.gradle b/plugins/sonar-xoo-plugin/build.gradle
index fbd8c4d1228..f12ceadcdf8 100644
--- a/plugins/sonar-xoo-plugin/build.gradle
+++ b/plugins/sonar-xoo-plugin/build.gradle
@@ -19,6 +19,7 @@ jar {
manifest {
attributes(
'Plugin-Key': 'xoo',
+ 'Plugin-Organization': 'SonarSource',
'Plugin-Version': project.version,
'Plugin-Class': 'org.sonar.xoo.XooPlugin',
'Plugin-ChildFirstClassLoader': 'false',
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
index 35914a7a95a..ab762def0c4 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
@@ -31,6 +31,7 @@ import org.sonar.xoo.extensions.XooExcludeFileFilter;
import org.sonar.xoo.extensions.XooIssueFilter;
import org.sonar.xoo.extensions.XooPostJob;
import org.sonar.xoo.extensions.XooProjectBuilder;
+import org.sonar.xoo.rule.telemetry.SensorMetrics;
import org.sonar.xoo.global.DeprecatedGlobalSensor;
import org.sonar.xoo.global.GlobalProjectSensor;
import org.sonar.xoo.lang.CpdTokenizerSensor;
@@ -85,6 +86,7 @@ import org.sonar.xoo.rule.XooSonarWayProfile;
import org.sonar.xoo.rule.hotspot.HotspotWithContextsSensor;
import org.sonar.xoo.rule.hotspot.HotspotWithSingleContextSensor;
import org.sonar.xoo.rule.hotspot.HotspotWithoutContextSensor;
+import org.sonar.xoo.rule.telemetry.OneIssuePerUninitializedVariableForTelemetrySensor;
import org.sonar.xoo.rule.variant.HotspotWithCodeVariantsSensor;
import org.sonar.xoo.rule.variant.IssueWithCodeVariantsSensor;
import org.sonar.xoo.scm.XooBlameCommand;
@@ -174,6 +176,8 @@ public class XooPlugin implements Plugin {
OnePredefinedRuleExternalIssuePerLineSensor.class,
OnePredefinedAndAdHocRuleExternalIssuePerLineSensor.class,
+ OneIssuePerUninitializedVariableForTelemetrySensor.class,
+
CreateIssueByInternalKeySensor.class,
MultilineIssuesSensor.class,
MultilineHotspotSensor.class,
@@ -184,6 +188,7 @@ public class XooPlugin implements Plugin {
OneVulnerabilityIssuePerProjectSensor.class,
OneVulnerabilityPerSecurityStandardSensor.class,
+ SensorMetrics.class,
DeprecatedGlobalSensor.class,
GlobalProjectSensor.class,
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensor.java
new file mode 100644
index 00000000000..1569e44bc93
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensor.java
@@ -0,0 +1,115 @@
+/*
+ * 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.xoo.rule.telemetry;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.xoo.Xoo;
+
+public class OneIssuePerUninitializedVariableForTelemetrySensor implements Sensor {
+
+ protected static final String RULE_KEY = "OneIssuePerUninitializedVariableForTelemetry";
+ protected static final String ENGINE_ID = "XooEngine";
+ protected static final String NAME = "One External Issue Per Uninitialized Variable Rule For Telemetry";
+ protected static final String ACTIVATE = "sonar.uninitializedVariableForTelemetrySensor.activate";
+ protected static final Long EFFORT_MINUTES = 5L;
+
+ private final SensorMetrics sensorMetrics;
+
+ public OneIssuePerUninitializedVariableForTelemetrySensor(SensorMetrics sensorMetrics) {
+ this.sensorMetrics = sensorMetrics;
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name(NAME)
+ .onlyOnLanguages(Xoo.KEY)
+ .onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE).orElse(false));
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ analyse(context);
+
+ sensorMetrics.finalizeAndReportTelemetry();
+ }
+
+ private void analyse(SensorContext context) {
+ FileSystem fs = context.fileSystem();
+ FilePredicates p = fs.predicates();
+ for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(InputFile.Type.MAIN)))) {
+ createIssues(file, context);
+ }
+ }
+
+ private void createIssues(InputFile file, SensorContext context) {
+ try {
+ String code = IOUtils.toString(file.inputStream(), file.charset());
+
+ List<String> lines = IOUtils.readLines(file.inputStream(), file.charset());
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i).trim();
+ if (isVariableDeclaration(line) && !isVariableInitialized(line, code)) {
+ NewExternalIssue newIssue = context.newExternalIssue();
+ newIssue
+ .engineId(ENGINE_ID)
+ .ruleId(RULE_KEY)
+ .at(newIssue.newLocation()
+ .on(file)
+ .at(file.selectLine(i + 1))
+ .message("This issue is generated on line containing uninitialized variable"))
+ .addImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)
+ .remediationEffortMinutes(EFFORT_MINUTES)
+ .save();
+
+ // Increment the number of issues found and add the remediation effort in minutes to telemetry
+ sensorMetrics.incrementUninitializedVariableRuleIssueCounter();
+ sensorMetrics.addUninitializedVariableRuleEffortInMinutes(EFFORT_MINUTES);
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to process " + file, e);
+ }
+ }
+
+ private boolean isVariableDeclaration(String line) {
+ return line.matches("[a-zA-Z][a-zA-Z0-9_]*\\s*;");
+ }
+
+ private boolean isVariableInitialized(String declarationLine, String code) {
+ String variableName = getVariableNameFromDeclaration(declarationLine);
+ return code.contains(variableName + " =");
+ }
+
+ private String getVariableNameFromDeclaration(String line) {
+ return line.replace(";", "").trim();
+ }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/SensorMetrics.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/SensorMetrics.java
new file mode 100644
index 00000000000..8adf1748bf3
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/SensorMetrics.java
@@ -0,0 +1,81 @@
+/*
+ * 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.xoo.rule.telemetry;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.scanner.ScannerSide;
+import org.sonar.api.utils.Version;
+
+@ScannerSide
+public class SensorMetrics {
+
+ private final SensorTelemetry sensorTelemetry;
+
+ // Metrics
+ private final AtomicInteger uninitializedVariableRuleIssues = new AtomicInteger(0);
+ private final AtomicLong uninitializedVariableRuleEffortInMinutes = new AtomicLong(0L);
+
+ SensorMetrics(SensorContext context) {
+ this.sensorTelemetry = new SensorTelemetry(context);
+ }
+
+ protected void incrementUninitializedVariableRuleIssueCounter() {
+ uninitializedVariableRuleIssues.incrementAndGet();
+ }
+
+ protected void addUninitializedVariableRuleEffortInMinutes(Long value) {
+ uninitializedVariableRuleEffortInMinutes.addAndGet(value);
+ }
+
+ protected void finalizeAndReportTelemetry() {
+ sensorTelemetry
+ .addTelemetry("uninitializedVariableRuleIssues", String.valueOf(uninitializedVariableRuleIssues.get()))
+ .addTelemetry("uninitializedVariableRuleEffortInMinutes", String.valueOf(uninitializedVariableRuleEffortInMinutes.get()))
+ .reportTelemetry();
+ }
+
+ private static class SensorTelemetry {
+ private final SensorContext context;
+ private static final String KEY_PREFIX = "xoo.";
+ private final Map<String, String> telemetry = new HashMap<>();
+
+ SensorTelemetry(SensorContext context) {
+ this.context = context;
+ }
+
+ SensorTelemetry addTelemetry(String key, String value) {
+ key = KEY_PREFIX + key;
+ if (telemetry.containsKey(key)) {
+ throw new IllegalArgumentException("Telemetry key is reported more than once: " + key);
+ }
+ telemetry.put(key, value);
+ return this;
+ }
+
+ void reportTelemetry() {
+ telemetry.forEach(context::addTelemetryProperty);
+ }
+ }
+
+}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/package-info.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/package-info.java
new file mode 100644
index 00000000000..50b3d282c42
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.xoo.rule.telemetry;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensorTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensorTest.java
new file mode 100644
index 00000000000..389394b5bcc
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensorTest.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.xoo.rule.telemetry;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+import org.sonar.api.batch.sensor.issue.ExternalIssue;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.xoo.Xoo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class OneIssuePerUninitializedVariableForTelemetrySensorTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private final OneIssuePerUninitializedVariableForTelemetrySensor sensor = new OneIssuePerUninitializedVariableForTelemetrySensor(mock(SensorMetrics.class));
+
+ @Test
+ public void testDescriptor() {
+ DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor();
+ sensor.describe(descriptor);
+ Configuration configWithProperty = new MapSettings().setProperty(OneIssuePerUninitializedVariableForTelemetrySensor.ACTIVATE, "true").asConfig();
+ Configuration configWithoutProperty = new MapSettings().asConfig();
+
+ assertThat(descriptor.languages()).containsOnly(Xoo.KEY);
+ assertThat(descriptor.name()).isEqualTo(OneIssuePerUninitializedVariableForTelemetrySensor.NAME);
+ assertThat(descriptor.configurationPredicate().test(configWithoutProperty)).isFalse();
+ assertThat(descriptor.configurationPredicate().test(configWithProperty)).isTrue();
+ }
+
+ @Test
+ public void testRule() throws IOException {
+ String code = """
+ package sample;
+ public class Sample {
+ a;
+ b;
+ c;
+
+ public Sample() {
+ a = 4;
+ }
+ }
+ """;
+ DefaultInputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.xoo")
+ .setLanguage(Xoo.KEY)
+ .setContents(code)
+ .setCharset(Charset.defaultCharset())
+ .build();
+
+ SensorContextTester context = SensorContextTester.create(temp.newFolder());
+ context.fileSystem().add(inputFile);
+ sensor.execute(context);
+
+ assertThat(context.allExternalIssues()).hasSize(2);
+ for (ExternalIssue issue : context.allExternalIssues()) {
+ assertThat(issue.remediationEffort()).isEqualTo(OneIssuePerUninitializedVariableForTelemetrySensor.EFFORT_MINUTES);
+ assertThat(issue.engineId()).isEqualTo(OneIssuePerUninitializedVariableForTelemetrySensor.ENGINE_ID);
+ assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("external_XooEngine", OneIssuePerUninitializedVariableForTelemetrySensor.RULE_KEY));
+ }
+ }
+
+}