testtools: Update to latest version.
[metze/samba/wip.git] / lib / testtools / testtools / matchers / _higherorder.py
1 # Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
2
3 __all__ = [
4     'AfterPreprocessing',
5     'AllMatch',
6     'Annotate',
7     'MatchesAny',
8     'MatchesAll',
9     'Not',
10     ]
11
12 import types
13
14 from ._impl import (
15     Matcher,
16     Mismatch,
17     MismatchDecorator,
18     )
19
20
21 class MatchesAny(object):
22     """Matches if any of the matchers it is created with match."""
23
24     def __init__(self, *matchers):
25         self.matchers = matchers
26
27     def match(self, matchee):
28         results = []
29         for matcher in self.matchers:
30             mismatch = matcher.match(matchee)
31             if mismatch is None:
32                 return None
33             results.append(mismatch)
34         return MismatchesAll(results)
35
36     def __str__(self):
37         return "MatchesAny(%s)" % ', '.join([
38             str(matcher) for matcher in self.matchers])
39
40
41 class MatchesAll(object):
42     """Matches if all of the matchers it is created with match."""
43
44     def __init__(self, *matchers, **options):
45         """Construct a MatchesAll matcher.
46
47         Just list the component matchers as arguments in the ``*args``
48         style. If you want only the first mismatch to be reported, past in
49         first_only=True as a keyword argument. By default, all mismatches are
50         reported.
51         """
52         self.matchers = matchers
53         self.first_only = options.get('first_only', False)
54
55     def __str__(self):
56         return 'MatchesAll(%s)' % ', '.join(map(str, self.matchers))
57
58     def match(self, matchee):
59         results = []
60         for matcher in self.matchers:
61             mismatch = matcher.match(matchee)
62             if mismatch is not None:
63                 if self.first_only:
64                     return mismatch
65                 results.append(mismatch)
66         if results:
67             return MismatchesAll(results)
68         else:
69             return None
70
71
72 class MismatchesAll(Mismatch):
73     """A mismatch with many child mismatches."""
74
75     def __init__(self, mismatches, wrap=True):
76         self.mismatches = mismatches
77         self._wrap = wrap
78
79     def describe(self):
80         descriptions = []
81         if self._wrap:
82             descriptions = ["Differences: ["]
83         for mismatch in self.mismatches:
84             descriptions.append(mismatch.describe())
85         if self._wrap:
86             descriptions.append("]")
87         return '\n'.join(descriptions)
88
89
90 class Not(object):
91     """Inverts a matcher."""
92
93     def __init__(self, matcher):
94         self.matcher = matcher
95
96     def __str__(self):
97         return 'Not(%s)' % (self.matcher,)
98
99     def match(self, other):
100         mismatch = self.matcher.match(other)
101         if mismatch is None:
102             return MatchedUnexpectedly(self.matcher, other)
103         else:
104             return None
105
106
107 class MatchedUnexpectedly(Mismatch):
108     """A thing matched when it wasn't supposed to."""
109
110     def __init__(self, matcher, other):
111         self.matcher = matcher
112         self.other = other
113
114     def describe(self):
115         return "%r matches %s" % (self.other, self.matcher)
116
117
118 class Annotate(object):
119     """Annotates a matcher with a descriptive string.
120
121     Mismatches are then described as '<mismatch>: <annotation>'.
122     """
123
124     def __init__(self, annotation, matcher):
125         self.annotation = annotation
126         self.matcher = matcher
127
128     @classmethod
129     def if_message(cls, annotation, matcher):
130         """Annotate ``matcher`` only if ``annotation`` is non-empty."""
131         if not annotation:
132             return matcher
133         return cls(annotation, matcher)
134
135     def __str__(self):
136         return 'Annotate(%r, %s)' % (self.annotation, self.matcher)
137
138     def match(self, other):
139         mismatch = self.matcher.match(other)
140         if mismatch is not None:
141             return AnnotatedMismatch(self.annotation, mismatch)
142
143
144 class PostfixedMismatch(MismatchDecorator):
145     """A mismatch annotated with a descriptive string."""
146
147     def __init__(self, annotation, mismatch):
148         super(PostfixedMismatch, self).__init__(mismatch)
149         self.annotation = annotation
150         self.mismatch = mismatch
151
152     def describe(self):
153         return '%s: %s' % (self.original.describe(), self.annotation)
154
155
156 AnnotatedMismatch = PostfixedMismatch
157
158
159 class PrefixedMismatch(MismatchDecorator):
160
161     def __init__(self, prefix, mismatch):
162         super(PrefixedMismatch, self).__init__(mismatch)
163         self.prefix = prefix
164
165     def describe(self):
166         return '%s: %s' % (self.prefix, self.original.describe())
167
168
169 class AfterPreprocessing(object):
170     """Matches if the value matches after passing through a function.
171
172     This can be used to aid in creating trivial matchers as functions, for
173     example::
174
175       def PathHasFileContent(content):
176           def _read(path):
177               return open(path).read()
178           return AfterPreprocessing(_read, Equals(content))
179     """
180
181     def __init__(self, preprocessor, matcher, annotate=True):
182         """Create an AfterPreprocessing matcher.
183
184         :param preprocessor: A function called with the matchee before
185             matching.
186         :param matcher: What to match the preprocessed matchee against.
187         :param annotate: Whether or not to annotate the matcher with
188             something explaining how we transformed the matchee. Defaults
189             to True.
190         """
191         self.preprocessor = preprocessor
192         self.matcher = matcher
193         self.annotate = annotate
194
195     def _str_preprocessor(self):
196         if isinstance(self.preprocessor, types.FunctionType):
197             return '<function %s>' % self.preprocessor.__name__
198         return str(self.preprocessor)
199
200     def __str__(self):
201         return "AfterPreprocessing(%s, %s)" % (
202             self._str_preprocessor(), self.matcher)
203
204     def match(self, value):
205         after = self.preprocessor(value)
206         if self.annotate:
207             matcher = Annotate(
208                 "after %s on %r" % (self._str_preprocessor(), value),
209                 self.matcher)
210         else:
211             matcher = self.matcher
212         return matcher.match(after)
213
214
215 # This is the old, deprecated. spelling of the name, kept for backwards
216 # compatibility.
217 AfterPreproccessing = AfterPreprocessing
218
219
220 class AllMatch(object):
221     """Matches if all provided values match the given matcher."""
222
223     def __init__(self, matcher):
224         self.matcher = matcher
225
226     def __str__(self):
227         return 'AllMatch(%s)' % (self.matcher,)
228
229     def match(self, values):
230         mismatches = []
231         for value in values:
232             mismatch = self.matcher.match(value)
233             if mismatch:
234                 mismatches.append(mismatch)
235         if mismatches:
236             return MismatchesAll(mismatches)
237
238
239 class MatchesPredicate(Matcher):
240     """Match if a given function returns True.
241
242     It is reasonably common to want to make a very simple matcher based on a
243     function that you already have that returns True or False given a single
244     argument (i.e. a predicate function).  This matcher makes it very easy to
245     do so. e.g.::
246
247       IsEven = MatchesPredicate(lambda x: x % 2 == 0, '%s is not even')
248       self.assertThat(4, IsEven)
249     """
250
251     def __init__(self, predicate, message):
252         """Create a ``MatchesPredicate`` matcher.
253
254         :param predicate: A function that takes a single argument and returns
255             a value that will be interpreted as a boolean.
256         :param message: A message to describe a mismatch.  It will be formatted
257             with '%' and be given whatever was passed to ``match()``. Thus, it
258             needs to contain exactly one thing like '%s', '%d' or '%f'.
259         """
260         self.predicate = predicate
261         self.message = message
262
263     def __str__(self):
264         return '%s(%r, %r)' % (
265             self.__class__.__name__, self.predicate, self.message)
266
267     def match(self, x):
268         if not self.predicate(x):
269             return Mismatch(self.message % x)