Skip to content

Commit 8950ca3

Browse files
subtests: fix inconcistent handling of non-string messages (#14196)
Fixes #14195 --------- Co-authored-by: Bruno Oliveira <bruno@pytest.org>
1 parent 7bc02cc commit 8950ca3

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ Ralf Schmitt
384384
Ralph Giles
385385
Ram Rachum
386386
Ran Benita
387+
Randy Döring
387388
Raphael Castaneda
388389
Raphael Pierzina
389390
Rafal Semik

changelog/14195.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed an issue where non-string messages passed to `unittest.TestCase.subTest()` were not printed.

src/_pytest/unittest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,10 @@ def addSubTest(
409409
| tuple[type[BaseException], BaseException, TracebackType]
410410
| None,
411411
) -> None:
412+
# Importing this private symbol locally in case this symbol is renamed/removed in the future; importing
413+
# it globally would break pytest entirely, importing it locally only will break unittests using `addSubTest`.
414+
from unittest.case import _subtest_msg_sentinel # type: ignore[attr-defined]
415+
412416
exception_info: ExceptionInfo[BaseException] | None
413417
match exc_info:
414418
case tuple():
@@ -427,7 +431,7 @@ def addSubTest(
427431
when="call",
428432
_ispytest=True,
429433
)
430-
msg = test._message if isinstance(test._message, str) else None # type: ignore[attr-defined]
434+
msg = None if test._message is _subtest_msg_sentinel else str(test._message) # type: ignore[attr-defined]
431435
report = self.ihook.pytest_runtest_makereport(item=self, call=call_info)
432436
sub_report = SubtestReport._new(
433437
report,

testing/test_subtests.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,36 @@ def test_foo(subtests):
373373
)
374374

375375

376+
def test_msg_not_a_string(
377+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
378+
) -> None:
379+
"""
380+
Using a non-string in subtests.test() should still show it in the terminal (#14195).
381+
382+
Note: this was not a problem originally with the subtests fixture, only with TestCase.subTest; this test
383+
was added for symmetry.
384+
"""
385+
monkeypatch.setenv("COLUMNS", "120")
386+
pytester.makepyfile(
387+
"""
388+
def test_int_msg(subtests):
389+
with subtests.test(42):
390+
assert False, "subtest failure"
391+
392+
def test_no_msg(subtests):
393+
with subtests.test():
394+
assert False, "subtest failure"
395+
"""
396+
)
397+
result = pytester.runpytest()
398+
result.stdout.fnmatch_lines(
399+
[
400+
"SUBFAILED[[]42[]] test_msg_not_a_string.py::test_int_msg - AssertionError: subtest failure",
401+
"SUBFAILED(<subtest>) test_msg_not_a_string.py::test_no_msg - AssertionError: subtest failure",
402+
]
403+
)
404+
405+
376406
@pytest.mark.parametrize("flag", ["--last-failed", "--stepwise"])
377407
def test_subtests_last_failed_step_wise(pytester: pytest.Pytester, flag: str) -> None:
378408
"""Check that --last-failed and --step-wise correctly rerun tests with failed subtests."""
@@ -622,6 +652,33 @@ def test_foo(self):
622652
"SUBSKIPPED[[]subtest 1[]] [[]1[]] *.py:*: skip subtest 1"
623653
)
624654

655+
def test_msg_not_a_string(
656+
self, pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
657+
) -> None:
658+
"""Using a non-string in TestCase.subTest should still show it in the terminal (#14195)."""
659+
monkeypatch.setenv("COLUMNS", "120")
660+
pytester.makepyfile(
661+
"""
662+
from unittest import TestCase
663+
664+
class T(TestCase):
665+
def test_int_msg(self):
666+
with self.subTest(42):
667+
assert False, "subtest failure"
668+
669+
def test_no_msg(self):
670+
with self.subTest():
671+
assert False, "subtest failure"
672+
"""
673+
)
674+
result = pytester.runpytest()
675+
result.stdout.fnmatch_lines(
676+
[
677+
"SUBFAILED[[]42[]] test_msg_not_a_string.py::T::test_int_msg - AssertionError: subtest failure",
678+
"SUBFAILED(<subtest>) test_msg_not_a_string.py::T::test_no_msg - AssertionError: subtest failure",
679+
]
680+
)
681+
625682

626683
class TestCapture:
627684
def create_file(self, pytester: pytest.Pytester) -> None:

0 commit comments

Comments
 (0)