diff --git a/src/codemodder/codemods/libcst_transformer.py b/src/codemodder/codemods/libcst_transformer.py index f9488ff5..671d51e1 100644 --- a/src/codemodder/codemods/libcst_transformer.py +++ b/src/codemodder/codemods/libcst_transformer.py @@ -263,19 +263,18 @@ def apply( with open(file_path, "r", encoding="utf-8") as f: source_tree = cst.parse_module(f.read()) except Exception: - file_context.add_failure(file_path) - logger.exception("error parsing file %s", file_path) + file_context.add_failure(file_path, reason := "Failed to parse file") + logger.exception("%s %s", reason, file_path) return None tree = source_tree - try: with file_context.timer.measure("transform"): for transformer in self.transformers: tree = transformer.transform(tree, results, file_context) except Exception: - file_context.add_failure(file_path) - logger.exception("error transforming file %s", file_path) + file_context.add_failure(file_path, reason := "Failed to transform file") + logger.exception("%s %s", reason, file_path) return None if not file_context.codemod_changes: diff --git a/src/codemodder/file_context.py b/src/codemodder/file_context.py index cf9d0c7d..36ed7d1e 100644 --- a/src/codemodder/file_context.py +++ b/src/codemodder/file_context.py @@ -31,8 +31,9 @@ def add_dependency(self, dependency: Dependency): def add_changeset(self, result: ChangeSet): self.changesets.append(result) - def add_failure(self, filename: Path): + def add_failure(self, filename: Path, reason: str): self.failures.append(filename) + self.add_unfixed_findings(self.get_all_findings(), reason, 0) def add_unfixed_findings( self, findings: list[Finding], reason: str, line_number: int | None = None @@ -58,3 +59,10 @@ def get_findings_for_location(self, line_number: int): ) and result.finding is not None ] + + def get_all_findings(self): + return [ + result.finding + for result in (self.results or []) + if result.finding is not None + ] diff --git a/tests/test_libcst_transformer.py b/tests/test_libcst_transformer.py index c848f3cb..078f933e 100644 --- a/tests/test_libcst_transformer.py +++ b/tests/test_libcst_transformer.py @@ -4,39 +4,98 @@ LibcstResultTransformer, LibcstTransformerPipeline, ) +from codemodder.context import CodemodExecutionContext +from codemodder.file_context import FileContext +from core_codemods.defectdojo.results import DefectDojoResult -def test_parse_error(mocker, caplog): - mocker.patch( - "codemodder.codemods.libcst_transformer.cst.parse_module", - side_effect=ParserSyntaxError, +def _apply_and_assert(mocker, transformer): + file_context = FileContext("home", mocker.MagicMock()) + execution_context = CodemodExecutionContext( + directory=mocker.MagicMock(), + dry_run=True, + verbose=False, + registry=mocker.MagicMock(), + repo_manager=mocker.MagicMock(), + path_include=[], + path_exclude=[], + ) + pipeline = LibcstTransformerPipeline(transformer) + pipeline.apply( + context=execution_context, + file_context=file_context, + results=None, ) + assert len(file_context.failures) == 1 + assert file_context.unfixed_findings == [] - transformer = mocker.MagicMock(spec=LibcstResultTransformer) - file_context = mocker.MagicMock() +def _apply_and_assert_with_tool(mocker, transformer, reason): + file_path = mocker.MagicMock() + file_context = FileContext( + "home", + file_path, + results=[ + DefectDojoResult.from_finding( + { + "id": 1, + "title": "python.django.security.audit.secure-cookies.django-secure-set-cookie", + "file_path": file_path, + "line": 2, + }, + ) + ], + ) + execution_context = CodemodExecutionContext( + directory=mocker.MagicMock(), + dry_run=True, + verbose=False, + registry=mocker.MagicMock(), + repo_manager=mocker.MagicMock(), + path_include=[], + path_exclude=[], + tool_result_files_map={"sonar": ["results.json"]}, + ) pipeline = LibcstTransformerPipeline(transformer) pipeline.apply( - context=mocker.MagicMock(), + context=execution_context, file_context=file_context, results=None, ) + assert len(file_context.failures) == 1 + assert len(file_context.unfixed_findings) == 1 + assert file_context.unfixed_findings[0].reason == reason - file_context.add_failure.assert_called_once() - assert "error parsing file" in caplog.text + +def test_parse_error(mocker, caplog): + mocker.patch( + "codemodder.codemods.libcst_transformer.cst.parse_module", + side_effect=ParserSyntaxError, + ) + transformer = mocker.MagicMock(spec=LibcstResultTransformer) + _apply_and_assert(mocker, transformer) + assert "Failed to parse file" in caplog.text def test_transformer_error(mocker, caplog): transformer = mocker.MagicMock(spec=LibcstResultTransformer) transformer.transform.side_effect = ParserSyntaxError - file_context = mocker.MagicMock() + _apply_and_assert(mocker, transformer) + assert "Failed to transform file" in caplog.text - pipeline = LibcstTransformerPipeline(transformer) - pipeline.apply( - context=mocker.MagicMock(), - file_context=file_context, - results=None, + +def test_parse_error_with_tool(mocker, caplog): + mocker.patch( + "codemodder.codemods.libcst_transformer.cst.parse_module", + side_effect=ParserSyntaxError, ) + transformer = mocker.MagicMock(spec=LibcstResultTransformer) + _apply_and_assert_with_tool(mocker, transformer, "Failed to parse file") + assert "Failed to parse file" in caplog.text - file_context.add_failure.assert_called_once() - assert "error transforming file" in caplog.text + +def test_transformer_error_with_tool(mocker, caplog): + transformer = mocker.MagicMock(spec=LibcstResultTransformer) + transformer.transform.side_effect = ParserSyntaxError + _apply_and_assert_with_tool(mocker, transformer, "Failed to transform file") + assert "Failed to transform file" in caplog.text