]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23327 Add black box test for analyzer telemetry
authorAlain Kermis <alain.kermis@sonarsource.com>
Thu, 10 Oct 2024 09:15:50 +0000 (11:15 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 11 Oct 2024 20:02:43 +0000 (20:02 +0000)
plugins/sonar-xoo-plugin/build.gradle
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensor.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/SensorMetrics.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/package-info.java [new file with mode: 0644]
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensorTest.java [new file with mode: 0644]

index fbd8c4d1228fe43279b4aa9ffc25672027c85bb9..f12ceadcdf859e85bfd975d2c922de0d826ed059 100644 (file)
@@ -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',
index 35914a7a95a6926cad5f87eb919455ad54b6d286..ab762def0c40292362ac7a002798fdead4ac6a8c 100644 (file)
@@ -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 (file)
index 0000000..1569e44
--- /dev/null
@@ -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 (file)
index 0000000..8adf174
--- /dev/null
@@ -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 (file)
index 0000000..50b3d28
--- /dev/null
@@ -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 (file)
index 0000000..389394b
--- /dev/null
@@ -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));
+    }
+  }
+
+}