diff --git a/doc/for-framework-folk.rst b/doc/for-framework-folk.rst index c289d739..e11ab06e 100644 --- a/doc/for-framework-folk.rst +++ b/doc/for-framework-folk.rst @@ -362,7 +362,7 @@ Test Doubles ------------ In testtools.testresult.doubles there are several test doubles that testtools -uses for its own testing: ``Python3TestResult``, and ``ExtendedTestResult``. +uses for its own testing: ``LoggingTestResult``, and ``ExtendedTestResult``. These TestResult objects implement a single variation of the TestResult API each, and log activity to a list ``self._events``. These are made available for the convenience of people writing their own extensions. diff --git a/tests/test_testcase.py b/tests/test_testcase.py index 77c66718..e7f0bcdc 100644 --- a/tests/test_testcase.py +++ b/tests/test_testcase.py @@ -48,7 +48,8 @@ ) from testtools.testresult.doubles import ( ExtendedTestResult, - Python3TestResult, + LogEvent, + TestResult, ) from .helpers import ( @@ -1270,7 +1271,7 @@ def test(self): def test_raising__UnexpectedSuccess_py3(self): case = self.make_unexpected_case() - result = Python3TestResult() + result = TestResult() case.run(result) case = result._events[0][1] self.assertEqual( @@ -1775,8 +1776,8 @@ class SkippingTest(TestCase): def test_that_raises_skipException(self): self.skipTest("skipping this test") - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_that_raises_skipException") test.run(result) case = result._events[0][1] @@ -1796,8 +1797,8 @@ class SkippingTest(TestCase): def test_that_raises_skipException(self): self.skipTest("skipping this test") - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_that_raises_skipException") test.run(result) case = result._events[0][1] @@ -1819,8 +1820,8 @@ def setUp(self): def test_that_raises_skipException(self): pass - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_that_raises_skipException") test.run(result) self.assertEqual("addSkip", events[1][0]) @@ -1830,8 +1831,8 @@ class SkippingTest(TestCase): def test_that_raises_skipException(self): raise self.skipException("skipping this test") - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_that_raises_skipException") test.run(result) self.assertEqual("addSkip", events[1][0]) @@ -1842,8 +1843,8 @@ class SkippingTest(TestCase): def test_that_is_decorated_with_skip(self): self.fail() - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_that_is_decorated_with_skip") test.run(result) self.assertEqual("addSkip", events[1][0]) @@ -1854,8 +1855,8 @@ class SkippingTest(TestCase): def test_that_is_decorated_with_skipIf(self): self.fail() - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_that_is_decorated_with_skipIf") test.run(result) self.assertEqual("addSkip", events[1][0]) @@ -1866,8 +1867,8 @@ class SkippingTest(TestCase): def test_that_is_decorated_with_skipUnless(self): self.fail() - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_that_is_decorated_with_skipUnless") test.run(result) self.assertEqual("addSkip", events[1][0]) @@ -1882,14 +1883,14 @@ class SkippingTest(TestCase): class NotSkippingTest(TestCase): test_no_skip = skipIf(False, "skipping this test")(shared) - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) test = SkippingTest("test_skip") test.run(result) self.assertEqual("addSkip", events[1][0]) - events2: list[tuple[str, ...]] = [] - result2 = Python3TestResult(events2) + events2: list[LogEvent] = [] + result2 = TestResult(events2) test2 = NotSkippingTest("test_no_skip") test2.run(result2) self.assertEqual("addFailure", events2[1][0]) @@ -1900,8 +1901,8 @@ class SkippingTest(TestCase): def test_that_is_decorated_with_skip(self): self.fail() - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) try: test = SkippingTest("test_that_is_decorated_with_skip") except unittest.SkipTest: @@ -1915,8 +1916,8 @@ class SkippingTest(TestCase): def test_that_is_decorated_with_skipIf(self): self.fail() - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) try: test = SkippingTest("test_that_is_decorated_with_skipIf") except unittest.SkipTest: @@ -1930,8 +1931,8 @@ class SkippingTest(TestCase): def test_that_is_decorated_with_skipUnless(self): self.fail() - events: list[tuple[str, ...]] = [] - result = Python3TestResult(events) + events: list[LogEvent] = [] + result = TestResult(events) try: test = SkippingTest("test_that_is_decorated_with_skipUnless") except unittest.SkipTest: @@ -2024,7 +2025,7 @@ def method(self): self.assertThat(events, Equals([True])) def test_added_handler_works(self): - events: list[tuple[str, ...]] = [] + events: list[LogEvent] = [] class Case(TestCase): def method(self): @@ -2036,7 +2037,7 @@ def method(self): self.assertThat(events, Equals([an_exc_info])) def test_handler_that_raises_is_not_caught(self): - events: list[tuple[str, ...]] = [] + events: list[LogEvent] = [] class Case(TestCase): def method(self): diff --git a/tests/test_testresult.py b/tests/test_testresult.py index df5e0f82..14169710 100644 --- a/tests/test_testresult.py +++ b/tests/test_testresult.py @@ -64,13 +64,13 @@ ) from testtools.testresult.doubles import ( ExtendedTestResult, - Python3TestResult, TwistedTestResult, _StatusEvent, ) from testtools.testresult.doubles import ( StreamResult as LoggingStreamResult, ) +from testtools.testresult.doubles import TestResult as LoggingTestResult from testtools.testresult.real import ( _details_to_str, _merge_tags, @@ -470,14 +470,14 @@ def makeResult(self): return ExtendedTestResult() -class TestPython3TestResultContract(TestCase, Python3Contract): +class TestLoggingTestResultContract(TestCase, Python3Contract): def makeResult(self): - return Python3TestResult() + return LoggingTestResult() -class TestAdaptedPython3TestResultContract(TestCase, DetailsContract): +class TestAdaptedLoggingTestResultContract(TestCase, DetailsContract): def makeResult(self): - return ExtendedToOriginalDecorator(Python3TestResult()) + return ExtendedToOriginalDecorator(LoggingTestResult()) class TestAdaptedTwistedTestResultContract(TestCase, DetailsContract): @@ -497,12 +497,12 @@ def makeResult(self): return TestResultDecorator(TestResult()) -class TestPython3TestResultDuration(TestCase): - """Tests for addDuration functionality in Python3TestResult.""" +class TestLoggingTestResultDuration(TestCase): + """Tests for addDuration functionality in LoggingTestResult.""" def test_addDuration_logging(self): - # Python3TestResult should log addDuration calls - result = Python3TestResult() + # LoggingTestResult should log addDuration calls + result = LoggingTestResult() test = make_erroring_test() result.addDuration(test, 1.5) @@ -510,8 +510,8 @@ def test_addDuration_logging(self): self.assertIn(expected_call, result._events) def test_addDuration_stores_in_collectedDurations(self): - # Python3TestResult should store durations in collectedDurations - result = Python3TestResult() + # LoggingTestResult should store durations in collectedDurations + result = LoggingTestResult() test = make_erroring_test() result.addDuration(test, 2.3) @@ -2896,7 +2896,7 @@ def testStopTestRun(self): class TestExtendedToOriginalResultDecoratorBase(TestCase): def make_result(self): - self.result = Python3TestResult() + self.result = LoggingTestResult() self.make_converter() def make_converter(self): diff --git a/tests/test_testsuite.py b/tests/test_testsuite.py index b7671a1d..9a917882 100644 --- a/tests/test_testsuite.py +++ b/tests/test_testsuite.py @@ -15,7 +15,7 @@ iterate_tests, ) from testtools.matchers import DocTestMatches, Equals -from testtools.testresult.doubles import StreamResult as LoggingStream +from testtools.testresult.doubles import StreamResult from testtools.testsuite import FixtureSuite, sorted_tests from .helpers import LoggingResult @@ -106,7 +106,7 @@ def split_suite(self, suite): class TestConcurrentStreamTestSuiteRun(TestCase): def test_trivial(self): - result = LoggingStream() + result = StreamResult() test1 = Sample("test_method1") test2 = Sample("test_method2") @@ -196,7 +196,7 @@ def __call__(self): def run(self): pass - result = LoggingStream() + result = StreamResult() def cases(): return [(BrokenTest(), "0")] diff --git a/testtools/runtest.py b/testtools/runtest.py index 243a5dab..f4ef9648 100644 --- a/testtools/runtest.py +++ b/testtools/runtest.py @@ -112,7 +112,7 @@ def _run_one(self, result: "TestResult") -> "TestResult": """ return self._run_prepared_result(ExtendedToOriginalDecorator(result)) # type: ignore[arg-type] - def _run_prepared_result(self, result: "TestResult") -> "TestResult": + def _run_prepared_result(self, result: TestResult) -> TestResult: """Run one test reporting to result. :param result: A testtools.TestResult to report activity to. @@ -189,7 +189,7 @@ def _run_core(self) -> None: self.case, details=self.case.getDetails() ) - def _run_cleanups(self, result: "TestResult") -> object | None: + def _run_cleanups(self, result: TestResult) -> object | None: """Run the cleanups that have been added with addCleanup. See the docstring for addCleanup for more information. diff --git a/testtools/testresult/doubles.py b/testtools/testresult/doubles.py index 21cedf90..405ca790 100644 --- a/testtools/testresult/doubles.py +++ b/testtools/testresult/doubles.py @@ -10,11 +10,13 @@ from testtools._types import OptExcInfo from testtools.tags import TagContext +from testtools.testresult.real import StreamResult as BaseStreamResult __all__ = [ "ExtendedTestResult", - "Python3TestResult", + "LogEvent", "StreamResult", + "TestResult", "TwistedTestResult", ] @@ -84,39 +86,40 @@ def __init__(self, event_log: list[LogEvent] | None = None) -> None: self._events = event_log -class Python3TestResult(LoggingBase): +class TestResult(LoggingBase, unittest.TestResult): """A precisely python 3 like test result, that logs.""" def __init__(self, event_log: list[LogEvent] | None = None) -> None: - super().__init__(event_log=event_log) - self.shouldStop = False - self._was_successful = True - self.testsRun = 0 - self.failfast = False + LoggingBase.__init__(self, event_log=event_log) + unittest.TestResult.__init__(self) self.collectedDurations: list[tuple[unittest.TestCase, float]] = [] def addError(self, test: unittest.TestCase, err: OptExcInfo) -> None: - self._was_successful = False + super().addError(test, err) self._events.append(("addError", test, err)) if self.failfast: self.stop() def addFailure(self, test: unittest.TestCase, err: OptExcInfo) -> None: - self._was_successful = False + super().addFailure(test, err) self._events.append(("addFailure", test, err)) if self.failfast: self.stop() def addSuccess(self, test: unittest.TestCase) -> None: + super().addSuccess(test) self._events.append(("addSuccess", test)) def addExpectedFailure(self, test: unittest.TestCase, err: OptExcInfo) -> None: + super().addExpectedFailure(test, err) self._events.append(("addExpectedFailure", test, err)) def addSkip(self, test: unittest.TestCase, reason: str) -> None: + super().addSkip(test, reason) self._events.append(("addSkip", test, reason)) def addUnexpectedSuccess(self, test: unittest.TestCase) -> None: + super().addUnexpectedSuccess(test) self._events.append(("addUnexpectedSuccess", test)) if self.failfast: self.stop() @@ -125,31 +128,36 @@ def addDuration(self, test: unittest.TestCase, duration: float) -> None: self._events.append(("addDuration", test, duration)) self.collectedDurations.append((test, duration)) + def wasSuccessful(self) -> bool: + # Preserve the pre-3.12 contract: unexpected successes do not count + # as failures. Python 3.12 changed unittest.TestResult.wasSuccessful() + # to also check unexpectedSuccesses, but that is not part of the + # Python3 result contract this class documents. + return len(self.failures) == len(self.errors) == 0 + def startTest(self, test: unittest.TestCase) -> None: + super().startTest(test) self._events.append(("startTest", test)) - self.testsRun += 1 def startTestRun(self) -> None: + super().startTestRun() self._events.append(("startTestRun",)) - def stop(self) -> None: - self.shouldStop = True - def stopTest(self, test: unittest.TestCase) -> None: + super().stopTest(test) self._events.append(("stopTest", test)) def stopTestRun(self) -> None: + super().stopTestRun() self._events.append(("stopTestRun",)) - def wasSuccessful(self) -> bool: - return self._was_successful - -class ExtendedTestResult(Python3TestResult): +class ExtendedTestResult(TestResult): """A test result like the proposed extended unittest result API.""" def __init__(self, event_log: list[LogEvent] | None = None) -> None: super().__init__(event_log) + self._was_successful = True self._tags = TagContext() def addError( @@ -239,14 +247,15 @@ def wasSuccessful(self) -> bool: return self._was_successful -class TwistedTestResult(LoggingBase): +class TwistedTestResult(LoggingBase, unittest.TestResult): """Emulate the relevant bits of :py:class:`twisted.trial.itrial.IReporter`. Used to ensure that we can use ``trial`` as a test runner. """ def __init__(self, event_log: list[LogEvent] | None = None) -> None: - super().__init__(event_log=event_log) + LoggingBase.__init__(self, event_log=event_log) + unittest.TestResult.__init__(self) self._was_successful = True self.testsRun = 0 @@ -288,7 +297,7 @@ def done(self) -> None: pass -class StreamResult(LoggingBase): +class StreamResult(LoggingBase, BaseStreamResult): """A StreamResult implementation for testing. All events are logged to _events.