From f2be8d4d35899809bc28b73b9cc66ffdf5cb3ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:08:08 +0100 Subject: [PATCH 1/4] subtests: fix inconcistent handling of non-string messages --- AUTHORS | 1 + changelog/14195.bugfix.rst | 1 + src/_pytest/unittest.py | 3 ++- testing/test_subtests.py | 50 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 changelog/14195.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 81e08c07488..6885ec6e793 100644 --- a/AUTHORS +++ b/AUTHORS @@ -384,6 +384,7 @@ Ralf Schmitt Ralph Giles Ram Rachum Ran Benita +Randy Döring Raphael Castaneda Raphael Pierzina Rafal Semik diff --git a/changelog/14195.bugfix.rst b/changelog/14195.bugfix.rst new file mode 100644 index 00000000000..5091fd8b160 --- /dev/null +++ b/changelog/14195.bugfix.rst @@ -0,0 +1 @@ +Fixed an issue where non-string messages passed to `unittest.TestCase.subTest()` are not printed. diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 23b92724f5d..ecd540a742e 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -16,6 +16,7 @@ from typing import Any from typing import TYPE_CHECKING from unittest import TestCase +from unittest.case import _subtest_msg_sentinel # type: ignore[attr-defined] import _pytest._code from _pytest._code import ExceptionInfo @@ -427,7 +428,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, diff --git a/testing/test_subtests.py b/testing/test_subtests.py index c480bb01658..fd964c1a05b 100644 --- a/testing/test_subtests.py +++ b/testing/test_subtests.py @@ -373,6 +373,30 @@ def test_foo(subtests): ) +def test_msg_not_a_string( + pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch +) -> None: + 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() 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.""" @@ -622,6 +646,32 @@ 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: + 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() test_msg_not_a_string.py::T::test_no_msg - AssertionError: subtest failure", + ] + ) + class TestCapture: def create_file(self, pytester: pytest.Pytester) -> None: From ef2ad8bb176d3e7cb1c9d25a8e6fc9881e20b8e3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 16 Feb 2026 11:31:10 -0300 Subject: [PATCH 2/4] Import symbol locally --- src/_pytest/unittest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index ecd540a742e..31be8847821 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -16,7 +16,6 @@ from typing import Any from typing import TYPE_CHECKING from unittest import TestCase -from unittest.case import _subtest_msg_sentinel # type: ignore[attr-defined] import _pytest._code from _pytest._code import ExceptionInfo @@ -410,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(): From e5065a57b9a3a5b93c85ce43142dd9741a0717bf Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 16 Feb 2026 11:35:58 -0300 Subject: [PATCH 3/4] Add test docstrings --- testing/test_subtests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/testing/test_subtests.py b/testing/test_subtests.py index fd964c1a05b..bc16c879fcb 100644 --- a/testing/test_subtests.py +++ b/testing/test_subtests.py @@ -376,6 +376,12 @@ 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( """ @@ -649,6 +655,7 @@ def test_foo(self): 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 78b9aec2d3079acc5c9156e589ca74ab26f89be3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 16 Feb 2026 11:36:37 -0300 Subject: [PATCH 4/4] Fix grammar in changelog --- changelog/14195.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/14195.bugfix.rst b/changelog/14195.bugfix.rst index 5091fd8b160..29ae149dd98 100644 --- a/changelog/14195.bugfix.rst +++ b/changelog/14195.bugfix.rst @@ -1 +1 @@ -Fixed an issue where non-string messages passed to `unittest.TestCase.subTest()` are not printed. +Fixed an issue where non-string messages passed to `unittest.TestCase.subTest()` were not printed.