diff --git a/NEWS b/NEWS index b2fa855f..40b5866b 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,12 @@ Changes * Drop support for Python 3.8. (Stephen Finucane) +Improvements +------------ + +* Add support for Python 3.12's ``addDuration`` method and ``collectedDurations`` + attribute in ``TestResult`` classes. (Jelmer Vernooij, #2045171) + * Remove a number of deprecated classes and methods. (Stephen Finucane) * ``testtools.matchers`` diff --git a/testtools/testresult/doubles.py b/testtools/testresult/doubles.py index 01e597a0..06c3af46 100644 --- a/testtools/testresult/doubles.py +++ b/testtools/testresult/doubles.py @@ -32,6 +32,7 @@ def __init__(self, event_log=None): self._was_successful = True self.testsRun = 0 self.failfast = False + self.collectedDurations = [] def addError(self, test, err): self._was_successful = False @@ -59,6 +60,10 @@ def addUnexpectedSuccess(self, test): if self.failfast: self.stop() + def addDuration(self, test, duration): + self._events.append(("addDuration", test, duration)) + self.collectedDurations.append((test, duration)) + def startTest(self, test): self._events.append(("startTest", test)) self.testsRun += 1 @@ -113,6 +118,9 @@ def addUnexpectedSuccess(self, test, details=None): else: self._events.append(("addUnexpectedSuccess", test)) + def addDuration(self, test, duration): + self._events.append(("addDuration", test, duration)) + def progress(self, offset, whence): self._events.append(("progress", offset, whence)) diff --git a/testtools/testresult/real.py b/testtools/testresult/real.py index c6fdcbac..626f1240 100644 --- a/testtools/testresult/real.py +++ b/testtools/testresult/real.py @@ -159,6 +159,14 @@ def addUnexpectedSuccess(self, test, details=None): if self.failfast: self.stop() + def addDuration(self, test, duration): + """Called to add a test duration. + + :param test: The test that completed. + :param duration: The duration of the test as a float in seconds. + """ + self.collectedDurations.append((test, duration)) + def wasSuccessful(self): """Has this result been successful so far? @@ -215,6 +223,8 @@ def startTestRun(self): # -- End: As per python 2.7 -- # -- Python 3.5 self.tb_locals = tb_locals + # -- Python 3.12 + self.collectedDurations = [] def stopTestRun(self): """Called after a test run completes @@ -1994,6 +2004,9 @@ def addExpectedFailure(self, test, err=None, details=None): def addUnexpectedSuccess(self, test, details=None): return self.decorated.addUnexpectedSuccess(test, details=details) + def addDuration(self, test, duration): + return self.decorated.addDuration(test, duration) + def progress(self, offset, whence): return self.decorated.progress(offset, whence) diff --git a/testtools/tests/test_testresult.py b/testtools/tests/test_testresult.py index dadaacca..eb2f6ba3 100644 --- a/testtools/tests/test_testresult.py +++ b/testtools/tests/test_testresult.py @@ -478,6 +478,75 @@ def makeResult(self): return TestResultDecorator(TestResult()) +class TestPython3TestResultDuration(TestCase): + """Tests for addDuration functionality in Python3TestResult.""" + + def test_addDuration_logging(self): + # Python3TestResult should log addDuration calls + result = Python3TestResult() + test = make_erroring_test() + + result.addDuration(test, 1.5) + expected_call = ("addDuration", test, 1.5) + self.assertIn(expected_call, result._events) + + def test_addDuration_stores_in_collectedDurations(self): + # Python3TestResult should store durations in collectedDurations + result = Python3TestResult() + test = make_erroring_test() + + result.addDuration(test, 2.3) + self.assertEqual([(test, 2.3)], result.collectedDurations) + + # Multiple durations should accumulate + result.addDuration(test, 1.7) + self.assertEqual([(test, 2.3), (test, 1.7)], result.collectedDurations) + + +class TestExtendedTestResultDuration(TestCase): + """Tests for addDuration functionality in ExtendedTestResult.""" + + def test_addDuration_logging(self): + # ExtendedTestResult should log addDuration calls + result = ExtendedTestResult() + test = make_erroring_test() + + result.addDuration(test, 3.1) + expected_call = ("addDuration", test, 3.1) + self.assertIn(expected_call, result._events) + + +class TestTestResultDecoratorDuration(TestCase): + """Tests for addDuration functionality in TestResultDecorator.""" + + def test_addDuration_forwards_to_decorated_result(self): + # TestResultDecorator should forward addDuration calls to decorated result + base_result = TestResult() + base_result.startTestRun() # Initialize collectedDurations + decorator = TestResultDecorator(base_result) + test = make_erroring_test() + + decorator.addDuration(test, 4.2) + + # Check that the duration was stored in the base result + self.assertEqual([(test, 4.2)], base_result.collectedDurations) + + def test_addDuration_multiple_forwards(self): + # Multiple addDuration calls should all be forwarded + base_result = TestResult() + base_result.startTestRun() + decorator = TestResultDecorator(base_result) + test1 = make_erroring_test() + test2 = make_erroring_test() + + decorator.addDuration(test1, 1.1) + decorator.addDuration(test2, 2.2) + decorator.addDuration(test1, 3.3) + + expected = [(test1, 1.1), (test2, 2.2), (test1, 3.3)] + self.assertEqual(expected, base_result.collectedDurations) + + # DetailsContract because ExtendedToStreamDecorator follows Python for # uxsuccess handling. class TestStreamToExtendedContract(TestCase, DetailsContract): @@ -1521,6 +1590,61 @@ def test_traceback_with_locals(self): ), ) + def test_addDuration(self): + # addDuration stores test-duration pairs in collectedDurations + result = self.makeResult() + test1 = make_erroring_test() + test2 = make_erroring_test() + + # Initially collectedDurations should be empty after startTestRun + result.startTestRun() + self.assertEqual([], result.collectedDurations) + + # Adding durations should store them as (test, duration) tuples + result.addDuration(test1, 1.5) + self.assertEqual([(test1, 1.5)], result.collectedDurations) + + result.addDuration(test2, 2.3) + self.assertEqual([(test1, 1.5), (test2, 2.3)], result.collectedDurations) + + # Adding duration for same test should create separate entries + result.addDuration(test1, 3.7) + expected = [(test1, 1.5), (test2, 2.3), (test1, 3.7)] + self.assertEqual(expected, result.collectedDurations) + + def test_addDuration_with_zero_duration(self): + # addDuration should work with zero duration + result = self.makeResult() + test = make_erroring_test() + result.startTestRun() + + result.addDuration(test, 0.0) + self.assertEqual([(test, 0.0)], result.collectedDurations) + + def test_addDuration_with_negative_duration(self): + # addDuration should work with negative duration (edge case) + result = self.makeResult() + test = make_erroring_test() + result.startTestRun() + + result.addDuration(test, -1.0) + self.assertEqual([(test, -1.0)], result.collectedDurations) + + def test_collectedDurations_resets_on_startTestRun(self): + # collectedDurations should be reset when startTestRun is called + result = self.makeResult() + test = make_erroring_test() + + # Add some durations + result.startTestRun() + result.addDuration(test, 1.0) + result.addDuration(test, 2.0) + self.assertEqual([(test, 1.0), (test, 2.0)], result.collectedDurations) + + # Starting a new test run should reset + result.startTestRun() + self.assertEqual([], result.collectedDurations) + class TestMultiTestResult(TestCase): """Tests for 'MultiTestResult'."""