Skip to content

Commit

Permalink
use tmpdir instead of tests/samples
Browse files Browse the repository at this point in the history
  • Loading branch information
clavedeluna committed Apr 2, 2024
1 parent 157477d commit 583d005
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 52 deletions.
2 changes: 1 addition & 1 deletion integration_tests/test_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_codemods_include_exclude_conflict(self):
completed_process = subprocess.run(
[
"codemodder",
"tests/samples/",
"some/path",
"--output",
"doesntmatter.txt",
"--codemod-exclude",
Expand Down
12 changes: 6 additions & 6 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_no_args(self, error_logger, mocker):
[
["--help"],
[
"tests/samples/",
"some/path",
"--output",
"here.txt",
"--codemod-include=url-sandbox",
Expand All @@ -51,7 +51,7 @@ def test_help_is_printed(self, mock_print_help, cli_args):
[
["--version"],
[
"tests/samples/",
"some/path",
"--output",
"here.txt",
"--codemod-include=url-sandbox",
Expand All @@ -72,7 +72,7 @@ def test_version_is_printed(self, mock_print_msg, cli_args):
[
["--list"],
[
"tests/samples/",
"some/path",
"--output",
"here.txt",
"--list",
Expand Down Expand Up @@ -112,7 +112,7 @@ def test_bad_output_format(self, error_logger):
with pytest.raises(SystemExit) as err:
parse_args(
[
"tests/samples/",
"some/path",
"--output",
"here.txt",
"--output-format",
Expand All @@ -132,7 +132,7 @@ def test_bad_option(self, error_logger):
with pytest.raises(SystemExit) as err:
parse_args(
[
"tests/samples/",
"some/path",
"--output",
"here.txt",
"--codemod=url-sandbox",
Expand All @@ -152,7 +152,7 @@ def test_bad_option(self, error_logger):
def test_codemod_name_or_id(self, codemod):
parse_args(
[
"tests/samples/",
"some/path",
"--output",
"here.txt",
f"--codemod-include={codemod}",
Expand Down
135 changes: 90 additions & 45 deletions tests/test_codemodder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from codemodder.diff import create_diff_from_tree
from codemodder.registry import load_registered_codemods
from codemodder.result import ResultSet
from codemodder.semgrep import run as semgrep_run


@pytest.fixture(autouse=True, scope="module")
Expand All @@ -21,19 +20,44 @@ def disable_codemod_apply(mocker, request):
run all of codemodder but we most often don't need to actually apply codemods.
"""
# Skip mocking only for specific tests that need to apply codemods.
if request.function.__name__ == "test_cst_parsing_fails":
if request.function.__name__ in (
"test_cst_parsing_fails",
"test_dry_run",
"test_run_codemod_name_or_id",
):
return
mocker.patch("codemodder.codemods.base_codemod.BaseCodemod.apply")


@pytest.fixture(scope="function")
def dir_structure(tmp_path_factory):
code_dir = tmp_path_factory.mktemp("code")
(code_dir / "test_request.py").write_text(
"""
from test_sources import untrusted_data
import requests
url = untrusted_data()
requests.get(url)
var = "hello"
"""
)
(code_dir / "test_random.py").write_text(
"""
import random
def func(foo=[]):
return random.random()
"""
)
codetf = code_dir / "result.codetf"
assert not codetf.exists()
return code_dir, codetf


class TestRun:
@mock.patch("libcst.parse_module")
def test_no_files_matched(self, mock_parse, tmpdir):
codetf = tmpdir / "result.codetf"
code_dir = tmpdir.mkdir("code")
code_dir.join("code.py").write("# anything")
assert not codetf.exists()

def test_no_files_matched(self, mock_parse, dir_structure):
code_dir, codetf = dir_structure
args = [
str(code_dir),
"--output",
Expand All @@ -50,15 +74,12 @@ def test_no_files_matched(self, mock_parse, tmpdir):

@mock.patch("libcst.parse_module", side_effect=Exception)
@mock.patch("codemodder.codetf.CodeTF.build")
def test_cst_parsing_fails(self, build_report, mock_parse, tmpdir):
code_dir = tmpdir.mkdir("code")
code_file = code_dir.join("test_request.py")
code_file.write("# anything")

def test_cst_parsing_fails(self, build_report, mock_parse, dir_structure):
code_dir, codetf = dir_structure
args = [
str(code_dir),
"--output",
str(tmpdir / "result.codetf"),
str(codetf),
"--codemod-include",
"fix-assert-tuple",
"--path-include",
Expand All @@ -80,17 +101,21 @@ def test_cst_parsing_fails(self, build_report, mock_parse, tmpdir):
assert requests_report.changeset == []
assert len(requests_report.failedFiles) == 1
assert sorted(requests_report.failedFiles) == [
str(code_file),
str(code_dir / "test_request.py"),
]

build_report.return_value.write_report.assert_called_once()

@mock.patch("codemodder.codemods.libcst_transformer.update_code")
@mock.patch("codemodder.codemods.semgrep.semgrep_run", side_effect=semgrep_run)
def test_dry_run(self, _, mock_update_code, tmpdir):
codetf = tmpdir / "result.codetf"
@mock.patch(
"codemodder.codemods.libcst_transformer.LibcstTransformerPipeline.apply",
new_callable=mock.PropertyMock,
)
@mock.patch("codemodder.context.CodemodExecutionContext.compile_results")
def test_dry_run(self, _, transform_apply, mock_update_code, dir_structure):
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
str(codetf),
"--dry-run",
Expand All @@ -103,16 +128,17 @@ def test_dry_run(self, _, mock_update_code, tmpdir):
res = run(args)
assert res == 0
assert codetf.exists()

transform_apply.assert_called()
mock_update_code.assert_not_called()

@pytest.mark.parametrize("dry_run", [True, False])
@mock.patch("codemodder.codetf.CodeTF.build")
def test_reporting(self, mock_reporting, dry_run):
def test_reporting(self, mock_reporting, dry_run, dir_structure):
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
"here.txt",
str(codetf),
# Make this test faster by restricting the number of codemods
"--codemod-include=use-generator,use-defusedxml,use-walrus-if",
]
Expand All @@ -132,34 +158,49 @@ def test_reporting(self, mock_reporting, dry_run):
mock_reporting.return_value.write_report.assert_called_once()

@pytest.mark.parametrize("codemod", ["secure-random", "pixee:python/secure-random"])
@mock.patch(
"codemodder.codemods.libcst_transformer.LibcstTransformerPipeline.apply",
new_callable=mock.PropertyMock,
)
@mock.patch("codemodder.context.CodemodExecutionContext.compile_results")
@mock.patch("codemodder.codetf.CodeTF.write_report")
def test_run_codemod_name_or_id(self, write_report, mock_compile_results, codemod):
def test_run_codemod_name_or_id(
self,
write_report,
mock_compile_results,
transform_apply,
codemod,
dir_structure,
):
del write_report
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
"here.txt",
str(codetf),
f"--codemod-include={codemod}",
]

exit_code = run(args)
assert exit_code == 0
# todo: if no codemods run do we still compile results?
mock_compile_results.assert_called()
transform_apply.assert_called()


class TestCodemodIncludeExclude:

@mock.patch("codemodder.registry.logger.warning")
@mock.patch("codemodder.codemodder.logger.info")
@mock.patch("codemodder.codetf.CodeTF.write_report")
def test_codemod_include_no_match(self, write_report, info_logger, warning_logger):
def test_codemod_include_no_match(
self, write_report, info_logger, warning_logger, dir_structure
):
bad_codemod = "doesntexist"
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
"here.txt",
str(codetf),
f"--codemod-include={bad_codemod}",
]
run(args)
Expand All @@ -177,14 +218,15 @@ def test_codemod_include_no_match(self, write_report, info_logger, warning_logge
@mock.patch("codemodder.codemodder.logger.info")
@mock.patch("codemodder.codetf.CodeTF.write_report")
def test_codemod_include_some_match(
self, write_report, info_logger, warning_logger
self, write_report, info_logger, warning_logger, dir_structure
):
bad_codemod = "doesntexist"
good_codemod = "secure-random"
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
"here.txt",
str(codetf),
f"--codemod-include={bad_codemod},{good_codemod}",
]
run(args)
Expand All @@ -199,14 +241,15 @@ def test_codemod_include_some_match(
@mock.patch("codemodder.codemodder.logger.info")
@mock.patch("codemodder.codetf.CodeTF.write_report")
def test_codemod_exclude_some_match(
self, write_report, info_logger, warning_logger
self, write_report, info_logger, warning_logger, dir_structure
):
bad_codemod = "doesntexist"
good_codemod = "secure-random"
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
"here.txt",
str(codetf),
f"--codemod-exclude={bad_codemod},{good_codemod}",
]
run(args)
Expand All @@ -229,13 +272,14 @@ def test_codemod_exclude_some_match(
@mock.patch("codemodder.codetf.CodeTF.write_report")
@mock.patch("codemodder.codemods.base_codemod.BaseCodemod.apply")
def test_codemod_exclude_no_match(
self, apply, write_report, info_logger, warning_logger
self, apply, write_report, info_logger, warning_logger, dir_structure
):
bad_codemod = "doesntexist"
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
"here.txt",
str(codetf),
f"--codemod-exclude={bad_codemod}",
]

Expand All @@ -248,14 +292,14 @@ def test_codemod_exclude_no_match(
)

@mock.patch("codemodder.codemods.semgrep.semgrep_run")
def test_exclude_all_registered_codemods(self, mock_semgrep_run, tmpdir):
codetf = tmpdir / "result.codetf"
def test_exclude_all_registered_codemods(self, mock_semgrep_run, dir_structure):
code_dir, codetf = dir_structure
assert not codetf.exists()

registry = load_registered_codemods()
names = ",".join(registry.names)
args = [
"tests/samples/",
str(code_dir),
"--output",
str(codetf),
f"--codemod-exclude={names}",
Expand All @@ -269,10 +313,11 @@ def test_exclude_all_registered_codemods(self, mock_semgrep_run, tmpdir):

class TestExitCode:
@mock.patch("codemodder.codetf.CodeTF.write_report")
def test_success_0(self, mock_report):
def test_no_changes_success_0(self, mock_report, dir_structure):
del mock_report
code_dir, codetf = dir_structure
args = [
"tests/samples/",
str(code_dir),
"--output",
"here.txt",
"--codemod-include=url-sandbox",
Expand Down Expand Up @@ -300,7 +345,7 @@ def test_bad_project_dir_1(self, mock_report):
def test_conflicting_include_exclude(self, mock_report):
del mock_report
args = [
"tests/samples/",
"anything",
"--output",
"here.txt",
"--codemod-exclude",
Expand Down

0 comments on commit 583d005

Please sign in to comment.