1 # Copyright (c) 2009 Jonathan M. Lange. 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__'
29 A Matcher must implement match and __str__ to be used by
30 testtools.TestCase.assertThat. Matcher.match(thing) returns None when
31 thing is completely matched, and a Mismatch object otherwise.
33 Matchers can be useful outside of test cases, as they are simply a
34 pattern matching language expressed as objects.
36 testtools.matchers is inspired by hamcrest, but is pythonic rather than
40 def match(self, something):
41 """Return None if this matcher matches something, a Mismatch otherwise.
43 raise NotImplementedError(self.match)
46 """Get a sensible human representation of the matcher.
48 This should include the parameters given to the matcher and any
49 state that would affect the matches operation.
51 raise NotImplementedError(self.__str__)
55 """An object describing a mismatch detected by a Matcher."""
58 """Describe the mismatch.
60 This should be either a human-readable string or castable to a string.
62 raise NotImplementedError(self.describe_difference)
66 """See if a string matches a doctest example."""
68 def __init__(self, example, flags=0):
69 """Create a DocTestMatches to match example.
71 :param example: The example to match e.g. 'foo bar baz'
72 :param flags: doctest comparison flags to match on. e.g.
75 if not example.endswith('\n'):
77 self.want = example # required variable name by doctest.
79 self._checker = doctest.OutputChecker()
83 flagstr = ", flags=%d" % self.flags
86 return 'DocTestMatches(%r%s)' % (self.want, flagstr)
88 def _with_nl(self, actual):
90 if not result.endswith('\n'):
94 def match(self, actual):
95 with_nl = self._with_nl(actual)
96 if self._checker.check_output(self.want, with_nl, self.flags):
98 return DocTestMismatch(self, with_nl)
100 def _describe_difference(self, with_nl):
101 return self._checker.output_difference(self, with_nl, self.flags)
104 class DocTestMismatch:
105 """Mismatch object for DocTestMatches."""
107 def __init__(self, matcher, with_nl):
108 self.matcher = matcher
109 self.with_nl = with_nl
112 return self.matcher._describe_difference(self.with_nl)
116 """Matches if the items are equal."""
118 def __init__(self, expected):
119 self.expected = expected
121 def match(self, other):
122 if self.expected == other:
124 return EqualsMismatch(self.expected, other)
127 return "Equals(%r)" % self.expected
130 class EqualsMismatch:
131 """Two things differed."""
133 def __init__(self, expected, other):
134 self.expected = expected
138 return "%r != %r" % (self.expected, self.other)
142 """Matches if the items are not equal.
144 In most cases, this is equivalent to `Not(Equals(foo))`. The difference
145 only matters when testing `__ne__` implementations.
148 def __init__(self, expected):
149 self.expected = expected
152 return 'NotEquals(%r)' % (self.expected,)
154 def match(self, other):
155 if self.expected != other:
157 return NotEqualsMismatch(self.expected, other)
160 class NotEqualsMismatch:
161 """Two things are the same."""
163 def __init__(self, expected, other):
164 self.expected = expected
168 return '%r == %r' % (self.expected, self.other)
172 """Matches if any of the matchers it is created with match."""
174 def __init__(self, *matchers):
175 self.matchers = matchers
177 def match(self, matchee):
179 for matcher in self.matchers:
180 mismatch = matcher.match(matchee)
183 results.append(mismatch)
184 return MismatchesAll(results)
187 return "MatchesAny(%s)" % ', '.join([
188 str(matcher) for matcher in self.matchers])
192 """Matches if all of the matchers it is created with match."""
194 def __init__(self, *matchers):
195 self.matchers = matchers
198 return 'MatchesAll(%s)' % ', '.join(map(str, self.matchers))
200 def match(self, matchee):
202 for matcher in self.matchers:
203 mismatch = matcher.match(matchee)
204 if mismatch is not None:
205 results.append(mismatch)
207 return MismatchesAll(results)
213 """A mismatch with many child mismatches."""
215 def __init__(self, mismatches):
216 self.mismatches = mismatches
219 descriptions = ["Differences: ["]
220 for mismatch in self.mismatches:
221 descriptions.append(mismatch.describe())
222 descriptions.append("]\n")
223 return '\n'.join(descriptions)
227 """Inverts a matcher."""
229 def __init__(self, matcher):
230 self.matcher = matcher
233 return 'Not(%s)' % (self.matcher,)
235 def match(self, other):
236 mismatch = self.matcher.match(other)
238 return MatchedUnexpectedly(self.matcher, other)
243 class MatchedUnexpectedly:
244 """A thing matched when it wasn't supposed to."""
246 def __init__(self, matcher, other):
247 self.matcher = matcher
251 return "%r matches %s" % (self.other, self.matcher)