Scenario Outline: A plain scenario -- @1.131 # features/feature_one.feature:138
Given I run a step # features/steps/steps.py:3 0.000s
Scenario Outline: A plain scenario -- @1.132 # features/feature_one.feature:139
Given I run a step # features/steps/steps.py:3 0.000s
Scenario Outline: A plain scenario -- @1.133 # features/feature_one.feature:140
Given I run a step # features/steps/steps.py:3 0.000s
Exception RecursionError: maximum recursion depth exceeded
Traceback (most recent call last):
File "/home/paul/git/REDACTED/django_issue/manage.py", line 22, in <module>
main()
~~~~^^
File "/home/paul/git/REDACTED/django_issue/manage.py", line 18, in main
execute_from_command_line(sys.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
utility.execute()
~~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/__init__.py", line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/base.py", line 416, in run_from_argv
self.execute(*args, **cmd_options)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/base.py", line 460, in execute
output = self.handle(*args, **options)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave_django/management/commands/behave.py", line 197, in handle
exit_status = behave_main(args=behave_args)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/__main__.py", line 290, in main
return run_behave(config)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/__main__.py", line 112, in run_behave
failed = runner.run()
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/runner.py", line 936, in run
return self.run_with_paths()
~~~~~~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/runner.py", line 956, in run_with_paths
return self.run_model()
~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/runner.py", line 757, in run_model
failed = feature.run(self)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/model.py", line 418, in run
failed = run_item.run(runner)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/model.py", line 1668, in run
failed = scenario.run(runner)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave/model.py", line 1231, in run
runner.run_hook("after_scenario", runner.context, self)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave_django/environment.py", line 147, in run_hook
django_test_runner.teardown_test(context)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave_django/environment.py", line 124, in teardown_test
context.test._post_teardown(run=True)
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/behave_django/testcase.py", line 20, in _post_teardown
super()._post_teardown()
~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 1227, in _post_teardown
self._fixture_teardown()
~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 1262, in _fixture_teardown
call_command(
~~~~~~~~~~~~^
"flush",
^^^^^^^^
...<5 lines>...
inhibit_post_migrate=inhibit_post_migrate,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/__init__.py", line 194, in call_command
return command.execute(*args, **defaults)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/base.py", line 460, in execute
output = self.handle(*args, **options)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/commands/flush.py", line 52, in handle
sql_list = sql_flush(
self.style,
...<2 lines>...
allow_cascade=allow_cascade,
)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/core/management/sql.py", line 11, in sql_flush
tables = connection.introspection.django_table_names(
only_existing=True, include_views=False
)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/db/backends/base/introspection.py", line 110, in django_table_names
existing_tables = set(self.table_names(include_views=include_views))
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/db/backends/base/introspection.py", line 56, in table_names
with self.connection.cursor() as cursor:
~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/utils/asyncio.py", line 26, in inner
return func(*args, **kwargs)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/db/backends/base/base.py", line 320, in cursor
return self._cursor()
~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/db/backends/base/base.py", line 296, in _cursor
self.ensure_connection()
~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 311, in patched_ensure_connection
real_ensure_connection(self, *args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 311, in patched_ensure_connection
real_ensure_connection(self, *args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 311, in patched_ensure_connection
real_ensure_connection(self, *args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
[Previous line repeated 130 more times]
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/utils/asyncio.py", line 26, in inner
return func(*args, **kwargs)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/db/backends/base/base.py", line 279, in ensure_connection
self.connect()
~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/utils/asyncio.py", line 26, in inner
return func(*args, **kwargs)
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/db/backends/base/base.py", line 257, in connect
self.set_autocommit(self.settings_dict["AUTOCOMMIT"])
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/db/backends/base/base.py", line 473, in set_autocommit
self.ensure_connection()
~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 311, in patched_ensure_connection
real_ensure_connection(self, *args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 311, in patched_ensure_connection
real_ensure_connection(self, *args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paul/git/REDACTED/django_issue/.venv/lib/python3.13/site-packages/django/test/testcases.py", line 311, in patched_ensure_connection
real_ensure_connection(self, *args, **kwargs)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
[Previous line repeated 130 more times]
RecursionError: maximum recursion depth exceeded
make: *** [Makefile:31: run-feature-tests] Error 1
tl;dr: Each scenario adds a new recursion level in multiple places (usually the settings fetch), and ends up throwing
RecursionError: maximum recursion depth exceeded. The error can be replicated with a vanilla Django project from Django 4.1 onward. I am unable to find a culprit and require help from someone to investigate further. The issue doesn't happen on vanillaLiveServerTestCaseunit tests. The recursion error is not consistently in the same place.Background
After bumping from Django 5.0.x to 5.1 in a project, our feature tests started to fail after ~50min with the error pasted in the collapsed menu below. Turns out once too many tests are ran, a recursion error happens.
Click to expand!
Reproduction
This super plain Django project reproduces the error. It has:
sys.setrecursionlimit(300)in the settings.py to fail a bit faster.The project:
django_issue.zip
To use: unzip and:
make allto run the 1000+ repeated feature testsmake run-unit-teststo compare with 1200 unit tests not failingpyproject.tomlline for django todjango = "4.1"to test with Django 4.1 (it will use behave-django 1.4.0) or<4.1to check with the version that doesn't fail.poetry updatethenmake allto replicate the issueHere is the end of the output of
make all. Note that the stack is different than the above error. The error is not really consistently in the same place, meaning there are recursion errors in several places.Click to expand!
And here with Django 4.1:
Click to expand!
Debugging
I recommend adding a breakpoint in your venv's
django.conf.UserSettingsHolder.__getattr__, make it conditional likename == "DEBUG"and observe the stack having 1 more__getattr__call per test. These are the kind of stacks I saw:Here in Django 5
Here in Django 4.1
Another one in 4.1
And a normal one in 4.0