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
9 changes: 9 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ testtools NEWS

Changes and improvements to testtools_, grouped by release.

2.8.1
~~~~~

Changes
-------

* Re-add accidentally removed methods on StreamToExtendedDecorator.
(Jelmer Vernooij)

2.8.0
~~~~~

Expand Down
30 changes: 30 additions & 0 deletions testtools/testresult/real.py
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,36 @@ def _handle_tests(self, test_record):
case = test_record.to_test_case()
case.run(self.decorated)

def wasSuccessful(self):
"""Return whether this result was successful.

Delegates to the decorated result object.
"""
return self.decorated.wasSuccessful()

@property
def shouldStop(self):
"""Return whether the test run should stop.

Delegates to the decorated result object.
"""
return self.decorated.shouldStop

def stop(self):
"""Indicate that the test run should stop.

Delegates to the decorated result object.
"""
return self.decorated.stop()

@property
def testsRun(self):
"""Return the number of tests run.

Delegates to the decorated result object.
"""
return self.decorated.testsRun


class StreamToQueue(StreamResult):
"""A StreamResult which enqueues events as a dict to a queue.Queue.
Expand Down
133 changes: 133 additions & 0 deletions testtools/tests/test_testresult.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,139 @@ def _make_result(self):
return StreamToExtendedDecorator(ExtendedTestResult())


class TestStreamToExtendedDecoratorMethods(TestCase):
"""Test that StreamToExtendedDecorator has all required TestResult methods.

This test class was added to address a bug where StreamToExtendedDecorator
was missing the wasSuccessful() method, causing subunit2junitxml to fail
with AttributeError in version 1.4.5.
"""

def setUp(self):
super().setUp()
self.base_result = ExtendedTestResult()
self.decorator = StreamToExtendedDecorator(self.base_result)

def test_has_wasSuccessful_method(self):
"""StreamToExtendedDecorator should have wasSuccessful() method."""
self.assertTrue(hasattr(self.decorator, "wasSuccessful"))
self.assertTrue(callable(self.decorator.wasSuccessful))

def test_wasSuccessful_returns_boolean(self):
"""wasSuccessful() should return a boolean value."""
result = self.decorator.wasSuccessful()
self.assertIsInstance(result, bool)

def test_wasSuccessful_delegates_to_decorated(self):
"""wasSuccessful() should delegate to the decorated result."""
# Initially should be successful
self.assertTrue(self.decorator.wasSuccessful())

def test_wasSuccessful_true_after_success(self):
"""wasSuccessful() should return True after a test success."""
self.decorator.startTestRun()
self.decorator.status(test_id="test1", test_status="inprogress")
self.decorator.status(test_id="test1", test_status="success")
self.decorator.stopTestRun()

self.assertTrue(self.decorator.wasSuccessful())

def test_wasSuccessful_false_after_failure(self):
"""wasSuccessful() should return False after a test failure."""
self.decorator.startTestRun()
self.decorator.status(test_id="test_fail", test_status="inprogress")
self.decorator.status(test_id="test_fail", test_status="fail")
self.decorator.stopTestRun()

self.assertFalse(self.decorator.wasSuccessful())

def test_has_shouldStop_property(self):
"""StreamToExtendedDecorator should have shouldStop property."""
self.assertTrue(hasattr(self.decorator, "shouldStop"))

def test_shouldStop_returns_boolean(self):
"""shouldStop property should return a boolean value."""
result = self.decorator.shouldStop
self.assertIsInstance(result, bool)

def test_shouldStop_initially_false(self):
"""shouldStop should initially be False."""
self.assertFalse(self.decorator.shouldStop)

def test_has_stop_method(self):
"""StreamToExtendedDecorator should have stop() method."""
self.assertTrue(hasattr(self.decorator, "stop"))
self.assertTrue(callable(self.decorator.stop))

def test_stop_sets_shouldStop(self):
"""stop() method should set shouldStop to True."""
self.assertFalse(self.decorator.shouldStop)
self.decorator.stop()
self.assertTrue(self.decorator.shouldStop)

def test_has_testsRun_property(self):
"""StreamToExtendedDecorator should have testsRun property."""
self.assertTrue(hasattr(self.decorator, "testsRun"))

def test_testsRun_returns_integer(self):
"""testsRun property should return an integer."""
result = self.decorator.testsRun
self.assertIsInstance(result, int)

def test_testsRun_initially_zero(self):
"""testsRun should initially be 0."""
self.assertEqual(0, self.decorator.testsRun)

def test_testsRun_increments_after_test(self):
"""testsRun should increment after a test runs."""
self.decorator.startTestRun()
self.decorator.status(test_id="test1", test_status="inprogress")
self.decorator.status(test_id="test1", test_status="success")
self.decorator.stopTestRun()

self.assertEqual(1, self.decorator.testsRun)

def test_subunit2junitxml_compatibility(self):
"""Test the exact pattern used by subunit2junitxml that was failing.

This reproduces the bug reported in subunit 1.4.5 where
StreamToExtendedDecorator was missing wasSuccessful() method.
"""
# This simulates what subunit2junitxml does
result = ExtendedTestResult()
decorated = StreamToExtendedDecorator(result)

# Should not raise AttributeError
success = decorated.wasSuccessful()
self.assertIsInstance(success, bool)
self.assertTrue(success) # No tests run yet, so should be successful

def test_result_attributes_accessible_after_test_run(self):
"""Test that result attributes are accessible after running tests."""
self.decorator.startTestRun()

# Run a successful test
self.decorator.status(test_id="test.success", test_status="inprogress")
self.decorator.status(test_id="test.success", test_status="success")

# Run a failed test
self.decorator.status(test_id="test.failure", test_status="inprogress")
self.decorator.status(
test_id="test.failure",
test_status="fail",
file_name="traceback",
file_bytes=b"Test failed",
eof=True,
)

self.decorator.stopTestRun()

# Test key query methods - these should work without AttributeError
self.assertFalse(self.decorator.wasSuccessful())
self.assertEqual(2, self.decorator.testsRun)
self.assertFalse(self.decorator.shouldStop)


class TestStreamToQueueContract(TestCase, TestStreamResultContract):
def _make_result(self):
queue = Queue()
Expand Down