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
2 changes: 1 addition & 1 deletion .testr.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run $LISTOPT $IDOPTION testtools.tests.test_suite
test_command=${PYTHON:-python3} -m subunit.run $LISTOPT $IDOPTION testtools.tests.test_suite
test_id_option=--load-list $IDFILE
test_list_option=--list
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ Changes
Improvements
------------

* Implemented verbosity support for the test runner. The ``-v`` (``--verbose``)
flag is now functional and properly controls output verbosity. Verbosity
levels: 0 (quiet), 1 (dots, default), 2 (test names). This applies to both
normal test runs and discovery mode. (LP: #872906)

* Add support for Python 3.12's ``addDuration`` method and ``collectedDurations``
attribute in ``TestResult`` classes. (Jelmer Vernooij, #2045171)

Expand Down
5 changes: 4 additions & 1 deletion testtools/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@ def __init__(
):
"""Create a TestToolsTestRunner.

:param verbosity: Ignored.
:param verbosity: Verbosity level. 0 for quiet, 1 for normal (dots, default),
2 for verbose (test names).
:param failfast: Stop running tests at the first failure.
:param buffer: Ignored.
:param stdout: Stream to use for stdout.
:param tb_locals: If True include local variables in tracebacks.
"""
self.verbosity = verbosity if verbosity is not None else 1
self.failfast = failfast
if stdout is None:
stdout = sys.stdout
Expand All @@ -102,6 +104,7 @@ def run(self, test):
unicode_output_stream(self.stdout),
failfast=self.failfast,
tb_locals=self.tb_locals,
verbosity=self.verbosity,
)
result.startTestRun()
try:
Expand Down
82 changes: 79 additions & 3 deletions testtools/testresult/real.py
Original file line number Diff line number Diff line change
Expand Up @@ -1186,12 +1186,21 @@ def wasSuccessful(self):
class TextTestResult(TestResult):
"""A TestResult which outputs activity to a text stream."""

def __init__(self, stream, failfast=False, tb_locals=False):
"""Construct a TextTestResult writing to stream."""
def __init__(self, stream, failfast=False, tb_locals=False, verbosity=1):
"""Construct a TextTestResult writing to stream.

:param stream: A file-like object to write results to.
:param failfast: Stop after the first failure.
:param tb_locals: If True include local variables in tracebacks.
:param verbosity: Verbosity level. 0 for quiet, 1 for normal (dots, default),
2 for verbose (test names).
"""
super().__init__(failfast=failfast, tb_locals=tb_locals)
self.stream = stream
self.sep1 = "=" * 70 + "\n"
self.sep2 = "-" * 70 + "\n"
self.verbosity = verbosity
self._progress_printed = False

def _delta_to_float(self, a_timedelta, precision):
# This calls ceiling to ensure that the most pessimistic view of time
Expand All @@ -1218,6 +1227,67 @@ def _show_list(self, label, error_list):
self.stream.write(self.sep2)
self.stream.write(output)

def startTest(self, test):
super().startTest(test)
if self.verbosity >= 2:
self.stream.write(f"{test.id()} ... ")
self.stream.flush()

def addSuccess(self, test, details=None):
super().addSuccess(test, details=details)
if self.verbosity == 1:
self.stream.write(".")
self.stream.flush()
self._progress_printed = True
elif self.verbosity >= 2:
self.stream.write("ok\n")
self.stream.flush()

def addError(self, test, err=None, details=None):
super().addError(test, err=err, details=details)
if self.verbosity == 1:
self.stream.write("E")
self.stream.flush()
elif self.verbosity >= 2:
self.stream.write("ERROR\n")
self.stream.flush()

def addFailure(self, test, err=None, details=None):
super().addFailure(test, err=err, details=details)
if self.verbosity == 1:
self.stream.write("F")
self.stream.flush()
elif self.verbosity >= 2:
self.stream.write("FAIL\n")
self.stream.flush()

def addSkip(self, test, reason=None, details=None):
super().addSkip(test, reason=reason, details=details)
if self.verbosity == 1:
self.stream.write("s")
self.stream.flush()
elif self.verbosity >= 2:
self.stream.write(f"skipped {reason!r}\n")
self.stream.flush()

def addExpectedFailure(self, test, err=None, details=None):
super().addExpectedFailure(test, err=err, details=details)
if self.verbosity == 1:
self.stream.write("x")
self.stream.flush()
elif self.verbosity >= 2:
self.stream.write("expected failure\n")
self.stream.flush()

def addUnexpectedSuccess(self, test, details=None):
super().addUnexpectedSuccess(test, details=details)
if self.verbosity == 1:
self.stream.write("u")
self.stream.flush()
elif self.verbosity >= 2:
self.stream.write("unexpected success\n")
self.stream.flush()

def startTestRun(self):
super().startTestRun()
self.__start = self._now()
Expand All @@ -1235,8 +1305,14 @@ def stopTestRun(self):
self.stream.write(
f"{self.sep1}UNEXPECTED SUCCESS: {test.id()}\n{self.sep2}"
)
# Add newline(s) before summary
# If we printed progress indicators (dots), add extra newline
if self._progress_printed:
self.stream.write("\n\n")
else:
self.stream.write("\n")
self.stream.write(
f"\nRan {self.testsRun} test{plural} in "
f"Ran {self.testsRun} test{plural} in "
f"{self._delta_to_float(stop - self.__start, 3):.3f}s\n"
)
if self.wasSuccessful():
Expand Down
26 changes: 26 additions & 0 deletions testtools/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ def test_stdout_honoured(self):
out.getvalue(),
MatchesRegex(
"""Tests running...
\\.\\.

Ran 2 tests in \\d.\\d\\d\\ds
OK
Expand Down Expand Up @@ -466,6 +467,31 @@ def test_issue_16662(self):
out.getvalue(),
)

def test_runner_verbosity_respected(self):
# Test that verbosity parameter is actually used by the runner
out = io.StringIO()
runner = run.TestToolsTestRunner(verbosity=2, stdout=out)
self.assertEqual(2, runner.verbosity)

def test_discover_verbosity(self):
# Test that -v flag in discover mode sets verbosity
self.useFixture(SampleTestFixture())
out = io.StringIO()
exc = self.assertRaises(
SystemExit,
run.main,
argv=[
"prog",
"discover",
"-v",
"-s",
self.useFixture(fixtures.TempDir()).path,
],
stdout=out,
)
# The output should show individual test names when verbose
self.assertEqual((0,), exc.args)


def test_suite():
from unittest import TestLoader
Expand Down