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
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand Down
8 changes: 8 additions & 0 deletions testtools/testresult/doubles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))

Expand Down
13 changes: 13 additions & 0 deletions testtools/testresult/real.py
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
124 changes: 124 additions & 0 deletions testtools/tests/test_testresult.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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'."""
Expand Down