[Django] #32655: Improve error if a string is passed as an extra_test to DiscoverRunner.build_suite()

23 views
Skip to first unread message

Django

unread,
Apr 14, 2021, 9:26:06 PM4/14/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris | Owner: nobody
Jerdonek |
Type: | Status: new
Cleanup/optimization |
Component: Testing | Version: 4.0
framework | Keywords:
Severity: Normal | iter_test_cases,DiscoverRunner,extra_tests
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
Currently, if you pass a string like `'abc'` rather than a `TestCase`
instance as one of the items in the `extra_tests` argument to
`DiscoverRunner.build_suite()`, you will get a not-too-helpful error that
looks something like this:

{{{
Traceback (most recent call last):
File "./runtests.py", line 593, in <module>
options.timing,
File "./runtests.py", line 325, in django_tests
failures = test_runner.run_tests(test_labels or get_installed())
File ".../django/test/runner.py", line 759, in run_tests
suite = self.build_suite(test_labels, extra_tests)
File ".../django/test/runner.py", line 641, in build_suite
all_tests.extend(iter_test_cases(extra_tests))
File ".../django/test/utils.py", line 249, in iter_test_cases
yield from iter_test_cases(test)
File ".../django/test/utils.py", line 249, in iter_test_cases
yield from iter_test_cases(test)
File ".../django/test/utils.py", line 249, in iter_test_cases
yield from iter_test_cases(test)
[Previous line repeated 991 more times]
File ".../django/test/utils.py", line 245, in iter_test_cases
if isinstance(test, TestCase):
RecursionError: maximum recursion depth exceeded while calling a Python
object
}}}

This is because
[https://github.com/django/django/blob/23fa29f6a6659e0f600d216de6bcb79e7f6818c9/django/test/utils.py#L238-L249
iter_test_cases()] goes into an infinite loop when it gets to
`iter_test_cases('a')` (iterating the first element of `'abc'`).

Since passing a string is a plausible mistake, I think it would be worth
doing something to short-circuit this case and prevent a `RecursionError`.
The following would accomplish this with a pointer to the problem and
without adding too much complexity:

{{{
diff --git a/django/test/utils.py b/django/test/utils.py
index e977db8a10..ec4dc1837d 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -244,6 +244,8 @@ def iter_test_cases(tests):
for test in tests:
if isinstance(test, TestCase):
yield test
+ elif isinstance(test, str):
+ raise TypeError(f'test {test!r} must be a test case or test
suite not string')
else:
# Otherwise, assume it is a test suite.
yield from iter_test_cases(test)
}}}

It would result in the following:

{{{
Traceback (most recent call last):
File "./runtests.py", line 593, in <module>
options.timing,
File "./runtests.py", line 325, in django_tests
failures = test_runner.run_tests(test_labels or get_installed())
File ".../django/test/runner.py", line 758, in run_tests
suite = self.build_suite(test_labels, extra_tests)
File ".../django/test/runner.py", line 641, in build_suite
all_tests.extend(iter_test_cases(extra_tests))
File ".../django/test/utils.py", line 248, in iter_test_cases
raise TypeError(f'test {test!r} must be a test case or test suite not
string')
TypeError: test 'abc' must be a test case or test suite not string
}}}

Note that prior to #32489 ("Add a generator function in runner.py to
iterate over a test suite's test cases"), the error looked like the
following, which was better than what it is now, but not quite as good as
what I propose above:

{{{
Traceback (most recent call last):
File "./runtests.py", line 593, in <module>
options.timing,
File "./runtests.py", line 325, in django_tests
failures = test_runner.run_tests(test_labels or get_installed())
File ".../django/test/runner.py", line 724, in run_tests
suite = self.build_suite(test_labels, extra_tests)
File ".../django/test/runner.py", line 615, in build_suite
suite.addTest(test)
File ".../unittest/suite.py", line 47, in addTest
raise TypeError("{} is not callable".format(repr(test)))
TypeError: 'abc' is not callable
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/32655>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Apr 14, 2021, 9:31:58 PM4/14/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |
Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
iter_test_cases,DiscoverRunner,extra_tests| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Chris Jerdonek:

Old description:

New description:

+ # Prevent an unfriendly RecursionError that can happen with
strings.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:1>

Django

unread,
Apr 14, 2021, 10:13:08 PM4/14/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
iter_test_cases,DiscoverRunner,extra_tests| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Chris Jerdonek):

Note that this issue was inspired by this error coming up in real-life
here: https://github.com/django/django/pull/14261#issuecomment-819546454

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:2>

Django

unread,
Apr 15, 2021, 1:31:53 AM4/15/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage:
iter_test_cases,DiscoverRunner,extra_tests| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak):

`extra_tests` is unused since 1e72b1c5c11d1d2dc3ce3660a1eb6b775dcca5a5.
Maybe we should deprecate it (and remove in Django 5.0), I don't see much
value in keeping this argument.

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:3>

Django

unread,
Apr 15, 2021, 2:39:18 AM4/15/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Carlton Gibson):

* stage: Unreviewed => Accepted


Comment:

OK, one way or the other, we can do something here. Thanks Chris.

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:4>

Django

unread,
Apr 15, 2021, 3:19:05 AM4/15/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Chris Jerdonek):

Thanks. A couple thoughts: Before deprecating `extra_tests`, we might want
to think through what the alternative escape hatch is for users that might
want to add tests not currently available via discovery. Though
`iter_test_cases()` is an internal function, having the extra check could
still help to avoid some puzzling moments.

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:5>

Django

unread,
Apr 15, 2021, 3:38:22 AM4/15/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak):

> Thanks. A couple thoughts: Before deprecating `extra_tests`, we might
want to think through what the alternative escape hatch is for users that
might want to add tests not currently available via discovery.

They can always subclass `DiscoverRunner`. `extra_tests` is not used
internally and it's untested. I don't think we need to provide any
alternative. We can always ask for opinions on the DevelopersMailingList.

> Though `iter_test_cases()` is an internal function, having the extra
check could still help to avoid some puzzling moments.

Agreed.

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:6>

Django

unread,
Jun 29, 2021, 1:36:44 PM6/29/21
to django-...@googlegroups.com
#32655: Improve error if a string is passed as an extra_test to
DiscoverRunner.build_suite()
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Chris Jerdonek):

* has_patch: 0 => 1


Comment:

PR: https://github.com/django/django/pull/14572

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:7>

Django

unread,
Jun 29, 2021, 2:46:10 PM6/29/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.

-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:8>

Django

unread,
Jun 29, 2021, 3:29:24 PM6/29/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"8bca838f4a230b78dd44fc1db3c1b81e444c6099" 8bca838]:
{{{
#!CommitTicketReference repository=""
revision="8bca838f4a230b78dd44fc1db3c1b81e444c6099"
Refs #32655 -- Improved error if iter_test_cases() is passed a string.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:9>

Django

unread,
Jun 29, 2021, 3:29:52 PM6/29/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: nobody
Type: | Status: new
Cleanup/optimization |

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak):

* has_patch: 1 => 0


--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:10>

Django

unread,
Jul 15, 2021, 6:30:33 PM7/15/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: Jacob
Type: | Walls
Cleanup/optimization | Status: assigned

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* owner: nobody => Jacob Walls
* status: new => assigned


* has_patch: 0 => 1


Comment:

[https://github.com/django/django/pull/14647 Deprecation PR]

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:11>

Django

unread,
Jul 16, 2021, 1:46:52 AM7/16/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: Jacob
Type: | Walls
Cleanup/optimization | Status: assigned
Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1

Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak):

* needs_better_patch: 0 => 1


--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:12>

Django

unread,
Jul 16, 2021, 8:59:04 AM7/16/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: Jacob
Type: | Walls
Cleanup/optimization | Status: assigned
Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
iter_test_cases,DiscoverRunner,extra_tests|
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* needs_better_patch: 1 => 0


--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:13>

Django

unread,
Jul 16, 2021, 2:48:12 PM7/16/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: Jacob
Type: | Walls
Cleanup/optimization | Status: assigned
Component: Testing framework | Version: 4.0
Severity: Normal | Resolution:
Keywords: | Triage Stage: Ready for
iter_test_cases,DiscoverRunner,extra_tests| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak):

* stage: Accepted => Ready for checkin


--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:14>

Django

unread,
Jul 16, 2021, 3:45:25 PM7/16/21
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: Jacob
Type: | Walls
Cleanup/optimization | Status: closed

Component: Testing framework | Version: 4.0
Severity: Normal | Resolution: fixed

Keywords: | Triage Stage: Ready for
iter_test_cases,DiscoverRunner,extra_tests| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak <felisiak.mariusz@…>):

* status: assigned => closed
* resolution: => fixed


Comment:

In [changeset:"56f9579105c324ff15250423bf9f8bdf1634cfb4" 56f9579]:
{{{
#!CommitTicketReference repository=""
revision="56f9579105c324ff15250423bf9f8bdf1634cfb4"
Fixed #32655 -- Deprecated extra_tests argument for
DiscoverRunner.build_suite()/run_tests().
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:15>

Django

unread,
Jan 17, 2023, 5:49:46 AM1/17/23
to django-...@googlegroups.com
#32655: Deprecate DiscoverRunner.build_suite()'s extra_tests argument.
-------------------------------------+-------------------------------------
Reporter: Chris Jerdonek | Owner: Jacob
Type: | Walls
Cleanup/optimization | Status: closed
Component: Testing framework | Version: 4.0
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
iter_test_cases,DiscoverRunner,extra_tests| checkin
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Mariusz Felisiak <felisiak.mariusz@…>):

In [changeset:"43b01300b7c7e9d24b549a408010e52a09726060" 43b01300]:
{{{
#!CommitTicketReference repository=""
revision="43b01300b7c7e9d24b549a408010e52a09726060"
Refs #32655 -- Removed extra_tests argument for
DiscoverRunner.build_suite()/run_tests().

Per deprecation timeline.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/32655#comment:16>

Reply all
Reply to author
Forward
0 new messages