testtools: Update to latest version.
[nivanova/samba-autobuild/.git] / lib / testtools / testtools / matchers / _basic.py
1 # Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
2
3 __all__ = [
4     'Contains',
5     'EndsWith',
6     'Equals',
7     'GreaterThan',
8     'Is',
9     'IsInstance',
10     'LessThan',
11     'MatchesRegex',
12     'NotEquals',
13     'StartsWith',
14     ]
15
16 import operator
17 from pprint import pformat
18 import re
19
20 from ..compat import (
21     _isbytes,
22     istext,
23     str_is_unicode,
24     text_repr,
25     )
26 from ..helpers import list_subtract
27 from ._higherorder import PostfixedMismatch
28 from ._impl import (
29     Matcher,
30     Mismatch,
31     )
32
33
34 def _format(thing):
35     """
36     Blocks of text with newlines are formatted as triple-quote
37     strings. Everything else is pretty-printed.
38     """
39     if istext(thing) or _isbytes(thing):
40         return text_repr(thing)
41     return pformat(thing)
42
43
44 class _BinaryComparison(object):
45     """Matcher that compares an object to another object."""
46
47     def __init__(self, expected):
48         self.expected = expected
49
50     def __str__(self):
51         return "%s(%r)" % (self.__class__.__name__, self.expected)
52
53     def match(self, other):
54         if self.comparator(other, self.expected):
55             return None
56         return _BinaryMismatch(self.expected, self.mismatch_string, other)
57
58     def comparator(self, expected, other):
59         raise NotImplementedError(self.comparator)
60
61
62 class _BinaryMismatch(Mismatch):
63     """Two things did not match."""
64
65     def __init__(self, expected, mismatch_string, other):
66         self.expected = expected
67         self._mismatch_string = mismatch_string
68         self.other = other
69
70     def describe(self):
71         left = repr(self.expected)
72         right = repr(self.other)
73         if len(left) + len(right) > 70:
74             return "%s:\nreference = %s\nactual    = %s\n" % (
75                 self._mismatch_string, _format(self.expected),
76                 _format(self.other))
77         else:
78             return "%s %s %s" % (left, self._mismatch_string, right)
79
80
81 class Equals(_BinaryComparison):
82     """Matches if the items are equal."""
83
84     comparator = operator.eq
85     mismatch_string = '!='
86
87
88 class NotEquals(_BinaryComparison):
89     """Matches if the items are not equal.
90
91     In most cases, this is equivalent to ``Not(Equals(foo))``. The difference
92     only matters when testing ``__ne__`` implementations.
93     """
94
95     comparator = operator.ne
96     mismatch_string = '=='
97
98
99 class Is(_BinaryComparison):
100     """Matches if the items are identical."""
101
102     comparator = operator.is_
103     mismatch_string = 'is not'
104
105
106 class LessThan(_BinaryComparison):
107     """Matches if the item is less than the matchers reference object."""
108
109     comparator = operator.__lt__
110     mismatch_string = 'is not >'
111
112
113 class GreaterThan(_BinaryComparison):
114     """Matches if the item is greater than the matchers reference object."""
115
116     comparator = operator.__gt__
117     mismatch_string = 'is not <'
118
119
120 class SameMembers(Matcher):
121     """Matches if two iterators have the same members.
122
123     This is not the same as set equivalence.  The two iterators must be of the
124     same length and have the same repetitions.
125     """
126
127     def __init__(self, expected):
128         super(SameMembers, self).__init__()
129         self.expected = expected
130
131     def __str__(self):
132         return '%s(%r)' % (self.__class__.__name__, self.expected)
133
134     def match(self, observed):
135         expected_only = list_subtract(self.expected, observed)
136         observed_only = list_subtract(observed, self.expected)
137         if expected_only == observed_only == []:
138             return
139         return PostfixedMismatch(
140             "\nmissing:    %s\nextra:      %s" % (
141                 _format(expected_only), _format(observed_only)),
142             _BinaryMismatch(self.expected, 'elements differ', observed))
143
144
145 class DoesNotStartWith(Mismatch):
146
147     def __init__(self, matchee, expected):
148         """Create a DoesNotStartWith Mismatch.
149
150         :param matchee: the string that did not match.
151         :param expected: the string that 'matchee' was expected to start with.
152         """
153         self.matchee = matchee
154         self.expected = expected
155
156     def describe(self):
157         return "%s does not start with %s." % (
158             text_repr(self.matchee), text_repr(self.expected))
159
160
161 class StartsWith(Matcher):
162     """Checks whether one string starts with another."""
163
164     def __init__(self, expected):
165         """Create a StartsWith Matcher.
166
167         :param expected: the string that matchees should start with.
168         """
169         self.expected = expected
170
171     def __str__(self):
172         return "StartsWith(%r)" % (self.expected,)
173
174     def match(self, matchee):
175         if not matchee.startswith(self.expected):
176             return DoesNotStartWith(matchee, self.expected)
177         return None
178
179
180 class DoesNotEndWith(Mismatch):
181
182     def __init__(self, matchee, expected):
183         """Create a DoesNotEndWith Mismatch.
184
185         :param matchee: the string that did not match.
186         :param expected: the string that 'matchee' was expected to end with.
187         """
188         self.matchee = matchee
189         self.expected = expected
190
191     def describe(self):
192         return "%s does not end with %s." % (
193             text_repr(self.matchee), text_repr(self.expected))
194
195
196 class EndsWith(Matcher):
197     """Checks whether one string ends with another."""
198
199     def __init__(self, expected):
200         """Create a EndsWith Matcher.
201
202         :param expected: the string that matchees should end with.
203         """
204         self.expected = expected
205
206     def __str__(self):
207         return "EndsWith(%r)" % (self.expected,)
208
209     def match(self, matchee):
210         if not matchee.endswith(self.expected):
211             return DoesNotEndWith(matchee, self.expected)
212         return None
213
214
215 class IsInstance(object):
216     """Matcher that wraps isinstance."""
217
218     def __init__(self, *types):
219         self.types = tuple(types)
220
221     def __str__(self):
222         return "%s(%s)" % (self.__class__.__name__,
223                 ', '.join(type.__name__ for type in self.types))
224
225     def match(self, other):
226         if isinstance(other, self.types):
227             return None
228         return NotAnInstance(other, self.types)
229
230
231 class NotAnInstance(Mismatch):
232
233     def __init__(self, matchee, types):
234         """Create a NotAnInstance Mismatch.
235
236         :param matchee: the thing which is not an instance of any of types.
237         :param types: A tuple of the types which were expected.
238         """
239         self.matchee = matchee
240         self.types = types
241
242     def describe(self):
243         if len(self.types) == 1:
244             typestr = self.types[0].__name__
245         else:
246             typestr = 'any of (%s)' % ', '.join(type.__name__ for type in
247                     self.types)
248         return "'%s' is not an instance of %s" % (self.matchee, typestr)
249
250
251 class DoesNotContain(Mismatch):
252
253     def __init__(self, matchee, needle):
254         """Create a DoesNotContain Mismatch.
255
256         :param matchee: the object that did not contain needle.
257         :param needle: the needle that 'matchee' was expected to contain.
258         """
259         self.matchee = matchee
260         self.needle = needle
261
262     def describe(self):
263         return "%r not in %r" % (self.needle, self.matchee)
264
265
266 class Contains(Matcher):
267     """Checks whether something is contained in another thing."""
268
269     def __init__(self, needle):
270         """Create a Contains Matcher.
271
272         :param needle: the thing that needs to be contained by matchees.
273         """
274         self.needle = needle
275
276     def __str__(self):
277         return "Contains(%r)" % (self.needle,)
278
279     def match(self, matchee):
280         try:
281             if self.needle not in matchee:
282                 return DoesNotContain(matchee, self.needle)
283         except TypeError:
284             # e.g. 1 in 2 will raise TypeError
285             return DoesNotContain(matchee, self.needle)
286         return None
287
288
289 class MatchesRegex(object):
290     """Matches if the matchee is matched by a regular expression."""
291
292     def __init__(self, pattern, flags=0):
293         self.pattern = pattern
294         self.flags = flags
295
296     def __str__(self):
297         args = ['%r' % self.pattern]
298         flag_arg = []
299         # dir() sorts the attributes for us, so we don't need to do it again.
300         for flag in dir(re):
301             if len(flag) == 1:
302                 if self.flags & getattr(re, flag):
303                     flag_arg.append('re.%s' % flag)
304         if flag_arg:
305             args.append('|'.join(flag_arg))
306         return '%s(%s)' % (self.__class__.__name__, ', '.join(args))
307
308     def match(self, value):
309         if not re.match(self.pattern, value, self.flags):
310             pattern = self.pattern
311             if not isinstance(pattern, str_is_unicode and str or unicode):
312                 pattern = pattern.decode("latin1")
313             pattern = pattern.encode("unicode_escape").decode("ascii")
314             return Mismatch("%r does not match /%s/" % (
315                     value, pattern.replace("\\\\", "\\")))