subunit/testtools: Include newer version.
[samba.git] / lib / subunit / python / testtools / testcase.py
1 # Copyright (c) 2008, 2009 Jonathan M. Lange. See LICENSE for details.
2
3 """Test case related stuff."""
4
5 __metaclass__ = type
6 __all__ = [
7     'clone_test_with_new_id',
8     'TestCase',
9     'skip',
10     'skipIf',
11     'skipUnless',
12     ]
13
14 import copy
15 try:
16     from functools import wraps
17 except ImportError:
18     wraps = None
19 import itertools
20 import sys
21 import types
22 import unittest
23
24 from testtools import content
25 from testtools.runtest import RunTest
26 from testtools.testresult import TestResult
27 from testtools.utils import advance_iterator
28
29
30 try:
31     # Try to use the python2.7 SkipTest exception for signalling skips.
32     from unittest.case import SkipTest as TestSkipped
33 except ImportError:
34     class TestSkipped(Exception):
35         """Raised within TestCase.run() when a test is skipped."""
36
37
38 try:
39     # Try to use the same exceptions python 2.7 does.
40     from unittest.case import _ExpectedFailure, _UnexpectedSuccess
41 except ImportError:
42     # Oops, not available, make our own.
43     class _UnexpectedSuccess(Exception):
44         """An unexpected success was raised.
45
46         Note that this exception is private plumbing in testtools' testcase
47         module.
48         """
49
50     class _ExpectedFailure(Exception):
51         """An expected failure occured.
52
53         Note that this exception is private plumbing in testtools' testcase
54         module.
55         """
56
57
58 class TestCase(unittest.TestCase):
59     """Extensions to the basic TestCase.
60
61     :ivar exception_handlers: Exceptions to catch from setUp, runTest and
62         tearDown. This list is able to be modified at any time and consists of
63         (exception_class, handler(case, result, exception_value)) pairs.
64     """
65
66     skipException = TestSkipped
67
68     def __init__(self, *args, **kwargs):
69         """Construct a TestCase.
70
71         :param testMethod: The name of the method to run.
72         :param runTest: Optional class to use to execute the test. If not
73             supplied testtools.runtest.RunTest is used. The instance to be
74             used is created when run() is invoked, so will be fresh each time.
75         """
76         unittest.TestCase.__init__(self, *args, **kwargs)
77         self._cleanups = []
78         self._unique_id_gen = itertools.count(1)
79         self.__setup_called = False
80         self.__teardown_called = False
81         self.__details = {}
82         self.__RunTest = kwargs.get('runTest', RunTest)
83         self.__exception_handlers = []
84         self.exception_handlers = [
85             (self.skipException, self._report_skip),
86             (self.failureException, self._report_failure),
87             (_ExpectedFailure, self._report_expected_failure),
88             (_UnexpectedSuccess, self._report_unexpected_success),
89             (Exception, self._report_error),
90             ]
91
92     def __eq__(self, other):
93         eq = getattr(unittest.TestCase, '__eq__', None)
94         if eq is not None and not unittest.TestCase.__eq__(self, other):
95             return False
96         return self.__dict__ == other.__dict__
97
98     def __repr__(self):
99         # We add id to the repr because it makes testing testtools easier.
100         return "<%s id=0x%0x>" % (self.id(), id(self))
101
102     def addDetail(self, name, content_object):
103         """Add a detail to be reported with this test's outcome.
104
105         For more details see pydoc testtools.TestResult.
106
107         :param name: The name to give this detail.
108         :param content_object: The content object for this detail. See
109             testtools.content for more detail.
110         """
111         self.__details[name] = content_object
112
113     def getDetails(self):
114         """Get the details dict that will be reported with this test's outcome.
115
116         For more details see pydoc testtools.TestResult.
117         """
118         return self.__details
119
120     def shortDescription(self):
121         return self.id()
122
123     def skip(self, reason):
124         """Cause this test to be skipped.
125
126         This raises self.skipException(reason). skipException is raised
127         to permit a skip to be triggered at any point (during setUp or the
128         testMethod itself). The run() method catches skipException and
129         translates that into a call to the result objects addSkip method.
130
131         :param reason: The reason why the test is being skipped. This must
132             support being cast into a unicode string for reporting.
133         """
134         raise self.skipException(reason)
135
136     def _formatTypes(self, classOrIterable):
137         """Format a class or a bunch of classes for display in an error."""
138         className = getattr(classOrIterable, '__name__', None)
139         if className is None:
140             className = ', '.join(klass.__name__ for klass in classOrIterable)
141         return className
142
143     def _runCleanups(self, result):
144         """Run the cleanups that have been added with addCleanup.
145
146         See the docstring for addCleanup for more information.
147
148         Returns True if all cleanups ran without error, False otherwise.
149         """
150         ok = True
151         while self._cleanups:
152             function, arguments, keywordArguments = self._cleanups.pop()
153             try:
154                 function(*arguments, **keywordArguments)
155             except KeyboardInterrupt:
156                 raise
157             except:
158                 self._report_error(self, result, None)
159                 ok = False
160         return ok
161
162     def addCleanup(self, function, *arguments, **keywordArguments):
163         """Add a cleanup function to be called after tearDown.
164
165         Functions added with addCleanup will be called in reverse order of
166         adding after the test method and before tearDown.
167
168         If a function added with addCleanup raises an exception, the error
169         will be recorded as a test error, and the next cleanup will then be
170         run.
171
172         Cleanup functions are always called before a test finishes running,
173         even if setUp is aborted by an exception.
174         """
175         self._cleanups.append((function, arguments, keywordArguments))
176
177     def addOnException(self, handler):
178         """Add a handler to be called when an exception occurs in test code.
179
180         This handler cannot affect what result methods are called, and is
181         called before any outcome is called on the result object. An example
182         use for it is to add some diagnostic state to the test details dict
183         which is expensive to calculate and not interesting for reporting in
184         the success case.
185
186         Handlers are called before the outcome (such as addFailure) that
187         the exception has caused.
188
189         Handlers are called in first-added, first-called order, and if they
190         raise an exception, that will propogate out of the test running
191         machinery, halting test processing. As a result, do not call code that
192         may unreasonably fail.
193         """
194         self.__exception_handlers.append(handler)
195
196     def _add_reason(self, reason):
197         self.addDetail('reason', content.Content(
198             content.ContentType('text', 'plain'),
199             lambda: [reason.encode('utf8')]))
200
201     def assertIn(self, needle, haystack):
202         """Assert that needle is in haystack."""
203         self.assertTrue(
204             needle in haystack, '%r not in %r' % (needle, haystack))
205
206     def assertIs(self, expected, observed, message=''):
207         """Assert that 'expected' is 'observed'.
208
209         :param expected: The expected value.
210         :param observed: The observed value.
211         :param message: An optional message describing the error.
212         """
213         if message:
214             message = ': ' + message
215         self.assertTrue(
216             expected is observed,
217             '%r is not %r%s' % (expected, observed, message))
218
219     def assertIsNot(self, expected, observed, message=''):
220         """Assert that 'expected' is not 'observed'."""
221         if message:
222             message = ': ' + message
223         self.assertTrue(
224             expected is not observed,
225             '%r is %r%s' % (expected, observed, message))
226
227     def assertNotIn(self, needle, haystack):
228         """Assert that needle is not in haystack."""
229         self.assertTrue(
230             needle not in haystack, '%r in %r' % (needle, haystack))
231
232     def assertIsInstance(self, obj, klass):
233         self.assertTrue(
234             isinstance(obj, klass),
235             '%r is not an instance of %s' % (obj, self._formatTypes(klass)))
236
237     def assertRaises(self, excClass, callableObj, *args, **kwargs):
238         """Fail unless an exception of class excClass is thrown
239            by callableObj when invoked with arguments args and keyword
240            arguments kwargs. If a different type of exception is
241            thrown, it will not be caught, and the test case will be
242            deemed to have suffered an error, exactly as for an
243            unexpected exception.
244         """
245         try:
246             ret = callableObj(*args, **kwargs)
247         except excClass:
248             return sys.exc_info()[1]
249         else:
250             excName = self._formatTypes(excClass)
251             self.fail("%s not raised, %r returned instead." % (excName, ret))
252     failUnlessRaises = assertRaises
253
254     def assertThat(self, matchee, matcher):
255         """Assert that matchee is matched by matcher.
256
257         :param matchee: An object to match with matcher.
258         :param matcher: An object meeting the testtools.Matcher protocol.
259         :raises self.failureException: When matcher does not match thing.
260         """
261         mismatch = matcher.match(matchee)
262         if not mismatch:
263             return
264         self.fail('Match failed. Matchee: "%s"\nMatcher: %s\nDifference: %s\n'
265             % (matchee, matcher, mismatch.describe()))
266
267     def defaultTestResult(self):
268         return TestResult()
269
270     def expectFailure(self, reason, predicate, *args, **kwargs):
271         """Check that a test fails in a particular way.
272
273         If the test fails in the expected way, a KnownFailure is caused. If it
274         succeeds an UnexpectedSuccess is caused.
275
276         The expected use of expectFailure is as a barrier at the point in a
277         test where the test would fail. For example:
278         >>> def test_foo(self):
279         >>>    self.expectFailure("1 should be 0", self.assertNotEqual, 1, 0)
280         >>>    self.assertEqual(1, 0)
281
282         If in the future 1 were to equal 0, the expectFailure call can simply
283         be removed. This separation preserves the original intent of the test
284         while it is in the expectFailure mode.
285         """
286         self._add_reason(reason)
287         try:
288             predicate(*args, **kwargs)
289         except self.failureException:
290             exc_info = sys.exc_info()
291             self.addDetail('traceback',
292                 content.TracebackContent(exc_info, self))
293             raise _ExpectedFailure(exc_info)
294         else:
295             raise _UnexpectedSuccess(reason)
296
297     def getUniqueInteger(self):
298         """Get an integer unique to this test.
299
300         Returns an integer that is guaranteed to be unique to this instance.
301         Use this when you need an arbitrary integer in your test, or as a
302         helper for custom anonymous factory methods.
303         """
304         return advance_iterator(self._unique_id_gen)
305
306     def getUniqueString(self, prefix=None):
307         """Get a string unique to this test.
308
309         Returns a string that is guaranteed to be unique to this instance. Use
310         this when you need an arbitrary string in your test, or as a helper
311         for custom anonymous factory methods.
312
313         :param prefix: The prefix of the string. If not provided, defaults
314             to the id of the tests.
315         :return: A bytestring of '<prefix>-<unique_int>'.
316         """
317         if prefix is None:
318             prefix = self.id()
319         return '%s-%d' % (prefix, self.getUniqueInteger())
320
321     def onException(self, exc_info):
322         """Called when an exception propogates from test code.
323
324         :seealso addOnException:
325         """
326         for handler in self.__exception_handlers:
327             handler(exc_info)
328
329     @staticmethod
330     def _report_error(self, result, err):
331         self._report_traceback()
332         result.addError(self, details=self.getDetails())
333
334     @staticmethod
335     def _report_expected_failure(self, result, err):
336         result.addExpectedFailure(self, details=self.getDetails())
337
338     @staticmethod
339     def _report_failure(self, result, err):
340         self._report_traceback()
341         result.addFailure(self, details=self.getDetails())
342
343     @staticmethod
344     def _report_skip(self, result, err):
345         if err.args:
346             reason = err.args[0]
347         else:
348             reason = "no reason given."
349         self._add_reason(reason)
350         result.addSkip(self, details=self.getDetails())
351
352     def _report_traceback(self):
353         self.addDetail('traceback',
354             content.TracebackContent(sys.exc_info(), self))
355
356     @staticmethod
357     def _report_unexpected_success(self, result, err):
358         result.addUnexpectedSuccess(self, details=self.getDetails())
359
360     def run(self, result=None):
361         return self.__RunTest(self, self.exception_handlers).run(result)
362
363     def _run_setup(self, result):
364         """Run the setUp function for this test.
365
366         :param result: A testtools.TestResult to report activity to.
367         :raises ValueError: If the base class setUp is not called, a
368             ValueError is raised.
369         """
370         self.setUp()
371         if not self.__setup_called:
372             raise ValueError(
373                 "TestCase.setUp was not called. Have you upcalled all the "
374                 "way up the hierarchy from your setUp? e.g. Call "
375                 "super(%s, self).setUp() from your setUp()."
376                 % self.__class__.__name__)
377
378     def _run_teardown(self, result):
379         """Run the tearDown function for this test.
380
381         :param result: A testtools.TestResult to report activity to.
382         :raises ValueError: If the base class tearDown is not called, a
383             ValueError is raised.
384         """
385         self.tearDown()
386         if not self.__teardown_called:
387             raise ValueError(
388                 "TestCase.tearDown was not called. Have you upcalled all the "
389                 "way up the hierarchy from your tearDown? e.g. Call "
390                 "super(%s, self).tearDown() from your tearDown()."
391                 % self.__class__.__name__)
392
393     def _run_test_method(self, result):
394         """Run the test method for this test.
395
396         :param result: A testtools.TestResult to report activity to.
397         :return: None.
398         """
399         absent_attr = object()
400         # Python 2.5+
401         method_name = getattr(self, '_testMethodName', absent_attr)
402         if method_name is absent_attr:
403             # Python 2.4
404             method_name = getattr(self, '_TestCase__testMethodName')
405         testMethod = getattr(self, method_name)
406         testMethod()
407
408     def setUp(self):
409         unittest.TestCase.setUp(self)
410         self.__setup_called = True
411
412     def tearDown(self):
413         unittest.TestCase.tearDown(self)
414         self.__teardown_called = True
415
416
417 # Python 2.4 did not know how to copy functions.
418 if types.FunctionType not in copy._copy_dispatch:
419     copy._copy_dispatch[types.FunctionType] = copy._copy_immutable
420
421
422
423 def clone_test_with_new_id(test, new_id):
424     """Copy a TestCase, and give the copied test a new id.
425     
426     This is only expected to be used on tests that have been constructed but
427     not executed.
428     """
429     newTest = copy.copy(test)
430     newTest.id = lambda: new_id
431     return newTest
432
433
434 def skip(reason):
435     """A decorator to skip unit tests.
436
437     This is just syntactic sugar so users don't have to change any of their
438     unit tests in order to migrate to python 2.7, which provides the
439     @unittest.skip decorator.
440     """
441     def decorator(test_item):
442         if wraps is not None:
443             @wraps(test_item)
444             def skip_wrapper(*args, **kwargs):
445                 raise TestCase.skipException(reason)
446         else:
447             def skip_wrapper(test_item):
448                 test_item.skip(reason)
449         return skip_wrapper
450     return decorator
451
452
453 def skipIf(condition, reason):
454     """Skip a test if the condition is true."""
455     if condition:
456         return skip(reason)
457     def _id(obj):
458         return obj
459     return _id
460
461
462 def skipUnless(condition, reason):
463     """Skip a test unless the condition is true."""
464     if not condition:
465         return skip(reason)
466     def _id(obj):
467         return obj
468     return _id