Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ Ralf Schmitt
Ralph Giles
Ram Rachum
Ran Benita
Randy Döring
Raphael Castaneda
Raphael Pierzina
Rafal Semik
Expand Down
1 change: 1 addition & 0 deletions changelog/14195.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed an issue where non-string messages passed to `unittest.TestCase.subTest()` were not printed.
6 changes: 5 additions & 1 deletion src/_pytest/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@ def addSubTest(
| tuple[type[BaseException], BaseException, TracebackType]
| None,
) -> None:
# Importing this private symbol locally in case this symbol is renamed/removed in the future; importing
# it globally would break pytest entirely, importing it locally only will break unittests using `addSubTest`.
from unittest.case import _subtest_msg_sentinel # type: ignore[attr-defined]

exception_info: ExceptionInfo[BaseException] | None
match exc_info:
case tuple():
Expand All @@ -427,7 +431,7 @@ def addSubTest(
when="call",
_ispytest=True,
)
msg = test._message if isinstance(test._message, str) else None # type: ignore[attr-defined]
msg = None if test._message is _subtest_msg_sentinel else str(test._message) # type: ignore[attr-defined]
report = self.ihook.pytest_runtest_makereport(item=self, call=call_info)
sub_report = SubtestReport._new(
report,
Expand Down
57 changes: 57 additions & 0 deletions testing/test_subtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,36 @@ def test_foo(subtests):
)


def test_msg_not_a_string(
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
) -> None:
"""
Using a non-string in subtests.test() should still show it in the terminal (#14195).

Note: this was not a problem originally with the subtests fixture, only with TestCase.subTest; this test
was added for symmetry.
"""
monkeypatch.setenv("COLUMNS", "120")
pytester.makepyfile(
"""
def test_int_msg(subtests):
with subtests.test(42):
assert False, "subtest failure"

def test_no_msg(subtests):
with subtests.test():
assert False, "subtest failure"
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"SUBFAILED[[]42[]] test_msg_not_a_string.py::test_int_msg - AssertionError: subtest failure",
"SUBFAILED(<subtest>) test_msg_not_a_string.py::test_no_msg - AssertionError: subtest failure",
]
)


@pytest.mark.parametrize("flag", ["--last-failed", "--stepwise"])
def test_subtests_last_failed_step_wise(pytester: pytest.Pytester, flag: str) -> None:
"""Check that --last-failed and --step-wise correctly rerun tests with failed subtests."""
Expand Down Expand Up @@ -619,6 +649,33 @@ def test_foo(self):
"SUBSKIPPED[[]subtest 1[]] [[]1[]] *.py:*: skip subtest 1"
)

def test_msg_not_a_string(
self, pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Using a non-string in TestCase.subTest should still show it in the terminal (#14195)."""
monkeypatch.setenv("COLUMNS", "120")
pytester.makepyfile(
"""
from unittest import TestCase

class T(TestCase):
def test_int_msg(self):
with self.subTest(42):
assert False, "subtest failure"

def test_no_msg(self):
with self.subTest():
assert False, "subtest failure"
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"SUBFAILED[[]42[]] test_msg_not_a_string.py::T::test_int_msg - AssertionError: subtest failure",
"SUBFAILED(<subtest>) test_msg_not_a_string.py::T::test_no_msg - AssertionError: subtest failure",
]
)


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