Skip to content

Commit c155d14

Browse files
committed
Be more lenient to stream=None being passed into TestResult classes
Subtle regression from 2.8.0, which added verbosity support
1 parent 90d2f33 commit c155d14

File tree

3 files changed

+137
-62
lines changed

3 files changed

+137
-62
lines changed

NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ Improvements
1717
* Support binary contents in ``FileContains`` matcher.
1818
(Jelmer Vernooij, #538)
1919

20+
* Allow stream=None to be passed to various TestResult
21+
classes that now support verbosity; fixes a regression
22+
from 2.8.0.
23+
(Jelmer Vernooij)
24+
2025
2.8.1
2126
~~~~~
2227

tests/test_testresult.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,65 @@ def run_tests():
20782078
),
20792079
)
20802080

2081+
def test_none_stream_is_accepted(self):
2082+
"""TextTestResult should accept None as stream for backward compatibility."""
2083+
result = TextTestResult(None)
2084+
test = make_test()
2085+
# Should not raise AttributeError
2086+
result.startTestRun()
2087+
result.startTest(test)
2088+
result.addSuccess(test)
2089+
result.stopTest(test)
2090+
result.stopTestRun()
2091+
2092+
def test_none_stream_with_failure(self):
2093+
"""TextTestResult with None stream should handle failures."""
2094+
result = TextTestResult(None)
2095+
test = make_failing_test()
2096+
# Should not raise AttributeError
2097+
test.run(result)
2098+
self.assertEqual(1, len(result.failures))
2099+
2100+
def test_verbosity_zero_produces_no_output(self):
2101+
"""TextTestResult with verbosity=0 should produce no per-test output."""
2102+
stream = io.StringIO()
2103+
result = TextTestResult(stream, verbosity=0)
2104+
test = make_test()
2105+
result.startTestRun()
2106+
result.startTest(test)
2107+
result.addSuccess(test)
2108+
result.stopTest(test)
2109+
# Get output up to stopTestRun (which still outputs summary)
2110+
output_before_stop = stream.getvalue()
2111+
# Should only have "Tests running...\n" from startTestRun
2112+
self.assertEqual("Tests running...\n", output_before_stop)
2113+
2114+
def test_verbosity_zero_allows_subclass_control(self):
2115+
"""Subclasses can use verbosity=0 to control their own output."""
2116+
2117+
class CustomResult(TextTestResult):
2118+
def __init__(self, stream):
2119+
super().__init__(stream, verbosity=0)
2120+
2121+
def addSuccess(self, test, details=None):
2122+
super().addSuccess(test, details)
2123+
self.stream.write("CUSTOM_SUCCESS_MARKER\n")
2124+
2125+
stream = io.StringIO()
2126+
result = CustomResult(stream)
2127+
test = make_test()
2128+
result.startTestRun()
2129+
result.startTest(test)
2130+
result.addSuccess(test)
2131+
result.stopTest(test)
2132+
output = stream.getvalue()
2133+
# Should have custom output but not parent's dot or "ok"
2134+
self.assertIn("CUSTOM_SUCCESS_MARKER\n", output)
2135+
self.assertNotIn("ok\n", output)
2136+
# Count dots - "Tests running..." has 3 dots, should not have a 4th
2137+
# from the success indicator
2138+
self.assertEqual(output.count("."), 3)
2139+
20812140

20822141
class TestThreadSafeForwardingResult(TestCase):
20832142
"""Tests for `TestThreadSafeForwardingResult`."""

testtools/testresult/real.py

Lines changed: 73 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,8 @@ def _delta_to_float(self, a_timedelta, precision):
12441244
)
12451245

12461246
def _show_list(self, label, error_list):
1247+
if self.stream is None:
1248+
return
12471249
for test, output in error_list:
12481250
self.stream.write(self.sep1)
12491251
self.stream.write(f"{label}: {test.id()}\n")
@@ -1252,69 +1254,76 @@ def _show_list(self, label, error_list):
12521254

12531255
def startTest(self, test):
12541256
super().startTest(test)
1255-
if self.verbosity >= 2:
1257+
if self.stream is not None and self.verbosity >= 2:
12561258
self.stream.write(f"{test.id()} ... ")
12571259
self.stream.flush()
12581260

12591261
def addSuccess(self, test, details=None):
12601262
super().addSuccess(test, details=details)
1261-
if self.verbosity == 1:
1262-
self.stream.write(".")
1263-
self.stream.flush()
1264-
self._progress_printed = True
1265-
elif self.verbosity >= 2:
1266-
self.stream.write("ok\n")
1267-
self.stream.flush()
1263+
if self.stream is not None:
1264+
if self.verbosity == 1:
1265+
self.stream.write(".")
1266+
self.stream.flush()
1267+
self._progress_printed = True
1268+
elif self.verbosity >= 2:
1269+
self.stream.write("ok\n")
1270+
self.stream.flush()
12681271

12691272
def addError(self, test, err=None, details=None):
12701273
super().addError(test, err=err, details=details)
1271-
if self.verbosity == 1:
1272-
self.stream.write("E")
1273-
self.stream.flush()
1274-
elif self.verbosity >= 2:
1275-
self.stream.write("ERROR\n")
1276-
self.stream.flush()
1274+
if self.stream is not None:
1275+
if self.verbosity == 1:
1276+
self.stream.write("E")
1277+
self.stream.flush()
1278+
elif self.verbosity >= 2:
1279+
self.stream.write("ERROR\n")
1280+
self.stream.flush()
12771281

12781282
def addFailure(self, test, err=None, details=None):
12791283
super().addFailure(test, err=err, details=details)
1280-
if self.verbosity == 1:
1281-
self.stream.write("F")
1282-
self.stream.flush()
1283-
elif self.verbosity >= 2:
1284-
self.stream.write("FAIL\n")
1285-
self.stream.flush()
1284+
if self.stream is not None:
1285+
if self.verbosity == 1:
1286+
self.stream.write("F")
1287+
self.stream.flush()
1288+
elif self.verbosity >= 2:
1289+
self.stream.write("FAIL\n")
1290+
self.stream.flush()
12861291

12871292
def addSkip(self, test, reason=None, details=None):
12881293
super().addSkip(test, reason=reason, details=details)
1289-
if self.verbosity == 1:
1290-
self.stream.write("s")
1291-
self.stream.flush()
1292-
elif self.verbosity >= 2:
1293-
self.stream.write(f"skipped {reason!r}\n")
1294-
self.stream.flush()
1294+
if self.stream is not None:
1295+
if self.verbosity == 1:
1296+
self.stream.write("s")
1297+
self.stream.flush()
1298+
elif self.verbosity >= 2:
1299+
self.stream.write(f"skipped {reason!r}\n")
1300+
self.stream.flush()
12951301

12961302
def addExpectedFailure(self, test, err=None, details=None):
12971303
super().addExpectedFailure(test, err=err, details=details)
1298-
if self.verbosity == 1:
1299-
self.stream.write("x")
1300-
self.stream.flush()
1301-
elif self.verbosity >= 2:
1302-
self.stream.write("expected failure\n")
1303-
self.stream.flush()
1304+
if self.stream is not None:
1305+
if self.verbosity == 1:
1306+
self.stream.write("x")
1307+
self.stream.flush()
1308+
elif self.verbosity >= 2:
1309+
self.stream.write("expected failure\n")
1310+
self.stream.flush()
13041311

13051312
def addUnexpectedSuccess(self, test, details=None):
13061313
super().addUnexpectedSuccess(test, details=details)
1307-
if self.verbosity == 1:
1308-
self.stream.write("u")
1309-
self.stream.flush()
1310-
elif self.verbosity >= 2:
1311-
self.stream.write("unexpected success\n")
1312-
self.stream.flush()
1314+
if self.stream is not None:
1315+
if self.verbosity == 1:
1316+
self.stream.write("u")
1317+
self.stream.flush()
1318+
elif self.verbosity >= 2:
1319+
self.stream.write("unexpected success\n")
1320+
self.stream.flush()
13131321

13141322
def startTestRun(self):
13151323
super().startTestRun()
13161324
self.__start = self._now()
1317-
self.stream.write("Tests running...\n")
1325+
if self.stream is not None:
1326+
self.stream.write("Tests running...\n")
13181327

13191328
def stopTestRun(self):
13201329
if self.testsRun != 1:
@@ -1324,31 +1333,33 @@ def stopTestRun(self):
13241333
stop = self._now()
13251334
self._show_list("ERROR", self.errors)
13261335
self._show_list("FAIL", self.failures)
1327-
for test in self.unexpectedSuccesses:
1336+
if self.stream is not None:
1337+
for test in self.unexpectedSuccesses:
1338+
self.stream.write(
1339+
f"{self.sep1}UNEXPECTED SUCCESS: {test.id()}\n{self.sep2}"
1340+
)
1341+
# Add newline(s) before summary
1342+
# If we printed progress indicators (dots), add extra newline
1343+
if self._progress_printed:
1344+
self.stream.write("\n\n")
1345+
else:
1346+
self.stream.write("\n")
13281347
self.stream.write(
1329-
f"{self.sep1}UNEXPECTED SUCCESS: {test.id()}\n{self.sep2}"
1330-
)
1331-
# Add newline(s) before summary
1332-
# If we printed progress indicators (dots), add extra newline
1333-
if self._progress_printed:
1334-
self.stream.write("\n\n")
1335-
else:
1336-
self.stream.write("\n")
1337-
self.stream.write(
1338-
f"Ran {self.testsRun} test{plural} in "
1339-
f"{self._delta_to_float(stop - self.__start, 3):.3f}s\n"
1340-
)
1341-
if self.wasSuccessful():
1342-
self.stream.write("OK\n")
1343-
else:
1344-
self.stream.write("FAILED (")
1345-
details = []
1346-
failure_count = sum(
1347-
len(x) for x in (self.failures, self.errors, self.unexpectedSuccesses)
1348+
f"Ran {self.testsRun} test{plural} in "
1349+
f"{self._delta_to_float(stop - self.__start, 3):.3f}s\n"
13481350
)
1349-
details.append(f"failures={failure_count}")
1350-
self.stream.write(", ".join(details))
1351-
self.stream.write(")\n")
1351+
if self.wasSuccessful():
1352+
self.stream.write("OK\n")
1353+
else:
1354+
self.stream.write("FAILED (")
1355+
details = []
1356+
failure_count = sum(
1357+
len(x)
1358+
for x in (self.failures, self.errors, self.unexpectedSuccesses)
1359+
)
1360+
details.append(f"failures={failure_count}")
1361+
self.stream.write(", ".join(details))
1362+
self.stream.write(")\n")
13521363
super().stopTestRun()
13531364

13541365

0 commit comments

Comments
 (0)