From 14780ed4cc64ada8cd286f13fe998a84cc8f69ee Mon Sep 17 00:00:00 2001 From: Alain Kermis Date: Thu, 10 Oct 2024 11:15:50 +0200 Subject: [PATCH] SONAR-23327 Add black box test for analyzer telemetry --- plugins/sonar-xoo-plugin/build.gradle | 1 + .../main/java/org/sonar/xoo/XooPlugin.java | 5 + ...initializedVariableForTelemetrySensor.java | 115 ++++++++++++++++++ .../xoo/rule/telemetry/SensorMetrics.java | 81 ++++++++++++ .../xoo/rule/telemetry/package-info.java | 23 ++++ ...ializedVariableForTelemetrySensorTest.java | 92 ++++++++++++++ 6 files changed, 317 insertions(+) create mode 100644 plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensor.java create mode 100644 plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/SensorMetrics.java create mode 100644 plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/telemetry/package-info.java create mode 100644 plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/telemetry/OneIssuePerUninitializedVariableForTelemetrySensorTest.java 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 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 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)); + } + } + +} -- 2.39.5