← Back to team overview

testtools-dev team mailing list archive

[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