Skip to content

Commit

Permalink
Tests for sast codemods
Browse files Browse the repository at this point in the history
  • Loading branch information
andrecsilva committed Jan 25, 2024
1 parent f1a28ca commit 8ba8f13
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 13 deletions.
18 changes: 14 additions & 4 deletions integration_tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BaseIntegrationTest(DependencyTestMixin, CleanRepoMixin):
_lines = []
num_changed_files = 1
allowed_exceptions = ()
sonar_issues_json: str | None = None

@classmethod
def setup_class(cls):
Expand All @@ -81,10 +82,16 @@ def _assert_run_fields(self, run, output_path):
assert run["tool"] == "codemodder-python"
assert run["version"] == __version__
assert run["elapsed"] != ""
assert (
run["commandLine"]
== f"codemodder {SAMPLES_DIR} --output {output_path} --codemod-include={self.codemod_instance.name} --path-include={self.code_path}"
)
if self.sonar_issues_json:
assert (
run["commandLine"]
== f"codemodder {SAMPLES_DIR} --output {output_path} --codemod-include={self.codemod_instance.name} --path-include={self.code_path} --sonar-issues-json={self.sonar_issues_json}"
)
else:
assert (
run["commandLine"]
== f"codemodder {SAMPLES_DIR} --output {output_path} --codemod-include={self.codemod_instance.name} --path-include={self.code_path}"
)
assert run["directory"] == os.path.abspath(SAMPLES_DIR)
assert run["sarifs"] == []

Expand Down Expand Up @@ -159,6 +166,9 @@ def test_file_rewritten(self):
f"--path-include={self.code_path}",
]

if self.sonar_issues_json:
command.append(f"--sonar-issues-json={self.sonar_issues_json}")

self.check_code_before()
self.check_dependencies_before()

Expand Down
38 changes: 38 additions & 0 deletions integration_tests/test_sonar_numpy_nan_equality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from core_codemods.sonar_numpy_nan_equality import (
SonarNumpyNanEquality,
SonarNumpyNanEqualityTransformer,
)
from integration_tests.base_test import (
BaseIntegrationTest,
original_and_expected_from_code_path,
)


class TestNumpyNanEquality(BaseIntegrationTest):
codemod = SonarNumpyNanEquality
code_path = "tests/samples/numpy_nan_equality.py"
original_code, expected_new_code = original_and_expected_from_code_path(
code_path,
[
(3, """if np.isnan(a):\n"""),
],
)
sonar_issues_json = "tests/samples/sonar_issues.json"

# fmt: off
expected_diff =(
"""--- \n"""
"""+++ \n"""
"""@@ -1,5 +1,5 @@\n"""
""" import numpy as np\n"""
""" \n"""
""" a = np.nan\n"""
"""-if a == np.nan:\n"""
"""+if np.isnan(a):\n"""
""" pass\n"""
)
# fmt: on

expected_line_change = "4"
change_description = SonarNumpyNanEqualityTransformer.change_description
num_changed_files = 1
23 changes: 14 additions & 9 deletions src/codemodder/sonar_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path
from typing_extensions import Self
from codemodder.result import LineInfo, Location, Result, ResultSet
from codemodder.logging import logger


class SonarLocation(Location):
Expand All @@ -17,7 +18,7 @@ def from_issue(cls, issue) -> Self:
class SonarResult(Result):
@classmethod
def from_issue(cls, issue) -> Self:
rule_id = issue.get("rule")
rule_id = issue.get("rule", None)
if not rule_id:
raise ValueError("Could not extract rule id from sarif result.")

Expand All @@ -28,11 +29,15 @@ def from_issue(cls, issue) -> Self:
class SonarResultSet(ResultSet):
@classmethod
def from_json(cls, json_file: str | Path) -> Self:
with open(json_file, "r", encoding="utf-8") as file:
data = json.load(file)

result_set = cls()
for issue in data.get("issues"):
result_set.add_result(SonarResult.from_issue(issue))

return result_set
try:
with open(json_file, "r", encoding="utf-8") as file:
data = json.load(file)

result_set = cls()
for issue in data.get("issues"):
result_set.add_result(SonarResult.from_issue(issue))

return result_set
except Exception:
logger.debug("Could not parse sonar json %s", json_file)
return cls()
57 changes: 57 additions & 0 deletions tests/codemods/base_codemod_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,60 @@ def create_dir_structure(self, tmpdir):
settings_folder = django_root / "mysite"
os.makedirs(settings_folder)
return (django_root, settings_folder)


class BaseSASTCodemodTest(BaseCodemodTest):
tool: ClassVar = NotImplemented

def run_and_assert( # pylint: disable=too-many-arguments
self,
tmpdir,
input_code,
expected,
num_changes: int = 1,
root: Path | None = None,
files: list[Path] | None = None,
lines_to_exclude: list[int] | None = None,
results: str = "",
):
root = root or tmpdir
tmp_file_path = files[0] if files else Path(tmpdir) / "code.py"
tmp_file_path.write_text(dedent(input_code))

tmp_results_file_path = Path(tmpdir) / "sast_results"

with open(tmp_results_file_path, "w", encoding="utf-8") as results_file:
results_file.write(results)

files_to_check = files or [tmp_file_path]

path_exclude = [f"{tmp_file_path}:{line}" for line in lines_to_exclude or []]

self.execution_context = CodemodExecutionContext(
directory=root,
dry_run=False,
verbose=False,
tool_result_files_map={self.tool: [str(tmp_results_file_path)]},
registry=mock.MagicMock(),
repo_manager=mock.MagicMock(),
path_include=[f.name for f in files_to_check],
path_exclude=path_exclude,
)

self.codemod.apply(self.execution_context, files_to_check)
changes = self.execution_context.get_results(self.codemod.id)

if input_code == expected:
assert not changes
return

assert len(changes) == 1
assert len(changes[0].changes) == num_changes

self.assert_changes(
tmpdir,
tmp_file_path,
input_code,
expected,
changes[0],
)
41 changes: 41 additions & 0 deletions tests/codemods/test_sonar_numpy_nan_equality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import json
from core_codemods.sonar_numpy_nan_equality import SonarNumpyNanEquality
from tests.codemods.base_codemod_test import BaseSASTCodemodTest
from textwrap import dedent


class TestSonarNumpyNanEquality(BaseSASTCodemodTest):
codemod = SonarNumpyNanEquality
tool = "sonar"

def test_name(self):
assert self.codemod.name == "numpy-nan-equality-S6725"

def test_simple(self, tmpdir):
input_code = """\
import numpy
if a == numpy.nan:
pass
"""
expected = """\
import numpy
if numpy.isnan(a):
pass
"""
issues = {
"issues": [
{
"rule": "python:S6725",
"component": f"{tmpdir / 'code.py'}",
"textRange": {
"startLine": 2,
"endLine": 2,
"startOffset": 3,
"endOffset": 17,
},
}
]
}
self.run_and_assert(
tmpdir, dedent(input_code), dedent(expected), results=json.dumps(issues)
)
1 change: 1 addition & 0 deletions tests/samples/sonar_issues.json

Large diffs are not rendered by default.

0 comments on commit 8ba8f13

Please sign in to comment.