Import testtools as well, required for subunit.
[samba.git] / lib / subunit / python / testtools / matchers.py
1 # Copyright (c) 2009 Jonathan M. Lange. See LICENSE for details.
2
3 """Matchers, a way to express complex assertions outside the testcase.
4
5 Inspired by 'hamcrest'.
6
7 Matcher provides the abstract API that all matchers need to implement.
8
9 Bundled matchers are listed in __all__: a list can be obtained by running
10 $ python -c 'import testtools.matchers; print testtools.matchers.__all__'
11 """
12
13 __metaclass__ = type
14 __all__ = [
15     'DocTestMatches',
16     'Equals',
17     'MatchesAny',
18     ]
19
20 import doctest
21
22
23 class Matcher:
24     """A pattern matcher.
25
26     A Matcher must implement match and __str__ to be used by
27     testtools.TestCase.assertThat. Matcher.match(thing) returns None when
28     thing is completely matched, and a Mismatch object otherwise.
29
30     Matchers can be useful outside of test cases, as they are simply a
31     pattern matching language expressed as objects.
32
33     testtools.matchers is inspired by hamcrest, but is pythonic rather than
34     a Java transcription.
35     """
36
37     def match(self, something):
38         """Return None if this matcher matches something, a Mismatch otherwise.
39         """
40         raise NotImplementedError(self.match)
41
42     def __str__(self):
43         """Get a sensible human representation of the matcher.
44
45         This should include the parameters given to the matcher and any
46         state that would affect the matches operation.
47         """
48         raise NotImplementedError(self.__str__)
49
50
51 class Mismatch:
52     """An object describing a mismatch detected by a Matcher."""
53
54     def describe(self):
55         """Describe the mismatch.
56
57         This should be either a human-readable string or castable to a string.
58         """
59         raise NotImplementedError(self.describe_difference)
60
61
62 class DocTestMatches:
63     """See if a string matches a doctest example."""
64
65     def __init__(self, example, flags=0):
66         """Create a DocTestMatches to match example.
67
68         :param example: The example to match e.g. 'foo bar baz'
69         :param flags: doctest comparison flags to match on. e.g.
70             doctest.ELLIPSIS.
71         """
72         if not example.endswith('\n'):
73             example += '\n'
74         self.want = example # required variable name by doctest.
75         self.flags = flags
76         self._checker = doctest.OutputChecker()
77
78     def __str__(self):
79         if self.flags:
80             flagstr = ", flags=%d" % self.flags
81         else:
82             flagstr = ""
83         return 'DocTestMatches(%r%s)' % (self.want, flagstr)
84
85     def _with_nl(self, actual):
86         result = str(actual)
87         if not result.endswith('\n'):
88             result += '\n'
89         return result
90
91     def match(self, actual):
92         with_nl = self._with_nl(actual)
93         if self._checker.check_output(self.want, with_nl, self.flags):
94             return None
95         return DocTestMismatch(self, with_nl)
96
97     def _describe_difference(self, with_nl):
98         return self._checker.output_difference(self, with_nl, self.flags)
99
100
101 class DocTestMismatch:
102     """Mismatch object for DocTestMatches."""
103
104     def __init__(self, matcher, with_nl):
105         self.matcher = matcher
106         self.with_nl = with_nl
107
108     def describe(self):
109         return self.matcher._describe_difference(self.with_nl)
110
111
112 class Equals:
113     """Matches if the items are equal."""
114
115     def __init__(self, expected):
116         self.expected = expected
117
118     def match(self, other):
119         if self.expected == other:
120             return None
121         return EqualsMismatch(self.expected, other)
122
123     def __str__(self):
124         return "Equals(%r)" % self.expected
125
126
127 class EqualsMismatch:
128     """Two things differed."""
129
130     def __init__(self, expected, other):
131         self.expected = expected
132         self.other = other
133
134     def describe(self):
135         return "%r != %r" % (self.expected, self.other)
136
137
138 class MatchesAny:
139     """Matches if any of the matchers it is created with match."""
140
141     def __init__(self, *matchers):
142         self.matchers = matchers
143
144     def match(self, matchee):
145         results = []
146         for matcher in self.matchers:
147             mismatch = matcher.match(matchee)
148             if mismatch is None:
149                 return None
150             results.append(mismatch)
151         return MismatchesAll(results)
152
153     def __str__(self):
154         return "MatchesAny(%s)" % ', '.join([
155             str(matcher) for matcher in self.matchers])
156
157
158 class MismatchesAll:
159     """A mismatch with many child mismatches."""
160
161     def __init__(self, mismatches):
162         self.mismatches = mismatches
163
164     def describe(self):
165         descriptions = ["Differences: ["]
166         for mismatch in self.mismatches:
167             descriptions.append(mismatch.describe())
168         descriptions.append("]\n")
169         return '\n'.join(descriptions)