1 # Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
3 """Matchers, a way to express complex assertions outside the testcase.
5 Inspired by 'hamcrest'.
7 Matcher provides the abstract API that all matchers need to implement.
9 Bundled matchers are listed in __all__: a list can be obtained by running
10 $ python -c 'import testtools.matchers; print testtools.matchers.__all__'
20 from testtools.compat import (
28 class Matcher(object):
31 A Matcher must implement match and __str__ to be used by
32 testtools.TestCase.assertThat. Matcher.match(thing) returns None when
33 thing is completely matched, and a Mismatch object otherwise.
35 Matchers can be useful outside of test cases, as they are simply a
36 pattern matching language expressed as objects.
38 testtools.matchers is inspired by hamcrest, but is pythonic rather than
42 def match(self, something):
43 """Return None if this matcher matches something, a Mismatch otherwise.
45 raise NotImplementedError(self.match)
48 """Get a sensible human representation of the matcher.
50 This should include the parameters given to the matcher and any
51 state that would affect the matches operation.
53 raise NotImplementedError(self.__str__)
56 class Mismatch(object):
57 """An object describing a mismatch detected by a Matcher."""
59 def __init__(self, description=None, details=None):
60 """Construct a `Mismatch`.
62 :param description: A description to use. If not provided,
63 `Mismatch.describe` must be implemented.
64 :param details: Extra details about the mismatch. Defaults
68 self._description = description
71 self._details = details
74 """Describe the mismatch.
76 This should be either a human-readable string or castable to a string.
77 In particular, is should either be plain ascii or unicode on Python 2,
78 and care should be taken to escape control characters.
81 return self._description
82 except AttributeError:
83 raise NotImplementedError(self.describe)
85 def get_details(self):
86 """Get extra details about the mismatch.
88 This allows the mismatch to provide extra information beyond the basic
89 description, including large text or binary files, or debugging internals
90 without having to force it to fit in the output of 'describe'.
92 The testtools assertion assertThat will query get_details and attach
93 all its values to the test, permitting them to be reported in whatever
94 manner the test environment chooses.
96 :return: a dict mapping names to Content objects. name is a string to
97 name the detail, and the Content object is the detail to add
98 to the result. For more information see the API to which items from
99 this dict are passed testtools.TestCase.addDetail.
101 return getattr(self, '_details', {})
104 return "<testtools.matchers.Mismatch object at %x attributes=%r>" % (
105 id(self), self.__dict__)
108 class MismatchError(AssertionError):
109 """Raised when a mismatch occurs."""
111 # This class exists to work around
112 # <https://bugs.launchpad.net/testtools/+bug/804127>. It provides a
113 # guaranteed way of getting a readable exception, no matter what crazy
114 # characters are in the matchee, matcher or mismatch.
116 def __init__(self, matchee, matcher, mismatch, verbose=False):
117 # Have to use old-style upcalling for Python 2.4 and 2.5
119 AssertionError.__init__(self)
120 self.matchee = matchee
121 self.matcher = matcher
122 self.mismatch = mismatch
123 self.verbose = verbose
126 difference = self.mismatch.describe()
128 # GZ 2011-08-24: Smelly API? Better to take any object and special
130 if istext(self.matchee) or _isbytes(self.matchee):
131 matchee = text_repr(self.matchee, multiline=False)
133 matchee = repr(self.matchee)
135 'Match failed. Matchee: %s\nMatcher: %s\nDifference: %s\n'
136 % (matchee, self.matcher, difference))
140 if not str_is_unicode:
142 __unicode__ = __str__
145 return self.__unicode__().encode("ascii", "backslashreplace")
148 class MismatchDecorator(object):
149 """Decorate a ``Mismatch``.
151 Forwards all messages to the original mismatch object. Probably the best
152 way to use this is inherit from this class and then provide your own
153 custom decoration logic.
156 def __init__(self, original):
157 """Construct a `MismatchDecorator`.
159 :param original: A `Mismatch` object to decorate.
161 self.original = original
164 return '<testtools.matchers.MismatchDecorator(%r)>' % (self.original,)
167 return self.original.describe()
169 def get_details(self):
170 return self.original.get_details()
173 # Signal that this is part of the testing framework, and that code from this
174 # should not normally appear in tracebacks.