Merge lp:~lifeless/testtools/placeholderskip into lp:~testtools-committers/testtools/trunk

Proposed by Robert Collins
Status: Merged
Approved by: Jonathan Lange
Approved revision: 249
Merged at revision: 247
Proposed branch: lp:~lifeless/testtools/placeholderskip
Merge into: lp:~testtools-committers/testtools/trunk
Diff against target: 251 lines (+73/-66)
3 files modified
NEWS (+8/-0)
testtools/testcase.py (+37/-40)
testtools/tests/test_testcase.py (+28/-26)
To merge this branch: bzr merge lp:~lifeless/testtools/placeholderskip
Reviewer Review Type Date Requested Status
Jonathan Lange Approve
Review via email: mp+100564@code.launchpad.net

Description of the change

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).

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Thanks Rob, this is a great idea.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2012-02-16 10:52:15 +0000
3+++ NEWS 2012-04-03 07:57:20 +0000
4@@ -7,6 +7,14 @@
5 NEXT
6 ~~~~
7
8+Changes
9+-------
10+
11+* ``PlaceHolder`` and ``ErrorHolder`` now support being given result details.
12+ (Robert Collins)
13+
14+* ``ErrorHolder`` is now just a function - all the logic is in ``PlaceHolder``.
15+ (Robert Collins)
16
17 0.9.14
18 ~~~~~~
19
20=== modified file 'testtools/testcase.py'
21--- testtools/testcase.py 2012-01-29 14:03:59 +0000
22+++ testtools/testcase.py 2012-04-03 07:57:20 +0000
23@@ -42,7 +42,10 @@
24 )
25 from testtools.monkey import patch
26 from testtools.runtest import RunTest
27-from testtools.testresult import TestResult
28+from testtools.testresult import (
29+ ExtendedToOriginalDecorator,
30+ TestResult,
31+ )
32
33 wraps = try_import('functools.wraps')
34
35@@ -301,9 +304,7 @@
36 self.__exception_handlers.append(handler)
37
38 def _add_reason(self, reason):
39- self.addDetail('reason', content.Content(
40- content.ContentType('text', 'plain'),
41- lambda: [reason.encode('utf8')]))
42+ self.addDetail('reason', content.text_content(reason))
43
44 def assertEqual(self, expected, observed, message=''):
45 """Assert that 'expected' is equal to 'observed'.
46@@ -602,21 +603,30 @@
47 particularly suitable for being added to TestResults.
48 """
49
50- def __init__(self, test_id, short_description=None):
51+ failureException = None
52+
53+ def __init__(self, test_id, short_description=None, details=None,
54+ outcome='addSuccess', error=None):
55 """Construct a `PlaceHolder`.
56
57 :param test_id: The id of the placeholder test.
58 :param short_description: The short description of the place holder
59 test. If not provided, the id will be used instead.
60+ :param details: Outcome details as accepted by addSuccess etc.
61+ :param outcome: The outcome to call. Defaults to 'addSuccess'.
62 """
63 self._test_id = test_id
64 self._short_description = short_description
65+ self._details = details or {}
66+ self._outcome = outcome
67+ if error is not None:
68+ self._details['traceback'] = content.TracebackContent(error, self)
69
70 def __call__(self, result=None):
71 return self.run(result=result)
72
73 def __repr__(self):
74- internal = [self._test_id]
75+ internal = [self._outcome, self._test_id, self._details]
76 if self._short_description is not None:
77 internal.append(self._short_description)
78 return "<%s.%s(%s)>" % (
79@@ -636,11 +646,17 @@
80 def id(self):
81 return self._test_id
82
83+ def _result(self, result):
84+ if result is None:
85+ return TestResult()
86+ else:
87+ return ExtendedToOriginalDecorator(result)
88+
89 def run(self, result=None):
90- if result is None:
91- result = TestResult()
92+ result = self._result(result)
93 result.startTest(self)
94- result.addSuccess(self)
95+ outcome = getattr(result, self._outcome)
96+ outcome(self, details=self._details)
97 result.stopTest(self)
98
99 def shortDescription(self):
100@@ -650,37 +666,18 @@
101 return self._short_description
102
103
104-class ErrorHolder(PlaceHolder):
105- """A placeholder test that will error out when run."""
106-
107- failureException = None
108-
109- def __init__(self, test_id, error, short_description=None):
110- """Construct an `ErrorHolder`.
111-
112- :param test_id: The id of the test.
113- :param error: The exc info tuple that will be used as the test's error.
114- :param short_description: An optional short description of the test.
115- """
116- super(ErrorHolder, self).__init__(
117- test_id, short_description=short_description)
118- self._error = error
119-
120- def __repr__(self):
121- internal = [self._test_id, self._error]
122- if self._short_description is not None:
123- internal.append(self._short_description)
124- return "<%s.%s(%s)>" % (
125- self.__class__.__module__,
126- self.__class__.__name__,
127- ", ".join(map(repr, internal)))
128-
129- def run(self, result=None):
130- if result is None:
131- result = TestResult()
132- result.startTest(self)
133- result.addError(self, self._error)
134- result.stopTest(self)
135+def ErrorHolder(test_id, error, short_description=None, details=None):
136+ """Construct an `ErrorHolder`.
137+
138+ :param test_id: The id of the test.
139+ :param error: The exc info tuple that will be used as the test's error.
140+ This is inserted into the details as 'traceback' - any existing key
141+ will be overridden.
142+ :param short_description: An optional short description of the test.
143+ :param details: Outcome details as accepted by addSuccess etc.
144+ """
145+ return PlaceHolder(test_id, short_description=short_description,
146+ details=details, outcome='addError', error=error)
147
148
149 # Python 2.4 did not know how to copy functions.
150
151=== modified file 'testtools/tests/test_testcase.py'
152--- testtools/tests/test_testcase.py 2012-02-04 16:44:10 +0000
153+++ testtools/tests/test_testcase.py 2012-04-03 07:57:20 +0000
154@@ -77,16 +77,21 @@
155 # repr(placeholder) shows you how the object was constructed.
156 test = PlaceHolder("test id")
157 self.assertEqual(
158- "<testtools.testcase.PlaceHolder(%s)>" % repr(test.id()),
159- repr(test))
160+ "<testtools.testcase.PlaceHolder('addSuccess', %s, {})>" % repr(
161+ test.id()), repr(test))
162
163 def test_repr_with_description(self):
164 # repr(placeholder) shows you how the object was constructed.
165 test = PlaceHolder("test id", "description")
166 self.assertEqual(
167- "<testtools.testcase.PlaceHolder(%r, %r)>" % (
168- test.id(), test.shortDescription()),
169- repr(test))
170+ "<testtools.testcase.PlaceHolder('addSuccess', %r, {}, %r)>" % (
171+ test.id(), test.shortDescription()), repr(test))
172+
173+ def test_repr_custom_outcome(self):
174+ test = PlaceHolder("test id", outcome='addSkip')
175+ self.assertEqual(
176+ "<testtools.testcase.PlaceHolder('addSkip', %r, {})>" % (
177+ test.id()), repr(test))
178
179 def test_counts_as_one_test(self):
180 # A placeholder test counts as one test.
181@@ -107,6 +112,17 @@
182 [('startTest', test), ('addSuccess', test), ('stopTest', test)],
183 log)
184
185+ def test_supplies_details(self):
186+ details = {'quux':None}
187+ test = PlaceHolder('foo', details=details)
188+ result = ExtendedTestResult()
189+ test.run(result)
190+ self.assertEqual(
191+ [('startTest', test),
192+ ('addSuccess', test, details),
193+ ('stopTest', test)],
194+ result._events)
195+
196 def test_call_is_run(self):
197 # A PlaceHolder can be called, in which case it behaves like run.
198 test = self.makePlaceHolder()
199@@ -127,6 +143,8 @@
200
201
202 class TestErrorHolder(TestCase):
203+ # Note that these tests exist because ErrorHolder exists - it could be
204+ # deprecated and dropped at this point.
205
206 run_test_with = FullStackRunTest
207
208@@ -158,23 +176,6 @@
209 test = ErrorHolder("test id", self.makeException(), "description")
210 self.assertEqual("description", test.shortDescription())
211
212- def test_repr_just_id(self):
213- # repr(placeholder) shows you how the object was constructed.
214- error = self.makeException()
215- test = ErrorHolder("test id", error)
216- self.assertEqual(
217- "<testtools.testcase.ErrorHolder(%r, %r)>" % (test.id(), error),
218- repr(test))
219-
220- def test_repr_with_description(self):
221- # repr(placeholder) shows you how the object was constructed.
222- error = self.makeException()
223- test = ErrorHolder("test id", error, "description")
224- self.assertEqual(
225- "<testtools.testcase.ErrorHolder(%r, %r, %r)>" % (
226- test.id(), error, test.shortDescription()),
227- repr(test))
228-
229 def test_counts_as_one_test(self):
230 # A placeholder test counts as one test.
231 test = self.makePlaceHolder()
232@@ -186,14 +187,15 @@
233 self.assertEqual(test.id(), str(test))
234
235 def test_runs_as_error(self):
236- # When run, a PlaceHolder test records a success.
237+ # When run, an ErrorHolder test records an error.
238 error = self.makeException()
239 test = self.makePlaceHolder(error=error)
240- log = []
241- test.run(LoggingResult(log))
242+ result = ExtendedTestResult()
243+ log = result._events
244+ test.run(result)
245 self.assertEqual(
246 [('startTest', test),
247- ('addError', test, error),
248+ ('addError', test, test._details),
249 ('stopTest', test)], log)
250
251 def test_call_is_run(self):

Subscribers

People subscribed via source and target branches