testtools: Update to latest version.
[metze/samba/wip.git] / lib / testtools / testtools / matchers / _exception.py
1 # Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
2
3 __all__ = [
4     'MatchesException',
5     'Raises',
6     'raises',
7     ]
8
9 import sys
10
11 from testtools.compat import (
12     classtypes,
13     _error_repr,
14     isbaseexception,
15     istext,
16     )
17 from ._basic import MatchesRegex
18 from ._higherorder import AfterPreproccessing
19 from ._impl import (
20     Matcher,
21     Mismatch,
22     )
23
24
25 class MatchesException(Matcher):
26     """Match an exc_info tuple against an exception instance or type."""
27
28     def __init__(self, exception, value_re=None):
29         """Create a MatchesException that will match exc_info's for exception.
30
31         :param exception: Either an exception instance or type.
32             If an instance is given, the type and arguments of the exception
33             are checked. If a type is given only the type of the exception is
34             checked. If a tuple is given, then as with isinstance, any of the
35             types in the tuple matching is sufficient to match.
36         :param value_re: If 'exception' is a type, and the matchee exception
37             is of the right type, then match against this.  If value_re is a
38             string, then assume value_re is a regular expression and match
39             the str() of the exception against it.  Otherwise, assume value_re
40             is a matcher, and match the exception against it.
41         """
42         Matcher.__init__(self)
43         self.expected = exception
44         if istext(value_re):
45             value_re = AfterPreproccessing(str, MatchesRegex(value_re), False)
46         self.value_re = value_re
47         self._is_instance = type(self.expected) not in classtypes() + (tuple,)
48
49     def match(self, other):
50         if type(other) != tuple:
51             return Mismatch('%r is not an exc_info tuple' % other)
52         expected_class = self.expected
53         if self._is_instance:
54             expected_class = expected_class.__class__
55         if not issubclass(other[0], expected_class):
56             return Mismatch('%r is not a %r' % (other[0], expected_class))
57         if self._is_instance:
58             if other[1].args != self.expected.args:
59                 return Mismatch('%s has different arguments to %s.' % (
60                         _error_repr(other[1]), _error_repr(self.expected)))
61         elif self.value_re is not None:
62             return self.value_re.match(other[1])
63
64     def __str__(self):
65         if self._is_instance:
66             return "MatchesException(%s)" % _error_repr(self.expected)
67         return "MatchesException(%s)" % repr(self.expected)
68
69
70 class Raises(Matcher):
71     """Match if the matchee raises an exception when called.
72
73     Exceptions which are not subclasses of Exception propogate out of the
74     Raises.match call unless they are explicitly matched.
75     """
76
77     def __init__(self, exception_matcher=None):
78         """Create a Raises matcher.
79
80         :param exception_matcher: Optional validator for the exception raised
81             by matchee. If supplied the exc_info tuple for the exception raised
82             is passed into that matcher. If no exception_matcher is supplied
83             then the simple fact of raising an exception is considered enough
84             to match on.
85         """
86         self.exception_matcher = exception_matcher
87
88     def match(self, matchee):
89         try:
90             result = matchee()
91             return Mismatch('%r returned %r' % (matchee, result))
92         # Catch all exceptions: Raises() should be able to match a
93         # KeyboardInterrupt or SystemExit.
94         except:
95             exc_info = sys.exc_info()
96             if self.exception_matcher:
97                 mismatch = self.exception_matcher.match(exc_info)
98                 if not mismatch:
99                     del exc_info
100                     return
101             else:
102                 mismatch = None
103             # The exception did not match, or no explicit matching logic was
104             # performed. If the exception is a non-user exception (that is, not
105             # a subclass of Exception on Python 2.5+) then propogate it.
106             if isbaseexception(exc_info[1]):
107                 del exc_info
108                 raise
109             return mismatch
110
111     def __str__(self):
112         return 'Raises()'
113
114
115 def raises(exception):
116     """Make a matcher that checks that a callable raises an exception.
117
118     This is a convenience function, exactly equivalent to::
119
120         return Raises(MatchesException(exception))
121
122     See `Raises` and `MatchesException` for more information.
123     """
124     return Raises(MatchesException(exception))