testtools-dev team mailing list archive
-
testtools-dev team
-
Mailing list archive
-
Message #00655
[Merge] lp:~allenap/testtools/gather-details into lp:testtools
Gavin Panella has proposed merging lp:~allenap/testtools/gather-details into lp:testtools.
Requested reviews:
testtools developers (testtools-dev)
For more details, see:
https://code.launchpad.net/~allenap/testtools/gather-details/+merge/62326
This branch does three things:
- Moves TestCase._gather_details() out into a standalone
function. This is motivated by wanting to use it in python-fixtures
to gather details from fixtures used by other fixtures.
- Ensures that details are gathered from fixtures even when there is a
failure in fixture.setUp().
- Fixes a closure bug when gathering more than one detail from a
fixture. Previously the same content was being reported for all
details because the variable that content_callback closed over was
being updated each time round the loop in _gather_details().
--
https://code.launchpad.net/~allenap/testtools/gather-details/+merge/62326
Your team testtools developers is requested to review the proposed merge of lp:~allenap/testtools/gather-details into lp:testtools.
=== modified file 'testtools/testcase.py'
--- testtools/testcase.py 2011-05-12 23:01:56 +0000
+++ testtools/testcase.py 2011-05-25 15:13:25 +0000
@@ -99,6 +99,39 @@
return decorator
+def copy_content(content_object):
+ """Make a copy of the given content object.
+
+ The content within `content_object` is iterated and saved. This is useful
+ when the source of the content is volatile, a log file in a temporary
+ directory for example.
+
+ :param content_object: A `content.Content` instance.
+ :return: A `content.Content` instance with the same mime-type as
+ `content_object` and a non-volatile copy of its content.
+ """
+ content_bytes = list(content_object.iter_bytes())
+ content_callback = lambda: content_bytes
+ return content.Content(content_object.content_type, content_callback)
+
+
+def gather_details(source, target):
+ """Merge the details from `source` into `target`.
+
+ :param source: A *detailed* object from which details will be gathered.
+ :param target: A *detailed* object into which details will be gathered.
+ """
+ source_details = source.getDetails()
+ target_details = target.getDetails()
+ for name, content_object in source_details.items():
+ new_name = name
+ disambiguator = itertools.count(1)
+ while new_name in target_details:
+ new_name = '%s-%d' % (name, advance_iterator(disambiguator))
+ name = new_name
+ target.addDetail(name, copy_content(content_object))
+
+
class TestCase(unittest.TestCase):
"""Extensions to the basic TestCase.
@@ -514,25 +547,15 @@
:return: The fixture, after setting it up and scheduling a cleanup for
it.
"""
- fixture.setUp()
- self.addCleanup(fixture.cleanUp)
- self.addCleanup(self._gather_details, fixture.getDetails)
- return fixture
-
- def _gather_details(self, getDetails):
- """Merge the details from getDetails() into self.getDetails()."""
- details = getDetails()
- my_details = self.getDetails()
- for name, content_object in details.items():
- new_name = name
- disambiguator = itertools.count(1)
- while new_name in my_details:
- new_name = '%s-%d' % (name, advance_iterator(disambiguator))
- name = new_name
- content_bytes = list(content_object.iter_bytes())
- content_callback = lambda:content_bytes
- self.addDetail(name,
- content.Content(content_object.content_type, content_callback))
+ try:
+ fixture.setUp()
+ except:
+ gather_details(fixture, self)
+ raise
+ else:
+ self.addCleanup(fixture.cleanUp)
+ self.addCleanup(gather_details, fixture, self)
+ return fixture
def setUp(self):
super(TestCase, self).setUp()
=== modified file 'testtools/tests/test_fixturesupport.py'
--- testtools/tests/test_fixturesupport.py 2011-01-22 17:56:00 +0000
+++ testtools/tests/test_fixturesupport.py 2011-05-25 15:13:25 +0000
@@ -73,6 +73,43 @@
self.assertEqual('content available until cleanUp',
''.join(details['content-1'].iter_text()))
+ def test_useFixture_multiple_details_captured(self):
+ class DetailsFixture(fixtures.Fixture):
+ def setUp(self):
+ fixtures.Fixture.setUp(self)
+ self.addDetail('aaa', content.text_content("foo"))
+ self.addDetail('bbb', content.text_content("bar"))
+ fixture = DetailsFixture()
+ class SimpleTest(TestCase):
+ def test_foo(self):
+ self.useFixture(fixture)
+ result = ExtendedTestResult()
+ SimpleTest('test_foo').run(result)
+ self.assertEqual('addSuccess', result._events[-2][0])
+ details = result._events[-2][2]
+ self.assertEqual(['aaa', 'bbb'], sorted(details))
+ self.assertEqual('foo', ''.join(details['aaa'].iter_text()))
+ self.assertEqual('bar', ''.join(details['bbb'].iter_text()))
+
+ def test_useFixture_details_captured_from_setUp(self):
+ # Details added during fixture set-up are gathered even if setUp()
+ # fails with an exception.
+ class BrokenFixture(fixtures.Fixture):
+ def setUp(self):
+ fixtures.Fixture.setUp(self)
+ self.addDetail('content', content.text_content("foobar"))
+ raise Exception()
+ fixture = BrokenFixture()
+ class SimpleTest(TestCase):
+ def test_foo(self):
+ self.useFixture(fixture)
+ result = ExtendedTestResult()
+ SimpleTest('test_foo').run(result)
+ self.assertEqual('addError', result._events[-2][0])
+ details = result._events[-2][2]
+ self.assertEqual(['content', 'traceback'], sorted(details))
+ self.assertEqual('foobar', ''.join(details['content'].iter_text()))
+
def test_suite():
from unittest import TestLoader
Follow ups