subunit: Import new version.
[Samba/fernandojvsilva.git] / lib / subunit / python / subunit / test_results.py
blob4ccc2aab3517219e70b340042f7f6a34ed81fdb6
2 # subunit: extensions to Python unittest to get test results from subprocesses.
3 # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
5 # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
6 # license at the users choice. A copy of both licenses are available in the
7 # project source as Apache-2.0 and BSD. You may not use this file except in
8 # compliance with one of these two licences.
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # license you chose for the specific language governing permissions and
14 # limitations under that license.
17 """TestResult helper classes used to by subunit."""
19 import datetime
21 import iso8601
22 import testtools
24 import subunit
27 # NOT a TestResult, because we are implementing the interface, not inheriting
28 # it.
29 class TestResultDecorator(object):
30 """General pass-through decorator.
32 This provides a base that other TestResults can inherit from to
33 gain basic forwarding functionality. It also takes care of
34 handling the case where the target doesn't support newer methods
35 or features by degrading them.
36 """
38 def __init__(self, decorated):
39 """Create a TestResultDecorator forwarding to decorated."""
40 # Make every decorator degrade gracefully.
41 self.decorated = testtools.ExtendedToOriginalDecorator(decorated)
43 def startTest(self, test):
44 return self.decorated.startTest(test)
46 def startTestRun(self):
47 return self.decorated.startTestRun()
49 def stopTest(self, test):
50 return self.decorated.stopTest(test)
52 def stopTestRun(self):
53 return self.decorated.stopTestRun()
55 def addError(self, test, err=None, details=None):
56 return self.decorated.addError(test, err, details=details)
58 def addFailure(self, test, err=None, details=None):
59 return self.decorated.addFailure(test, err, details=details)
61 def addSuccess(self, test, details=None):
62 return self.decorated.addSuccess(test, details=details)
64 def addSkip(self, test, reason=None, details=None):
65 return self.decorated.addSkip(test, reason, details=details)
67 def addExpectedFailure(self, test, err=None, details=None):
68 return self.decorated.addExpectedFailure(test, err, details=details)
70 def addUnexpectedSuccess(self, test, details=None):
71 return self.decorated.addUnexpectedSuccess(test, details=details)
73 def progress(self, offset, whence):
74 return self.decorated.progress(offset, whence)
76 def wasSuccessful(self):
77 return self.decorated.wasSuccessful()
79 @property
80 def shouldStop(self):
81 return self.decorated.shouldStop
83 def stop(self):
84 return self.decorated.stop()
86 def tags(self, gone_tags, new_tags):
87 return self.decorated.time(gone_tags, new_tags)
89 def time(self, a_datetime):
90 return self.decorated.time(a_datetime)
93 class HookedTestResultDecorator(TestResultDecorator):
94 """A TestResult which calls a hook on every event."""
96 def __init__(self, decorated):
97 self.super = super(HookedTestResultDecorator, self)
98 self.super.__init__(decorated)
100 def startTest(self, test):
101 self._before_event()
102 return self.super.startTest(test)
104 def startTestRun(self):
105 self._before_event()
106 return self.super.startTestRun()
108 def stopTest(self, test):
109 self._before_event()
110 return self.super.stopTest(test)
112 def stopTestRun(self):
113 self._before_event()
114 return self.super.stopTestRun()
116 def addError(self, test, err=None, details=None):
117 self._before_event()
118 return self.super.addError(test, err, details=details)
120 def addFailure(self, test, err=None, details=None):
121 self._before_event()
122 return self.super.addFailure(test, err, details=details)
124 def addSuccess(self, test, details=None):
125 self._before_event()
126 return self.super.addSuccess(test, details=details)
128 def addSkip(self, test, reason=None, details=None):
129 self._before_event()
130 return self.super.addSkip(test, reason, details=details)
132 def addExpectedFailure(self, test, err=None, details=None):
133 self._before_event()
134 return self.super.addExpectedFailure(test, err, details=details)
136 def addUnexpectedSuccess(self, test, details=None):
137 self._before_event()
138 return self.super.addUnexpectedSuccess(test, details=details)
140 def progress(self, offset, whence):
141 self._before_event()
142 return self.super.progress(offset, whence)
144 def wasSuccessful(self):
145 self._before_event()
146 return self.super.wasSuccessful()
148 @property
149 def shouldStop(self):
150 self._before_event()
151 return self.super.shouldStop
153 def stop(self):
154 self._before_event()
155 return self.super.stop()
157 def time(self, a_datetime):
158 self._before_event()
159 return self.super.time(a_datetime)
162 class AutoTimingTestResultDecorator(HookedTestResultDecorator):
163 """Decorate a TestResult to add time events to a test run.
165 By default this will cause a time event before every test event,
166 but if explicit time data is being provided by the test run, then
167 this decorator will turn itself off to prevent causing confusion.
170 def __init__(self, decorated):
171 self._time = None
172 super(AutoTimingTestResultDecorator, self).__init__(decorated)
174 def _before_event(self):
175 time = self._time
176 if time is not None:
177 return
178 time = datetime.datetime.utcnow().replace(tzinfo=iso8601.Utc())
179 self.decorated.time(time)
181 def progress(self, offset, whence):
182 return self.decorated.progress(offset, whence)
184 @property
185 def shouldStop(self):
186 return self.decorated.shouldStop
188 def time(self, a_datetime):
189 """Provide a timestamp for the current test activity.
191 :param a_datetime: If None, automatically add timestamps before every
192 event (this is the default behaviour if time() is not called at
193 all). If not None, pass the provided time onto the decorated
194 result object and disable automatic timestamps.
196 self._time = a_datetime
197 return self.decorated.time(a_datetime)
200 class TestResultFilter(TestResultDecorator):
201 """A pyunit TestResult interface implementation which filters tests.
203 Tests that pass the filter are handed on to another TestResult instance
204 for further processing/reporting. To obtain the filtered results,
205 the other instance must be interrogated.
207 :ivar result: The result that tests are passed to after filtering.
208 :ivar filter_predicate: The callback run to decide whether to pass
209 a result.
212 def __init__(self, result, filter_error=False, filter_failure=False,
213 filter_success=True, filter_skip=False,
214 filter_predicate=None):
215 """Create a FilterResult object filtering to result.
217 :param filter_error: Filter out errors.
218 :param filter_failure: Filter out failures.
219 :param filter_success: Filter out successful tests.
220 :param filter_skip: Filter out skipped tests.
221 :param filter_predicate: A callable taking (test, outcome, err,
222 details) and returning True if the result should be passed
223 through. err and details may be none if no error or extra
224 metadata is available. outcome is the name of the outcome such
225 as 'success' or 'failure'.
227 TestResultDecorator.__init__(self, result)
228 self._filter_error = filter_error
229 self._filter_failure = filter_failure
230 self._filter_success = filter_success
231 self._filter_skip = filter_skip
232 if filter_predicate is None:
233 filter_predicate = lambda test, outcome, err, details: True
234 self.filter_predicate = filter_predicate
235 # The current test (for filtering tags)
236 self._current_test = None
237 # Has the current test been filtered (for outputting test tags)
238 self._current_test_filtered = None
239 # The (new, gone) tags for the current test.
240 self._current_test_tags = None
242 def addError(self, test, err=None, details=None):
243 if (not self._filter_error and
244 self.filter_predicate(test, 'error', err, details)):
245 self.decorated.startTest(test)
246 self.decorated.addError(test, err, details=details)
247 else:
248 self._filtered()
250 def addFailure(self, test, err=None, details=None):
251 if (not self._filter_failure and
252 self.filter_predicate(test, 'failure', err, details)):
253 self.decorated.startTest(test)
254 self.decorated.addFailure(test, err, details=details)
255 else:
256 self._filtered()
258 def addSkip(self, test, reason=None, details=None):
259 if (not self._filter_skip and
260 self.filter_predicate(test, 'skip', reason, details)):
261 self.decorated.startTest(test)
262 self.decorated.addSkip(test, reason, details=details)
263 else:
264 self._filtered()
266 def addSuccess(self, test, details=None):
267 if (not self._filter_success and
268 self.filter_predicate(test, 'success', None, details)):
269 self.decorated.startTest(test)
270 self.decorated.addSuccess(test, details=details)
271 else:
272 self._filtered()
274 def addExpectedFailure(self, test, err=None, details=None):
275 if self.filter_predicate(test, 'expectedfailure', err, details):
276 self.decorated.startTest(test)
277 return self.decorated.addExpectedFailure(test, err,
278 details=details)
279 else:
280 self._filtered()
282 def addUnexpectedSuccess(self, test, details=None):
283 self.decorated.startTest(test)
284 return self.decorated.addUnexpectedSuccess(test, details=details)
286 def _filtered(self):
287 self._current_test_filtered = True
289 def startTest(self, test):
290 """Start a test.
292 Not directly passed to the client, but used for handling of tags
293 correctly.
295 self._current_test = test
296 self._current_test_filtered = False
297 self._current_test_tags = set(), set()
299 def stopTest(self, test):
300 """Stop a test.
302 Not directly passed to the client, but used for handling of tags
303 correctly.
305 if not self._current_test_filtered:
306 # Tags to output for this test.
307 if self._current_test_tags[0] or self._current_test_tags[1]:
308 self.decorated.tags(*self._current_test_tags)
309 self.decorated.stopTest(test)
310 self._current_test = None
311 self._current_test_filtered = None
312 self._current_test_tags = None
314 def tags(self, new_tags, gone_tags):
315 """Handle tag instructions.
317 Adds and removes tags as appropriate. If a test is currently running,
318 tags are not affected for subsequent tests.
320 :param new_tags: Tags to add,
321 :param gone_tags: Tags to remove.
323 if self._current_test is not None:
324 # gather the tags until the test stops.
325 self._current_test_tags[0].update(new_tags)
326 self._current_test_tags[0].difference_update(gone_tags)
327 self._current_test_tags[1].update(gone_tags)
328 self._current_test_tags[1].difference_update(new_tags)
329 return self.decorated.tags(new_tags, gone_tags)
331 def id_to_orig_id(self, id):
332 if id.startswith("subunit.RemotedTestCase."):
333 return id[len("subunit.RemotedTestCase."):]
334 return id