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

Python 3.13: test_generate_missing fails with unbounded recursion #643

Open
musicinmybrain opened this issue Oct 27, 2023 · 1 comment
Open

Comments

@musicinmybrain
Copy link

Using the current master, 30ba3f7:

$ python3.13 --version
Python 3.13.0a1
$ tox -e py3.13
[…]
====================================== FAILURES =======================================
________________________________ test_generate_missing ________________________________

pytester = <Pytester PosixPath('/tmp/pytest-of-ben/pytest-22/test_generate_missing0')>

    def test_generate_missing(pytester):
        """Test generate missing command."""
        pytester.makefile(
            ".feature",
            generation=textwrap.dedent(
                """\
                Feature: Missing code generation

                    Background:
                        Given I have a foobar

                    Scenario: Scenario tests which are already bound to the tests stay as is
                        Given I have a bar


                    Scenario: Code is generated for scenarios which are not bound to any tests
                        Given I have a bar


                    Scenario: Code is generated for scenario steps which are not yet defined(implemented)  
                        Given I have a custom bar
                """
            ),  
        )   

        pytester.makepyfile(
            textwrap.dedent(
                """\
            import functools

            from pytest_bdd import scenario, given

            scenario = functools.partial(scenario, "generation.feature")

            @given("I have a bar")
            def _():
                return "bar"

            @scenario("Scenario tests which are already bound to the tests stay as is")
            def test_foo():
                pass

            @scenario("Code is generated for scenario steps which are not yet defined(implemented)")
            def test_missing_steps():
                pass
            """
            )
        )

        result = pytester.runpytest("--generate-missing", "--feature", "generation.feature")
        result.assert_outcomes(passed=0, failed=0, errors=0)
        assert not result.stderr.str()
>       assert result.ret == 0
E       assert <ExitCode.INTERNAL_ERROR: 3> == 0
E        +  where <ExitCode.INTERNAL_ERROR: 3> = <RunResult ret=3 len(stdout.lines)=19 len(stderr.lines)=0 duration=0.04s>.ret

pytester   = <Pytester PosixPath('/tmp/pytest-of-ben/pytest-22/test_generate_missing0')>
result     = <RunResult ret=3 len(stdout.lines)=19 len(stderr.lines)=0 duration=0.04s>

/home/ben/src/forks/pytest-bdd/tests/generation/test_generate_missing.py:69: AssertionError
-------------------------------- Captured stdout call ---------------------------------
================================= test session starts =================================
platform linux -- Python 3.13.0a1, pytest-7.4.3, pluggy-1.3.0
rootdir: /tmp/pytest-of-ben/pytest-22/test_generate_missing0
plugins: bdd-7.0.0
collected 2 items
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "/home/ben/src/forks/pytest-bdd/.tox/py3.13/lib/python3.13/site-packages/_pytest/main.py", line 271, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>                          ^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "/home/ben/src/forks/pytest-bdd/.tox/py3.13/lib/python3.13/site-packages/pytest_bdd/generation.py", line 184, in _show_missing_code_main
INTERNALERROR>     if scenario in scenarios:
INTERNALERROR>        ^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR>   File "<string>", line 4, in __eq__
INTERNALERROR>   File "<string>", line 4, in __eq__
INTERNALERROR>   File "<string>", line 4, in __eq__
INTERNALERROR>   [Previous line repeated 489 more times]
INTERNALERROR> RecursionError: maximum recursion depth exceeded in comparison

================================ no tests ran in 0.01s ================================
=============================== short test summary info ===============================
FAILED tests/generation/test_generate_missing.py::test_generate_missing - assert <ExitCode.INTERNAL_ERROR: 3> == 0
====================== 1 failed, 119 passed, 1 skipped in 9.83s =======================
py3.13: exit 1 (10.25 seconds) /home/ben/src/forks/pytest-bdd> pytest -vvl pid=1875907
.pkg: _exit> python /usr/lib/python3.11/site-packages/pyproject_api/_backend.py True poetry.core.masonry.api
  py3.13: FAIL code 1 (17.65=setup[7.40]+cmd[10.25] seconds)
  evaluation failed :( (17.71 seconds)
@musicinmybrain musicinmybrain changed the title Python 3.13: test_generate_missing fails with unbound recursion Python 3.13: test_generate_missing fails with unbounded recursion Oct 27, 2023
@encukou
Copy link

encukou commented Jan 8, 2024

This is due to the following in parser.py:

@dataclass
class Feature:
    scenarios: OrderedDict[str, ScenarioTemplate]
    [...]


@dataclass
class ScenarioTemplate:
    feature: Feature
    [...]

That is, a Feature references all its ScenarioTemplates, and a ScenarioTemplate references all its Features.
Both are in a dataclass field with the default compare=True, so comparing scenarios (if scenario in scenarios: in _show_missing_code_main) results in infinite recursion.

Perhaps Feature should be compared by identity rather than by its contents? If that's the case, the fix would be to use:

@dataclass(eq=False)
class Feature:
    ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants