Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add static code analyzer rules #2202

Merged
merged 15 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ load-tests/**/Dependencies.toml
# Ballerina related ignores
Ballerina.lock
velocity.log*

compiler-plugin-tests/**/target
1 change: 1 addition & 0 deletions compiler-plugin-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ test {
systemProperty "ballerina.offline.flag", "true"
useTestNG()
finalizedBy jacocoTestReport
testLogging.showStandardStreams = true
}

jacocoTestReport {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package io.ballerina.stdlib.http.compiler.staticcodeanalyzer;

import org.testng.Assert;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;
import org.testng.internal.ExitCode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* This class includes tests for Ballerina Http static code analyzer.
*/
class StaticCodeAnalyzerTest {

private static final Path RESOURCE_PACKAGES_DIRECTORY = Paths
.get("src", "test", "resources", "static_code_analyzer", "ballerina_packages").toAbsolutePath();
private static final Path EXPECTED_JSON_OUTPUT_DIRECTORY = Paths.
get("src", "test", "resources", "static_code_analyzer", "expected_output").toAbsolutePath();
private static final Path BALLERINA_PATH = Paths
.get("../", "target", "ballerina-runtime", "bin", "bal").toAbsolutePath();
private static final Path JSON_RULES_FILE_PATH = Paths
.get("../", "compiler-plugin", "src", "main", "resources", "rules.json").toAbsolutePath();
private static final String SCAN_COMMAND = "scan";

@BeforeSuite
public void pullScanTool() throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), "tool", "pull", SCAN_COMMAND);
Process process = processBuilder.start();
int exitCode = process.waitFor();
String output = convertInputStreamToString(process.getInputStream());
if (Pattern.compile("tool 'scan:.+\\..+\\..+' successfully set as the active version\\.")
.matcher(output).find() || Pattern.compile("tool 'scan:.+\\..+\\..+' is already active\\.")
.matcher(output).find()) {
return;
}
Assert.assertFalse(ExitCode.hasFailure(exitCode));
}

@Test
public void validateRulesJson() throws IOException {
String expectedRules = "[" + Arrays.stream(HttpRule.values())
.map(HttpRule::toString).collect(Collectors.joining(",")) + "]";
String actualRules = Files.readString(JSON_RULES_FILE_PATH);
assertJsonEqual(normalizeJson(actualRules), normalizeJson(expectedRules));
}

@Test
public void testStaticCodeRules() throws IOException, InterruptedException {
for (HttpRule rule : HttpRule.values()) {
String targetPackageName = "rule" + rule.getId();
String actualJsonReport = StaticCodeAnalyzerTest.executeScanProcess(targetPackageName);
String expectedJsonReport = Files
.readString(EXPECTED_JSON_OUTPUT_DIRECTORY.resolve(targetPackageName + ".json"));
assertJsonEqual(actualJsonReport, expectedJsonReport);
}
}

public static String executeScanProcess(String targetPackage) throws IOException, InterruptedException {
ProcessBuilder processBuilder2 = new ProcessBuilder(BALLERINA_PATH.toString(), SCAN_COMMAND);
processBuilder2.directory(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage).toFile());
Process process2 = processBuilder2.start();
int exitCode = process2.waitFor();
Assert.assertFalse(ExitCode.hasFailure(exitCode));
return Files.readString(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage)
.resolve("target").resolve("report").resolve("scan_results.json"));
}

public static String convertInputStreamToString(InputStream inputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line).append(System.lineSeparator());
}
}
return stringBuilder.toString();
}

private void assertJsonEqual(String actual, String expected) {
Assert.assertEquals(normalizeJson(actual), normalizeJson(expected));
}

private static String normalizeJson(String json) {
return json.replaceAll("\\s*\"\\s*", "\"")
.replaceAll("\\s*:\\s*", ":")
.replaceAll("\\s*,\\s*", ",")
.replaceAll("\\s*\\{\\s*", "{")
.replaceAll("\\s*}\\s*", "}")
.replaceAll("\\s*\\[\\s*", "[")
.replaceAll("\\s*]\\s*", "]")
.replaceAll("\n", "");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
org = "ballerina"
name = "rule1"
version = "0.1.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;

service on new http:Listener(8080) {
resource function default .() returns string? {
return;
}

resource function default greet() returns string? {
return;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;

service class GreetingService {
*http:Service;

resource function default .() returns string {
return "";
}

resource function default greet() returns string {
return "";
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;

type ServiceContract service object {
*http:Service;
resource function default .() returns string;
resource function default greet() returns string;
};

type MyContract service object {
*http:ServiceContract;
resource function default .() returns string;
resource function default greet() returns string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
org = "ballerina"
name = "rule2"
version = "0.1.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;

@http:ServiceConfig {
cors: {
allowOrigins: ["*"]
}
}
service on new http:Listener(8080) {

@http:ResourceConfig {
cors: {
allowOrigins: ["*"]
}
}
resource function get greet() returns string? {
return;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;

service class GreetingService {
*http:Service;

@http:ResourceConfig {
cors: {
allowOrigins: ["*"]
}
}
resource function get .() returns string? {
return;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import ballerina/http;

@http:ServiceConfig {
cors: {
allowOrigins: ["*"]
}
}
type ServiceContract service object {
*http:Service;

@http:ResourceConfig {
cors: {
allowOrigins: ["*"]
}
}
resource function get greet() returns string;
};

@http:ServiceConfig {
cors: {
allowOrigins: ["*"]
}
}
type MyContract service object {
*http:ServiceContract;

@http:ResourceConfig {
cors: {
allowOrigins: ["*"]
}
}
resource function get greet() returns string;
};
Loading
Loading