Skip to content

Commit

Permalink
Remove astor dependency
Browse files Browse the repository at this point in the history
Python 3.8 required us to use a library to print the generated Python
AST into Python source files.  Since we dropped the support for Python
3.8 (and 3.9) with 0bfb66a we can now
simply us the `unparse` function from Python's AST library that was
introduced in Python 3.9.

This change removes all `astor`-related code and makes the necessary
changes to use Python's `ast.unparse`.
  • Loading branch information
stephanlukasczyk committed Mar 16, 2022
1 parent ace7518 commit 6391f90
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 133 deletions.
1 change: 0 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
sphinx==4.4.0
sphinx-autodoc-typehints==1.16.0
astor==0.8.1
simple-parsing==0.0.18
bytecode==0.13.0
typing_inspect==0.7.1
Expand Down
14 changes: 1 addition & 13 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions pynguin/generation/export/abstractexporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
from pathlib import Path
from typing import TYPE_CHECKING

import astor

import pynguin.testcase.testcase_to_ast as tc_to_ast

if TYPE_CHECKING:
Expand Down Expand Up @@ -97,6 +95,7 @@ def __create_function_node(
defaults=[],
vararg=None,
kwarg=None,
posonlyargs=[],
kwonlyargs=[],
kw_defaults=[],
),
Expand All @@ -118,4 +117,4 @@ def _save_ast_to_file(path: str | os.PathLike, module: ast.Module) -> None:
target.parent.mkdir(parents=True, exist_ok=True)
with target.open(mode="w", encoding="UTF-8") as file:
file.write("# Automatically generated by Pynguin.\n")
file.write(astor.to_source(module))
file.write(ast.unparse(ast.fix_missing_locations(module)))
2 changes: 1 addition & 1 deletion pynguin/generation/export/pytestexporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ def export_sequences(self, path: str | os.PathLike, test_cases: list[tc.TestCase
module_aliases, common_modules
)
functions = AbstractTestExporter._create_functions(asts, False)
module = ast.Module(body=import_nodes + functions)
module = ast.Module(body=import_nodes + functions, type_ignores=[])
AbstractTestExporter._save_ast_to_file(path, module)
5 changes: 2 additions & 3 deletions pynguin/testcase/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from types import CodeType, ModuleType
from typing import TYPE_CHECKING, Any, Callable

import astor
from bytecode import Compare
from jellyfish import levenshtein_distance
from ordered_set import OrderedSet
Expand Down Expand Up @@ -1114,13 +1113,13 @@ def _execute_statement(
) -> Exception | None:
ast_node = exec_ctx.executable_node_for(statement)
if self._logger.isEnabledFor(logging.DEBUG):
self._logger.debug("Executing %s", astor.to_source(ast_node))
self._logger.debug("Executing %s", ast.unparse(ast_node))
code = compile(ast_node, "<ast>", "exec")
try:
# pylint: disable=exec-used
exec(code, exec_ctx.global_namespace, exec_ctx.local_namespace) # nosec
except Exception as err: # pylint: disable=broad-except
failed_stmt = astor.to_source(ast_node)
failed_stmt = ast.unparse(ast_node)
TestCaseExecutor._logger.debug(
"Failed to execute statement:\n%s%s", failed_stmt, err.args
)
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ classifiers = [

[tool.poetry.dependencies]
python = "^3.10"
astor = "^0.8.1"
simple-parsing = "^0.0.18"
bytecode = "^0"
typing_inspect = "^0"
Expand Down
7 changes: 2 additions & 5 deletions tests/analyses/test_ast_to_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
[
(
""" float_0 = 1.1
var_0 = module_0.positional_only(float_0)
"""
var_0 = module_0.positional_only(float_0)"""
),
(
""" float_0 = 1.1
Expand All @@ -32,8 +31,7 @@
str_1 = 'key'
str_2 = 'value'
str_3 = {str_1: str_2}
var_1 = module_0.all_params(float_0, int_0, *var_0, param4=str_0, **str_3)
"""
var_1 = module_0.all_params(float_0, int_0, *var_0, param4=str_0, **str_3)"""
),
],
)
Expand All @@ -42,7 +40,6 @@ def test_parameter_mapping_roundtrip(testcase_seed, tmp_path):
"""# Automatically generated by Pynguin.
import tests.fixtures.grammar.parameters as module_0
def test_case_0():
"""
+ testcase_seed
Expand Down
13 changes: 7 additions & 6 deletions tests/assertion/test_assertion_generation_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import ast
import importlib

import astor
import pytest

import pynguin.assertion.assertiongenerator as ag
Expand All @@ -32,17 +31,15 @@
assert human_0 is not None
assert module_0.static_state == 0
str_1 = human_0.get_name()
assert str_1 == 'foo bar'
""",
assert str_1 == 'foo bar'""",
),
(
ag.MutationAnalysisAssertionGenerator,
"""str_0 = 'foo bar'
float_0 = 39.82
human_0 = module_0.Human(str_0, float_0)
assert module_0.static_state == 0
str_1 = human_0.get_name()
""",
str_1 = human_0.get_name()""",
),
],
)
Expand Down Expand Up @@ -79,5 +76,9 @@ def test_generate_mutation_assertions(generator, expected_result):

visitor = tc_to_ast.TestCaseToAstVisitor()
test_case.accept(visitor)
source = astor.to_source(ast.Module(body=visitor.test_case_asts[0]))
source = ast.unparse(
ast.fix_missing_locations(
ast.Module(body=visitor.test_case_asts[0], type_ignores=[])
)
)
assert source == expected_result
38 changes: 21 additions & 17 deletions tests/assertion/test_assertion_to_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
import ast
import enum
from _ast import Module
from unittest.mock import MagicMock

import astor
import pytest

import pynguin.assertion.assertion as ass
Expand All @@ -24,34 +23,39 @@ def assertion_to_ast() -> ata.AssertionToAstVisitor:
return ata.AssertionToAstVisitor(scope, module_aliases, set())


def __create_source_from_ast(module_body: list[ast.stmt]) -> str:
return ast.unparse(
ast.fix_missing_locations(ast.Module(body=module_body, type_ignores=[]))
)


def test_none(assertion_to_ast):
assertion = ass.NotNoneAssertion(source=vr.VariableReference(MagicMock(), None))
assertion.accept(assertion_to_ast)
assert (
astor.to_source(Module(body=assertion_to_ast.nodes))
== "assert var_0 is not None\n"
__create_source_from_ast(assertion_to_ast.nodes) == "assert var_0 is not None"
)


@pytest.mark.parametrize(
"obj,output",
[
(True, "assert var_0 is True\n"),
(False, "assert var_0 is False\n"),
((True, False), "assert var_0 == (True, False)\n"),
([3, 8], "assert var_0 == [3, 8]\n"),
([[3, 8], {"foo"}], "assert var_0 == [[3, 8], {'foo'}]\n"),
(True, "assert var_0 is True"),
(False, "assert var_0 is False"),
((True, False), "assert var_0 == (True, False)"),
([3, 8], "assert var_0 == [3, 8]"),
([[3, 8], {"foo"}], "assert var_0 == [[3, 8], {'foo'}]"),
(
{"foo": ["nope", 1, False, None]},
"assert var_0 == {'foo': ['nope', 1, False, None]}\n",
"assert var_0 == {'foo': ['nope', 1, False, None]}",
),
(
{"foo": "bar", "baz": "argh"},
"assert var_0 == {'foo': 'bar', 'baz': 'argh'}\n",
"assert var_0 == {'foo': 'bar', 'baz': 'argh'}",
),
(
{enum.Enum("Dummy", "a").a: False},
"assert var_0 == {module_0.Dummy.a: False}\n",
"assert var_0 == {module_0.Dummy.a: False}",
),
],
)
Expand All @@ -60,7 +64,7 @@ def test_object_assertion(assertion_to_ast, obj, output):
source=vr.VariableReference(MagicMock(), None), value=obj
)
assertion.accept(assertion_to_ast)
assert astor.to_source(Module(body=assertion_to_ast.nodes)) == output
assert __create_source_from_ast(assertion_to_ast.nodes) == output


def test_float_assertion(assertion_to_ast):
Expand All @@ -69,18 +73,18 @@ def test_float_assertion(assertion_to_ast):
)
assertion.accept(assertion_to_ast)
assert (
astor.to_source(Module(body=assertion_to_ast.nodes))
== "assert var_0 == pytest.approx(1.5, abs=0.01, rel=0.01)\n"
__create_source_from_ast(assertion_to_ast.nodes)
== "assert var_0 == pytest.approx(1.5, abs=0.01, rel=0.01)"
)


@pytest.mark.parametrize(
"length, output",
[(0, "assert len(var_0) == 0\n"), (42, "assert len(var_0) == 42\n")],
[(0, "assert len(var_0) == 0"), (42, "assert len(var_0) == 42")],
)
def test_collection_length(assertion_to_ast, length, output):
assertion = ass.CollectionLengthAssertion(
source=vr.VariableReference(MagicMock(), None), length=length
)
assertion.accept(assertion_to_ast)
assert astor.to_source(Module(body=assertion_to_ast.nodes)) == output
assert __create_source_from_ast(assertion_to_ast.nodes) == output
5 changes: 1 addition & 4 deletions tests/generation/export/test_pytestexporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def test_export_sequence(exportable_test_case, tmp_path):
import pytest
import tests.fixtures.accessibles.accessible as module_0
def test_case_0():
int_0 = 5
some_type_0 = module_0.SomeType(int_0)
Expand All @@ -26,13 +25,11 @@ def test_case_0():
float_1 = module_0.simple_function(float_0)
assert float_1 == pytest.approx(42.23, abs=0.01, rel=0.01)
def test_case_1():
int_0 = 5
some_type_0 = module_0.SomeType(int_0)
assert some_type_0 == 5
float_0 = 42.23
float_1 = module_0.simple_function(float_0)
assert float_1 == pytest.approx(42.23, abs=0.01, rel=0.01)
"""
assert float_1 == pytest.approx(42.23, abs=0.01, rel=0.01)"""
)
Loading

0 comments on commit 6391f90

Please sign in to comment.