Skip to main content

Lib/unittest/runner.py

cpython 3.14 @ ab2d84fe1023/Lib/unittest/runner.py

Lib/unittest/runner.py contains two public classes and one private helper. _WritelnDecorator wraps a stream to add a writeln convenience method. TextTestResult extends result.TestResult to write dots or verbose lines to that stream. TextTestRunner ties them together: it creates the result, runs the suite, and prints the final summary.

Map​

LinesSymbolRole
16-29_WritelnDecoratorWraps a file-like stream; adds writeln(arg=None)
32-178TextTestResultStream-printing result; verbosity controls dot vs full-name mode
40-50TextTestResult.__init__Sets showAll, dots, _theme, _newline, durations
52-57getDescriptionReturns test name, optionally with first docstring line
59-65startTestWrites test name in verbose mode before the test runs
67-78_write_statusShared writer called by all addXxx methods
96-121addSuccess, addError, addFailureWrite status character or full label
123-130addSkipWrites skipped reason or s
152-178printErrors, printErrorListPrints full tracebacks after the run
181-240TextTestRunnerRunner; owns run() and _makeResult()
189-208TextTestRunner.__init__Stores verbosity, buffer, warnings, failfast, durations
210-217_makeResultInstantiates resultclass with stream, descriptions, verbosity
241-313runCreates result, times the suite, prints summary

Reading​

_WritelnDecorator and stream wrapping​

All attribute access on the decorator (except stream itself) is forwarded to the wrapped stream via __getattr__. The stream guard prevents infinite recursion if the stream is inspected before __init__ completes.

# CPython: Lib/unittest/runner.py:16 _WritelnDecorator
class _WritelnDecorator(object):
def __init__(self, stream):
self.stream = stream

def __getattr__(self, attr):
if attr in ('stream', '__getstate__'):
raise AttributeError(attr)
return getattr(self.stream, attr)

def writeln(self, arg=None):
if arg:
self.write(arg)
self.write('\n')

TextTestRunner.__init__ wraps sys.stderr (or a caller-supplied stream) in _WritelnDecorator before passing it to TextTestResult, so the result can call self.stream.writeln() without caring whether the underlying stream supports that method.

Verbosity modes and ANSI theming​

showAll (verbosity 2 and above) prints the full test name and result on each line. dots (verbosity 1) prints a single character. Both can be false at verbosity 0, which produces no per-test output. _theme is a namespace of ANSI escape sequences from _colorize.get_theme; it is used in every addXxx method when writing to a TTY.

# CPython: Lib/unittest/runner.py:40 TextTestResult.__init__
def __init__(self, stream, descriptions, verbosity, *, durations=None):
super(TextTestResult, self).__init__(stream, descriptions, verbosity)
self.stream = stream
self.showAll = verbosity > 1
self.dots = verbosity == 1
self.descriptions = descriptions
self._theme = get_theme(tty_file=stream).unittest
self._newline = True
self.durations = durations

addSuccess, addFailure, addError, and addSkip​

Each method calls super() first to append to self.errors or self.failures, then emits to the stream. The theme colours wrap each status string so output is coloured when writing to a TTY. In CPython 3.14 these strings changed from plain text ("ok", "ERROR") to ANSI-wrapped forms.

# CPython: Lib/unittest/runner.py:96 addSuccess
def addSuccess(self, test):
super(TextTestResult, self).addSuccess(test)
t = self._theme
if self.showAll:
self._write_status(test, f"{t.passed}ok{t.reset}")
elif self.dots:
self.stream.write(f"{t.passed}.{t.reset}")
self.stream.flush()

# CPython: Lib/unittest/runner.py:105 addError
def addError(self, test, err):
super(TextTestResult, self).addError(test, err)
t = self._theme
if self.showAll:
self._write_status(test, f"{t.fail}ERROR{t.reset}")
elif self.dots:
self.stream.write(f"{t.fail}E{t.reset}")
self.stream.flush()

# CPython: Lib/unittest/runner.py:113 addSkip
def addSkip(self, test, reason):
super(TextTestResult, self).addSkip(test, reason)
if self.showAll:
self._write_status(test, "skipped {0!r}".format(reason))
elif self.dots:
self.stream.write("s")
self.stream.flush()

TextTestRunner.run: timing and failfast​

time.perf_counter measures wall-clock time around the suite call. failfast is set on the result object before the suite runs; TestResult.addFailure and TestResult.addError check it and call self.stop() if it is set, which sets shouldStop = True and causes the loader to break out of its iteration.

# CPython: Lib/unittest/runner.py:241 TextTestRunner.run
def run(self, test):
result = self._makeResult()
registerResult(result)
result.failfast = self.failfast
result.buffer = self.buffer
result.tb_locals = self.tb_locals
with warnings.catch_warnings():
...
start_time = time.perf_counter()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun()
try:
test(result)
finally:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()
stop_time = time.perf_counter()
time_taken = stop_time - start_time
result.printErrors()
run = result.testsRun
self.stream.writeln("Ran %d test%s in %.3fs" %
(run, run != 1 and "s" or "", time_taken))
if not result.wasSuccessful():
self.stream.write(f"{t.fail_info}FAILED{t.reset} ...")
else:
self.stream.write(f"{t.passed}OK{t.reset}")
return result

test(result) works because TestSuite.__call__ delegates to TestSuite.run(result). The startTestRun/stopTestRun methods are called via getattr rather than a direct call so that result classes that do not implement them are not penalised. The result object is returned so callers can inspect it programmatically without re-running tests.

gopy notes​

Status: not yet ported.

Planned package path: module/unittest/.

_WritelnDecorator maps to a thin Go struct wrapping an io.Writer with an extra Writeln method. The TextTestResult verbosity logic is straightforward to port since it is conditional writes to a stream. The ANSI theme integration requires either a stub or a Go equivalent that checks isatty; the _colorize module is itself unported. Timing via time.perf_counter maps to time.Now() in Go, and the delta to time.Since. The startTestRun/stopTestRun optional protocol maps to a Go interface with a default no-op implementation.