Skip to content

Commit

Permalink
NO-JIRA Add tests to verify Java SE rules execution
Browse files Browse the repository at this point in the history
  • Loading branch information
damien-urruty-sonarsource committed Jan 28, 2025
1 parent 270499d commit 2f16d03
Show file tree
Hide file tree
Showing 14 changed files with 297 additions and 118 deletions.
22 changes: 20 additions & 2 deletions medium-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
<artifactItem>
<groupId>org.sonarsource.java</groupId>
<artifactId>sonar-java-plugin</artifactId>
<version>7.16.0.30901</version>
<version>8.8.0.37665</version>
<type>jar</type>
</artifactItem>
<artifactItem>
Expand All @@ -123,7 +123,7 @@
<artifactItem>
<groupId>org.sonarsource.python</groupId>
<artifactId>sonar-python-plugin</artifactId>
<version>4.1.0.11333</version>
<version>4.26.0.19456</version>
<type>jar</type>
</artifactItem>
<artifactItem>
Expand Down Expand Up @@ -201,6 +201,24 @@
<version>6.41.2.69583</version>
<type>jar</type>
</artifactItem>
<artifactItem>
<groupId>org.sonarsource.java</groupId>
<artifactId>sonar-java-symbolic-execution-plugin</artifactId>
<version>8.8.0.37665</version>
<type>jar</type>
</artifactItem>
<artifactItem>
<groupId>com.sonarsource.dbd</groupId>
<artifactId>sonar-dbd-plugin</artifactId>
<version>1.36.1.13250</version>
<type>jar</type>
</artifactItem>
<artifactItem>
<groupId>com.sonarsource.dbd</groupId>
<artifactId>sonar-dbd-java-frontend-plugin</artifactId>
<version>1.36.1.13250</version>
<type>jar</type>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/plugins</outputDirectory>
<overWriteReleases>false</overWriteReleases>
Expand Down
100 changes: 97 additions & 3 deletions medium-tests/src/test/java/mediumtest/ConnectedIssueMediumTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,32 @@
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogTester;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.analysis.AnalyzeFilesAndTrackParams;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.GetEffectiveIssueDetailsParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;
import org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute;
import org.sonarsource.sonarlint.core.rpc.protocol.common.ClientFileDto;
import org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity;
import org.sonarsource.sonarlint.core.rpc.protocol.common.Language;
import org.sonarsource.sonarlint.core.rpc.protocol.common.TextRangeDto;
import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;
import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;
import utils.TestPlugin;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.sonarsource.sonarlint.core.rpc.protocol.common.Language.JAVA;
import static utils.AnalysisUtils.analyzeFileAndGetIssues;
import static utils.AnalysisUtils.createFile;

class ConnectedIssueMediumTests {
@RegisterExtension
private static final SonarLintLogTester logTester = new SonarLintLogTester();
private static final String CONFIG_SCOPE_ID = "configScopeId";
private static final String CONNECTION_ID = "connectionId";
// commercial plugins might not be available
// (if you pass -Dcommercial to maven, a profile will be activated that downloads the commercial plugins)
private static final boolean COMMERCIAL_ENABLED = System.getProperty("commercial") != null;

@SonarLintTest
void simpleJavaBound(SonarLintTestHarness harness, @TempDir Path baseDir) {
Expand Down Expand Up @@ -102,9 +110,95 @@ public void foo() {
assertThat(issues).extracting("ruleKey", "severityMode.right.cleanCodeAttribute")
.usingRecursiveFieldByFieldElementComparator()
.containsOnly(
tuple("java:S106", CleanCodeAttribute.CONVENTIONAL),
tuple("java:S1220", CleanCodeAttribute.CONVENTIONAL),
tuple("java:S1481", CleanCodeAttribute.CONVENTIONAL));
tuple("java:S106", CleanCodeAttribute.MODULAR),
tuple("java:S1220", CleanCodeAttribute.MODULAR),
tuple("java:S1481", CleanCodeAttribute.CLEAR));
}

@SonarLintTest
void simpleJavaSymbolicEngineBound(SonarLintTestHarness harness, @TempDir Path baseDir) {
assumeTrue(COMMERCIAL_ENABLED);
var inputFile = createFile(baseDir, "Foo.java",
"""
public class Foo {
public void foo() {
boolean a = true;
if (a) {
System.out.println( "Hello World!" );
}
}
}""");

var client = harness.newFakeClient()
.withInitialFs(CONFIG_SCOPE_ID, List.of(
new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))
.build();
var server = harness.newFakeSonarQubeServer()
.withQualityProfile("qpKey", qualityProfile -> qualityProfile.withLanguage("java"))
.withProject("projectKey")
.start();
var backend = harness.newBackend()
.withSonarQubeConnection("connectionId", server, storage -> storage
.withPlugins(TestPlugin.JAVA, TestPlugin.JAVA_SE)
.withProject("projectKey", project -> project.withRuleSet("java", ruleSet -> ruleSet
.withActiveRule("java:S2589", "BLOCKER")
.withActiveRule("java:S106", "BLOCKER"))
.withMainBranch("main")))
.withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, "projectKey")
.withEnabledLanguageInStandaloneMode(Language.JAVA)
.start(client);

var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIG_SCOPE_ID);

assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)
.usingRecursiveFieldByFieldElementComparator()
.contains(
tuple("java:S2589", new TextRangeDto(4, 8, 4, 9)),
tuple("java:S106", new TextRangeDto(5, 6, 5, 16)));
}

@SonarLintTest
void simpleJavaSymbolicEngineBoundWithDbd(SonarLintTestHarness harness, @TempDir Path baseDir) {
assumeTrue(COMMERCIAL_ENABLED);
var inputFile = createFile(baseDir, "Foo.java",
"""
public class Foo {
public void foo() {
boolean a = true;
if (a) {
System.out.println( "Hello World!" );
}
}
}""");

var client = harness.newFakeClient()
.withInitialFs(CONFIG_SCOPE_ID, List.of(
new ClientFileDto(inputFile.toUri(), baseDir.relativize(inputFile), CONFIG_SCOPE_ID, false, null, inputFile, null, null, true)))
.build();
var server = harness.newFakeSonarQubeServer()
.withQualityProfile("qpKey", qualityProfile -> qualityProfile.withLanguage("java"))
.withProject("projectKey")
.start();
var backend = harness.newBackend()
.withDataflowBugDetectionEnabled()
.withSonarQubeConnection("connectionId", server, storage -> storage
.withPlugins(TestPlugin.PYTHON, TestPlugin.JAVA, TestPlugin.JAVA_SE, TestPlugin.DBD, TestPlugin.DBD_JAVA)
.withProject("projectKey", project -> project.withRuleSet("java", ruleSet -> ruleSet
.withActiveRule("java:S2589", "BLOCKER")
.withActiveRule("java:S106", "BLOCKER"))
.withMainBranch("main")))
.withBoundConfigScope(CONFIG_SCOPE_ID, CONNECTION_ID, "projectKey")
.withEnabledLanguageInStandaloneMode(Language.JAVA)
.withEnabledLanguageInStandaloneMode(Language.PYTHON)
.start(client);

var issues = analyzeFileAndGetIssues(inputFile.toUri(), client, backend, CONFIG_SCOPE_ID);

assertThat(issues).extracting(RaisedIssueDto::getRuleKey, RaisedIssueDto::getTextRange)
.usingRecursiveFieldByFieldElementComparator()
.contains(
tuple("java:S2589", new TextRangeDto(4, 8, 4, 9)),
tuple("java:S106", new TextRangeDto(5, 6, 5, 16)));
}

@SonarLintTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute.CONVENTIONAL;
import static org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute.FORMATTED;
import static org.sonarsource.sonarlint.core.rpc.protocol.common.CleanCodeAttribute.MODULAR;
import static org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.LOW;
import static org.sonarsource.sonarlint.core.rpc.protocol.common.ImpactSeverity.MEDIUM;
import static org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity.BLOCKER;
Expand All @@ -72,15 +74,15 @@ void it_should_return_embedded_rule_when_project_is_not_bound(SonarLintTestHarne

assertThat(details)
.extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getLeft().getHtmlContent())
.containsExactly("python:S139", "Comments should not be located at the end of lines of code", CONVENTIONAL, PYTHON, LOW,
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())
.containsExactly("python:S139", "Comments should not be located at the end of lines of code", FORMATTED, PYTHON, LOW,
PYTHON_S139_DESCRIPTION);
assertThat(details.getParams())
.extracting(EffectiveRuleParamDto::getName, EffectiveRuleParamDto::getDescription, EffectiveRuleParamDto::getValue, EffectiveRuleParamDto::getDefaultValue)
.containsExactly(tuple("legalTrailingCommentPattern",
"Pattern for text of trailing comments that are allowed. By default, Mypy and Black pragma comments as well as comments containing only one word.",
"^#\\s*+([^\\s]++|fmt.*|type.*)$",
"^#\\s*+([^\\s]++|fmt.*|type.*)$"));
"^#\\s*+([^\\s]++|fmt.*|type.*|noqa.*)$",
"^#\\s*+([^\\s]++|fmt.*|type.*|noqa.*)$"));
}

@SonarLintTest
Expand All @@ -98,7 +100,7 @@ void it_should_consider_standalone_rule_config_for_effective_parameter_values(So
.containsExactly(tuple("legalTrailingCommentPattern",
"Pattern for text of trailing comments that are allowed. By default, Mypy and Black pragma comments as well as comments containing only one word.",
"initialValue",
"^#\\s*+([^\\s]++|fmt.*|type.*)$"));
"^#\\s*+([^\\s]++|fmt.*|type.*|noqa.*)$"));

backend.getRulesService().updateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(Map.of("python:S139",
new StandaloneRuleConfigDto(true, Map.of("legalTrailingCommentPattern", "updatedValue")))));
Expand All @@ -110,7 +112,7 @@ void it_should_consider_standalone_rule_config_for_effective_parameter_values(So
.containsExactly(tuple("legalTrailingCommentPattern",
"Pattern for text of trailing comments that are allowed. By default, Mypy and Black pragma comments as well as comments containing only one word.",
"updatedValue",
"^#\\s*+([^\\s]++|fmt.*|type.*)$"));
"^#\\s*+([^\\s]++|fmt.*|type.*|noqa.*)$"));
}

@SonarLintTest
Expand Down Expand Up @@ -140,8 +142,8 @@ void it_should_return_rule_loaded_from_server_plugin_when_project_is_bound_and_p

assertThat(details)
.extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getLeft().getHtmlContent())
.containsExactly("java:S106", "Standard outputs should not be used directly to log anything", CONVENTIONAL, JAVA, MEDIUM,
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())
.containsExactly("java:S106", "Standard outputs should not be used directly to log anything", MODULAR, JAVA, MEDIUM,
JAVA_S106_DESCRIPTION);
assertThat(details.getParams()).isEmpty();
}
Expand All @@ -156,16 +158,16 @@ void it_should_merge_rule_from_storage_and_server_when_project_is_bound(SonarLin
.withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)
.start();
mockWebServerExtension.addProtobufResponse("/api/rules/show.protobuf?key=python:S139", Rules.ShowResponse.newBuilder()
.setRule(Rules.Rule.newBuilder().setName("newName").setSeverity("INFO").setType(Common.RuleType.BUG).setLang("py").setHtmlNote("extendedDesc").build())
.setRule(Rules.Rule.newBuilder().setName("newName").setSeverity("INFO").setType(Common.RuleType.BUG).setLang("py").build())
.build());

var details = getEffectiveRuleDetails(backend, "scopeId", "python:S139");

assertThat(details)
.extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getLeft().getHtmlContent())
.containsExactly("python:S139", "Comments should not be located at the end of lines of code", CONVENTIONAL, PYTHON, LOW,
PYTHON_S139_DESCRIPTION + "extendedDesc");
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())
.containsExactly("python:S139", "Comments should not be located at the end of lines of code", FORMATTED, PYTHON, LOW,
PYTHON_S139_DESCRIPTION);
assertThat(details.getParams()).isEmpty();
}

Expand All @@ -180,16 +182,16 @@ void it_should_merge_rule_from_storage_and_server_when_parent_project_is_bound(S
.withConnectedEmbeddedPluginAndEnabledLanguage(TestPlugin.PYTHON)
.start();
mockWebServerExtension.addProtobufResponse("/api/rules/show.protobuf?key=python:S139", Rules.ShowResponse.newBuilder()
.setRule(Rules.Rule.newBuilder().setName("newName").setSeverity("INFO").setType(Common.RuleType.BUG).setLang("py").setHtmlNote("extendedDesc").build())
.setRule(Rules.Rule.newBuilder().setName("newName").setSeverity("INFO").setType(Common.RuleType.BUG).setLang("py").build())
.build());

var details = getEffectiveRuleDetails(backend, "childScopeId", "python:S139");

assertThat(details)
.extracting(EffectiveRuleDetailsDto::getKey, EffectiveRuleDetailsDto::getName, EffectiveRuleDetailsDto::getCleanCodeAttribute, EffectiveRuleDetailsDto::getLanguage,
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getLeft().getHtmlContent())
.containsExactly("python:S139", "Comments should not be located at the end of lines of code", CONVENTIONAL, PYTHON, LOW,
PYTHON_S139_DESCRIPTION + "extendedDesc");
r -> r.getDefaultImpacts().get(0).getImpactSeverity(), r -> r.getDescription().getRight().getTabs().get(0).getContent().getLeft().getHtmlContent())
.containsExactly("python:S139", "Comments should not be located at the end of lines of code", FORMATTED, PYTHON, LOW,
PYTHON_S139_DESCRIPTION);
assertThat(details.getParams()).isEmpty();
}

Expand Down Expand Up @@ -446,7 +448,7 @@ void it_should_add_a_more_info_tab_if_no_resource_section_exists_and_extended_de
The application must determine where the third-party data comes from and treat that data
source as an attack vector. Two rules apply:
</p>
<p>
First, before using it in the application&apos;s business logic, the application must
validate the attacker-controlled data against predefined formats, such as:
Expand All @@ -457,7 +459,7 @@ void it_should_add_a_more_info_tab_if_no_resource_section_exists_and_extended_de
<li>Types</li>
<li>Or any strict schema</li>
</ul>
<p>
Second, the application must sanitize string data before inserting it into interpreted
contexts (client-side code, file paths, SQL queries). Unsanitized code can corrupt the
Expand Down Expand Up @@ -541,39 +543,48 @@ private EffectiveRuleDetailsDto getEffectiveRuleDetails(SonarLintTestRpcServer b
really readable, trailing comments would have to be properly written and formatted (correct alignment, no interference with the visual structure of
the code, not too long to be visible) but most often, automatic code formatters would not handle this correctly: the code would end up less readable.
Comments are far better placed on the previous empty line of code, where they will always be visible and properly formatted.</p>
<h2>Noncompliant Code Example</h2>
<h3>Noncompliant code example</h3>
<pre>
a = b + c # This is a trailing comment that can be very very long
</pre>
<h2>Compliant Solution</h2>
<h3>Compliant solution</h3>
<pre>
# This very long comment is better placed before the line of code
a = b + c
</pre>""";
private static final String JAVA_S106_DESCRIPTION = """
<p>When logging a message there are several important requirements which must be fulfilled:</p>
<p>In software development, logs serve as a record of events within an application, providing crucial insights for debugging. When logging, it is
essential to ensure that the logs are:</p>
<ul>
<li> The user must be able to easily retrieve the logs </li>
<li> The format of all logged message must be uniform to allow the user to easily read the log </li>
<li> Logged data must actually be recorded </li>
<li> Sensitive data must only be logged securely </li>
<li> easily accessible </li>
<li> uniformly formatted for readability </li>
<li> properly recorded </li>
<li> securely logged when dealing with sensitive data </li>
</ul>
<p>If a program directly writes to the standard outputs, there is absolutely no way to comply with those requirements. That’s why defining and using a
dedicated logger is highly recommended.</p>
<h2>Noncompliant Code Example</h2>
<pre>
System.out.println("My Message"); // Noncompliant
</pre>
<h2>Compliant Solution</h2>
<pre>
logger.log("My Message");
<p>Those requirements are not met if a program directly writes to the standard outputs (e.g., System.out, System.err). That is why defining and using
a dedicated logger is highly recommended.</p>
<p>The following noncompliant code:</p>
<pre data-diff-id="1" data-diff-type="noncompliant">
class MyClass {
public void doSomething() {
System.out.println("My Message"); // Noncompliant, output directly to System.out without a logger
}
}
</pre>
<h2>See</h2>
<ul>
<li> <a href="https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/">OWASP Top 10 2021 Category A9</a> - Security Logging and
Monitoring Failures </li>
<li> <a href="https://www.owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure">OWASP Top 10 2017 Category A3</a> - Sensitive Data
Exposure </li>
<li> <a href="https://wiki.sei.cmu.edu/confluence/x/nzdGBQ">CERT, ERR02-J.</a> - Prevent exceptions while logging data </li>
</ul>""";
<p>Could be replaced by:</p>
<pre data-diff-id="1" data-diff-type="compliant">
import java.util.logging.Logger;
class MyClass {
Logger logger = Logger.getLogger(getClass().getName());
public void doSomething() {
// ...
logger.info("My Message"); // Compliant, output via logger
// ...
}
}
</pre>""";
}
Loading

0 comments on commit 2f16d03

Please sign in to comment.