← Back to team overview

testtools-dev team mailing list archive

[Merge] lp:~lifeless/testtools/placeholderskip into lp:testtools

 

Robert Collins has proposed merging lp:~lifeless/testtools/placeholderskip into lp:testtools.

Requested reviews:
  testtools committers (testtools-committers)

For more details, see:
https://code.launchpad.net/~lifeless/testtools/placeholderskip/+merge/100564

This branch makes it easy to do placeholder skips - something that testscenarios would like to do. (https://code.launchpad.net/~mbp/testscenarios/module-scenarios/+merge/80287)

Placeholder(testid, outcome='addSkip', details={'reason': text_content('module failed to import')}) for instance.

It reduces the total LoC for testtools non-test code and slightly increases the test code size.

I haven't added docs describing the increased functionality beyond the docstring updates. I am happy to do so if appropriate (but I try not to repeat API docs in the manual in other projects).
-- 
https://code.launchpad.net/~lifeless/testtools/placeholderskip/+merge/100564
Your team testtools developers is subscribed to branch lp:testtools.
=== modified file 'NEWS'
--- NEWS	2012-02-16 10:52:15 +0000
+++ NEWS	2012-04-03 07:57:20 +0000
@@ -7,6 +7,14 @@
 NEXT
 ~~~~
 
+Changes
+-------
+
+* ``PlaceHolder`` and ``ErrorHolder`` now support being given result details.
+  (Robert Collins)
+
+* ``ErrorHolder`` is now just a function - all the logic is in ``PlaceHolder``.
+  (Robert Collins)
 
 0.9.14
 ~~~~~~

=== modified file 'testtools/testcase.py'
--- testtools/testcase.py	2012-01-29 14:03:59 +0000
+++ testtools/testcase.py	2012-04-03 07:57:20 +0000
@@ -42,7 +42,10 @@
     )
 from testtools.monkey import patch
 from testtools.runtest import RunTest
-from testtools.testresult import TestResult
+from testtools.testresult import (
+    ExtendedToOriginalDecorator,
+    TestResult,
+    )
 
 wraps = try_import('functools.wraps')
 
@@ -301,9 +304,7 @@
         self.__exception_handlers.append(handler)
 
     def _add_reason(self, reason):
-        self.addDetail('reason', content.Content(
-            content.ContentType('text', 'plain'),
-            lambda: [reason.encode('utf8')]))
+        self.addDetail('reason', content.text_content(reason))
 
     def assertEqual(self, expected, observed, message=''):
         """Assert that 'expected' is equal to 'observed'.
@@ -602,21 +603,30 @@
     particularly suitable for being added to TestResults.
     """
 
-    def __init__(self, test_id, short_description=None):
+    failureException = None
+
+    def __init__(self, test_id, short_description=None, details=None,
+        outcome='addSuccess', error=None):
         """Construct a `PlaceHolder`.
 
         :param test_id: The id of the placeholder test.
         :param short_description: The short description of the place holder
             test. If not provided, the id will be used instead.
+        :param details: Outcome details as accepted by addSuccess etc.
+        :param outcome: The outcome to call. Defaults to 'addSuccess'.
         """
         self._test_id = test_id
         self._short_description = short_description
+        self._details = details or {}
+        self._outcome = outcome
+        if error is not None:
+            self._details['traceback'] = content.TracebackContent(error, self)
 
     def __call__(self, result=None):
         return self.run(result=result)
 
     def __repr__(self):
-        internal = [self._test_id]
+        internal = [self._outcome, self._test_id, self._details]
         if self._short_description is not None:
             internal.append(self._short_description)
         return "<%s.%s(%s)>" % (
@@ -636,11 +646,17 @@
     def id(self):
         return self._test_id
 
+    def _result(self, result):
+        if result is None:
+            return TestResult()
+        else:
+            return ExtendedToOriginalDecorator(result)
+
     def run(self, result=None):
-        if result is None:
-            result = TestResult()
+        result = self._result(result)
         result.startTest(self)
-        result.addSuccess(self)
+        outcome = getattr(result, self._outcome)
+        outcome(self, details=self._details)
         result.stopTest(self)
 
     def shortDescription(self):
@@ -650,37 +666,18 @@
             return self._short_description
 
 
-class ErrorHolder(PlaceHolder):
-    """A placeholder test that will error out when run."""
-
-    failureException = None
-
-    def __init__(self, test_id, error, short_description=None):
-        """Construct an `ErrorHolder`.
-
-        :param test_id: The id of the test.
-        :param error: The exc info tuple that will be used as the test's error.
-        :param short_description: An optional short description of the test.
-        """
-        super(ErrorHolder, self).__init__(
-            test_id, short_description=short_description)
-        self._error = error
-
-    def __repr__(self):
-        internal = [self._test_id, self._error]
-        if self._short_description is not None:
-            internal.append(self._short_description)
-        return "<%s.%s(%s)>" % (
-            self.__class__.__module__,
-            self.__class__.__name__,
-            ", ".join(map(repr, internal)))
-
-    def run(self, result=None):
-        if result is None:
-            result = TestResult()
-        result.startTest(self)
-        result.addError(self, self._error)
-        result.stopTest(self)
+def ErrorHolder(test_id, error, short_description=None, details=None):
+    """Construct an `ErrorHolder`.
+
+    :param test_id: The id of the test.
+    :param error: The exc info tuple that will be used as the test's error.
+        This is inserted into the details as 'traceback' - any existing key
+        will be overridden.
+    :param short_description: An optional short description of the test.
+    :param details: Outcome details as accepted by addSuccess etc.
+    """
+    return PlaceHolder(test_id, short_description=short_description,
+        details=details, outcome='addError', error=error)
 
 
 # Python 2.4 did not know how to copy functions.

=== modified file 'testtools/tests/test_testcase.py'
--- testtools/tests/test_testcase.py	2012-02-04 16:44:10 +0000
+++ testtools/tests/test_testcase.py	2012-04-03 07:57:20 +0000
@@ -77,16 +77,21 @@
         # repr(placeholder) shows you how the object was constructed.
         test = PlaceHolder("test id")
         self.assertEqual(
-            "<testtools.testcase.PlaceHolder(%s)>" % repr(test.id()),
-            repr(test))
+            "<testtools.testcase.PlaceHolder('addSuccess', %s, {})>" % repr(
+            test.id()), repr(test))
 
     def test_repr_with_description(self):
         # repr(placeholder) shows you how the object was constructed.
         test = PlaceHolder("test id", "description")
         self.assertEqual(
-            "<testtools.testcase.PlaceHolder(%r, %r)>" % (
-                test.id(), test.shortDescription()),
-            repr(test))
+            "<testtools.testcase.PlaceHolder('addSuccess', %r, {}, %r)>" % (
+            test.id(), test.shortDescription()), repr(test))
+
+    def test_repr_custom_outcome(self):
+        test = PlaceHolder("test id", outcome='addSkip')
+        self.assertEqual(
+            "<testtools.testcase.PlaceHolder('addSkip', %r, {})>" % (
+            test.id()), repr(test))
 
     def test_counts_as_one_test(self):
         # A placeholder test counts as one test.
@@ -107,6 +112,17 @@
             [('startTest', test), ('addSuccess', test), ('stopTest', test)],
             log)
 
+    def test_supplies_details(self):
+        details = {'quux':None}
+        test = PlaceHolder('foo', details=details)
+        result = ExtendedTestResult()
+        test.run(result)
+        self.assertEqual(
+            [('startTest', test),
+             ('addSuccess', test, details),
+             ('stopTest', test)],
+            result._events)
+
     def test_call_is_run(self):
         # A PlaceHolder can be called, in which case it behaves like run.
         test = self.makePlaceHolder()
@@ -127,6 +143,8 @@
 
 
 class TestErrorHolder(TestCase):
+    # Note that these tests exist because ErrorHolder exists - it could be
+    # deprecated and dropped at this point.
 
     run_test_with = FullStackRunTest
 
@@ -158,23 +176,6 @@
         test = ErrorHolder("test id", self.makeException(), "description")
         self.assertEqual("description", test.shortDescription())
 
-    def test_repr_just_id(self):
-        # repr(placeholder) shows you how the object was constructed.
-        error = self.makeException()
-        test = ErrorHolder("test id", error)
-        self.assertEqual(
-            "<testtools.testcase.ErrorHolder(%r, %r)>" % (test.id(), error),
-            repr(test))
-
-    def test_repr_with_description(self):
-        # repr(placeholder) shows you how the object was constructed.
-        error = self.makeException()
-        test = ErrorHolder("test id", error, "description")
-        self.assertEqual(
-            "<testtools.testcase.ErrorHolder(%r, %r, %r)>" % (
-                test.id(), error, test.shortDescription()),
-            repr(test))
-
     def test_counts_as_one_test(self):
         # A placeholder test counts as one test.
         test = self.makePlaceHolder()
@@ -186,14 +187,15 @@
         self.assertEqual(test.id(), str(test))
 
     def test_runs_as_error(self):
-        # When run, a PlaceHolder test records a success.
+        # When run, an ErrorHolder test records an error.
         error = self.makeException()
         test = self.makePlaceHolder(error=error)
-        log = []
-        test.run(LoggingResult(log))
+        result = ExtendedTestResult()
+        log = result._events
+        test.run(result)
         self.assertEqual(
             [('startTest', test),
-             ('addError', test, error),
+             ('addError', test, test._details),
              ('stopTest', test)], log)
 
     def test_call_is_run(self):


Follow ups