1 # Copyright (c) 2009-2011 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__'
43 from pprint
import pformat
48 from testtools
.compat
import (
59 class Matcher(object):
62 A Matcher must implement match and __str__ to be used by
63 testtools.TestCase.assertThat. Matcher.match(thing) returns None when
64 thing is completely matched, and a Mismatch object otherwise.
66 Matchers can be useful outside of test cases, as they are simply a
67 pattern matching language expressed as objects.
69 testtools.matchers is inspired by hamcrest, but is pythonic rather than
73 def match(self
, something
):
74 """Return None if this matcher matches something, a Mismatch otherwise.
76 raise NotImplementedError(self
.match
)
79 """Get a sensible human representation of the matcher.
81 This should include the parameters given to the matcher and any
82 state that would affect the matches operation.
84 raise NotImplementedError(self
.__str
__)
87 class Mismatch(object):
88 """An object describing a mismatch detected by a Matcher."""
90 def __init__(self
, description
=None, details
=None):
91 """Construct a `Mismatch`.
93 :param description: A description to use. If not provided,
94 `Mismatch.describe` must be implemented.
95 :param details: Extra details about the mismatch. Defaults
99 self
._description
= description
102 self
._details
= details
105 """Describe the mismatch.
107 This should be either a human-readable string or castable to a string.
108 In particular, is should either be plain ascii or unicode on Python 2,
109 and care should be taken to escape control characters.
112 return self
._description
113 except AttributeError:
114 raise NotImplementedError(self
.describe
)
116 def get_details(self
):
117 """Get extra details about the mismatch.
119 This allows the mismatch to provide extra information beyond the basic
120 description, including large text or binary files, or debugging internals
121 without having to force it to fit in the output of 'describe'.
123 The testtools assertion assertThat will query get_details and attach
124 all its values to the test, permitting them to be reported in whatever
125 manner the test environment chooses.
127 :return: a dict mapping names to Content objects. name is a string to
128 name the detail, and the Content object is the detail to add
129 to the result. For more information see the API to which items from
130 this dict are passed testtools.TestCase.addDetail.
132 return getattr(self
, '_details', {})
135 return "<testtools.matchers.Mismatch object at %x attributes=%r>" % (
136 id(self
), self
.__dict
__)
139 class MismatchError(AssertionError):
140 """Raised when a mismatch occurs."""
142 # This class exists to work around
143 # <https://bugs.launchpad.net/testtools/+bug/804127>. It provides a
144 # guaranteed way of getting a readable exception, no matter what crazy
145 # characters are in the matchee, matcher or mismatch.
147 def __init__(self
, matchee
, matcher
, mismatch
, verbose
=False):
148 # Have to use old-style upcalling for Python 2.4 and 2.5
150 AssertionError.__init
__(self
)
151 self
.matchee
= matchee
152 self
.matcher
= matcher
153 self
.mismatch
= mismatch
154 self
.verbose
= verbose
157 difference
= self
.mismatch
.describe()
159 # GZ 2011-08-24: Smelly API? Better to take any object and special
161 if istext(self
.matchee
) or _isbytes(self
.matchee
):
162 matchee
= text_repr(self
.matchee
, multiline
=False)
164 matchee
= repr(self
.matchee
)
166 'Match failed. Matchee: %s\nMatcher: %s\nDifference: %s\n'
167 % (matchee
, self
.matcher
, difference
))
171 if not str_is_unicode
:
173 __unicode__
= __str__
176 return self
.__unicode
__().encode("ascii", "backslashreplace")
179 class MismatchDecorator(object):
180 """Decorate a ``Mismatch``.
182 Forwards all messages to the original mismatch object. Probably the best
183 way to use this is inherit from this class and then provide your own
184 custom decoration logic.
187 def __init__(self
, original
):
188 """Construct a `MismatchDecorator`.
190 :param original: A `Mismatch` object to decorate.
192 self
.original
= original
195 return '<testtools.matchers.MismatchDecorator(%r)>' % (self
.original
,)
198 return self
.original
.describe()
200 def get_details(self
):
201 return self
.original
.get_details()
204 class _NonManglingOutputChecker(doctest
.OutputChecker
):
205 """Doctest checker that works with unicode rather than mangling strings
207 This is needed because current Python versions have tried to fix string
208 encoding related problems, but regressed the default behaviour with unicode
209 inputs in the process.
211 In Python 2.6 and 2.7 `OutputChecker.output_difference` is was changed to
212 return a bytestring encoded as per `sys.stdout.encoding`, or utf-8 if that
213 can't be determined. Worse, that encoding process happens in the innocent
214 looking `_indent` global function. Because the `DocTestMismatch.describe`
215 result may well not be destined for printing to stdout, this is no good
216 for us. To get a unicode return as before, the method is monkey patched if
217 `doctest._encoding` exists.
219 Python 3 has a different problem. For some reason both inputs are encoded
220 to ascii with 'backslashreplace', making an escaped string matches its
221 unescaped form. Overriding the offending `OutputChecker._toAscii` method
222 is sufficient to revert this.
225 def _toAscii(self
, s
):
226 """Return `s` unchanged rather than mangling it to ascii"""
229 # Only do this overriding hackery if doctest has a broken _input function
230 if getattr(doctest
, "_encoding", None) is not None:
231 from types
import FunctionType
as __F
232 __f
= doctest
.OutputChecker
.output_difference
.im_func
233 __g
= dict(__f
.func_globals
)
234 def _indent(s
, indent
=4, _pattern
=re
.compile("^(?!$)", re
.MULTILINE
)):
235 """Prepend non-empty lines in `s` with `indent` number of spaces"""
236 return _pattern
.sub(indent
*" ", s
)
237 __g
["_indent"] = _indent
238 output_difference
= __F(__f
.func_code
, __g
, "output_difference")
239 del __F
, __f
, __g
, _indent
242 class DocTestMatches(object):
243 """See if a string matches a doctest example."""
245 def __init__(self
, example
, flags
=0):
246 """Create a DocTestMatches to match example.
248 :param example: The example to match e.g. 'foo bar baz'
249 :param flags: doctest comparison flags to match on. e.g.
252 if not example
.endswith('\n'):
254 self
.want
= example
# required variable name by doctest.
256 self
._checker
= _NonManglingOutputChecker()
260 flagstr
= ", flags=%d" % self
.flags
263 return 'DocTestMatches(%r%s)' % (self
.want
, flagstr
)
265 def _with_nl(self
, actual
):
266 result
= self
.want
.__class
__(actual
)
267 if not result
.endswith('\n'):
271 def match(self
, actual
):
272 with_nl
= self
._with
_nl
(actual
)
273 if self
._checker
.check_output(self
.want
, with_nl
, self
.flags
):
275 return DocTestMismatch(self
, with_nl
)
277 def _describe_difference(self
, with_nl
):
278 return self
._checker
.output_difference(self
, with_nl
, self
.flags
)
281 class DocTestMismatch(Mismatch
):
282 """Mismatch object for DocTestMatches."""
284 def __init__(self
, matcher
, with_nl
):
285 self
.matcher
= matcher
286 self
.with_nl
= with_nl
289 s
= self
.matcher
._describe
_difference
(self
.with_nl
)
290 if str_is_unicode
or isinstance(s
, unicode):
292 # GZ 2011-08-24: This is actually pretty bogus, most C0 codes should
293 # be escaped, in addition to non-ascii bytes.
294 return s
.decode("latin1").encode("ascii", "backslashreplace")
297 class DoesNotContain(Mismatch
):
299 def __init__(self
, matchee
, needle
):
300 """Create a DoesNotContain Mismatch.
302 :param matchee: the object that did not contain needle.
303 :param needle: the needle that 'matchee' was expected to contain.
305 self
.matchee
= matchee
309 return "%r not in %r" % (self
.needle
, self
.matchee
)
312 class DoesNotStartWith(Mismatch
):
314 def __init__(self
, matchee
, expected
):
315 """Create a DoesNotStartWith Mismatch.
317 :param matchee: the string that did not match.
318 :param expected: the string that 'matchee' was expected to start with.
320 self
.matchee
= matchee
321 self
.expected
= expected
324 return "%s does not start with %s." % (
325 text_repr(self
.matchee
), text_repr(self
.expected
))
328 class DoesNotEndWith(Mismatch
):
330 def __init__(self
, matchee
, expected
):
331 """Create a DoesNotEndWith Mismatch.
333 :param matchee: the string that did not match.
334 :param expected: the string that 'matchee' was expected to end with.
336 self
.matchee
= matchee
337 self
.expected
= expected
340 return "%s does not end with %s." % (
341 text_repr(self
.matchee
), text_repr(self
.expected
))
344 class _BinaryComparison(object):
345 """Matcher that compares an object to another object."""
347 def __init__(self
, expected
):
348 self
.expected
= expected
351 return "%s(%r)" % (self
.__class
__.__name
__, self
.expected
)
353 def match(self
, other
):
354 if self
.comparator(other
, self
.expected
):
356 return _BinaryMismatch(self
.expected
, self
.mismatch_string
, other
)
358 def comparator(self
, expected
, other
):
359 raise NotImplementedError(self
.comparator
)
362 class _BinaryMismatch(Mismatch
):
363 """Two things did not match."""
365 def __init__(self
, expected
, mismatch_string
, other
):
366 self
.expected
= expected
367 self
._mismatch
_string
= mismatch_string
370 def _format(self
, thing
):
371 # Blocks of text with newlines are formatted as triple-quote
372 # strings. Everything else is pretty-printed.
373 if istext(thing
) or _isbytes(thing
):
374 return text_repr(thing
)
375 return pformat(thing
)
378 left
= repr(self
.expected
)
379 right
= repr(self
.other
)
380 if len(left
) + len(right
) > 70:
381 return "%s:\nreference = %s\nactual = %s\n" % (
382 self
._mismatch
_string
, self
._format
(self
.expected
),
383 self
._format
(self
.other
))
385 return "%s %s %s" % (left
, self
._mismatch
_string
, right
)
388 class Equals(_BinaryComparison
):
389 """Matches if the items are equal."""
391 comparator
= operator
.eq
392 mismatch_string
= '!='
395 class NotEquals(_BinaryComparison
):
396 """Matches if the items are not equal.
398 In most cases, this is equivalent to ``Not(Equals(foo))``. The difference
399 only matters when testing ``__ne__`` implementations.
402 comparator
= operator
.ne
403 mismatch_string
= '=='
406 class Is(_BinaryComparison
):
407 """Matches if the items are identical."""
409 comparator
= operator
.is_
410 mismatch_string
= 'is not'
413 class IsInstance(object):
414 """Matcher that wraps isinstance."""
416 def __init__(self
, *types
):
417 self
.types
= tuple(types
)
420 return "%s(%s)" % (self
.__class
__.__name
__,
421 ', '.join(type.__name
__ for type in self
.types
))
423 def match(self
, other
):
424 if isinstance(other
, self
.types
):
426 return NotAnInstance(other
, self
.types
)
429 class NotAnInstance(Mismatch
):
431 def __init__(self
, matchee
, types
):
432 """Create a NotAnInstance Mismatch.
434 :param matchee: the thing which is not an instance of any of types.
435 :param types: A tuple of the types which were expected.
437 self
.matchee
= matchee
441 if len(self
.types
) == 1:
442 typestr
= self
.types
[0].__name
__
444 typestr
= 'any of (%s)' % ', '.join(type.__name
__ for type in
446 return "'%s' is not an instance of %s" % (self
.matchee
, typestr
)
449 class LessThan(_BinaryComparison
):
450 """Matches if the item is less than the matchers reference object."""
452 comparator
= operator
.__lt
__
453 mismatch_string
= 'is not >'
456 class GreaterThan(_BinaryComparison
):
457 """Matches if the item is greater than the matchers reference object."""
459 comparator
= operator
.__gt
__
460 mismatch_string
= 'is not <'
463 class MatchesAny(object):
464 """Matches if any of the matchers it is created with match."""
466 def __init__(self
, *matchers
):
467 self
.matchers
= matchers
469 def match(self
, matchee
):
471 for matcher
in self
.matchers
:
472 mismatch
= matcher
.match(matchee
)
475 results
.append(mismatch
)
476 return MismatchesAll(results
)
479 return "MatchesAny(%s)" % ', '.join([
480 str(matcher
) for matcher
in self
.matchers
])
483 class MatchesAll(object):
484 """Matches if all of the matchers it is created with match."""
486 def __init__(self
, *matchers
):
487 self
.matchers
= matchers
490 return 'MatchesAll(%s)' % ', '.join(map(str, self
.matchers
))
492 def match(self
, matchee
):
494 for matcher
in self
.matchers
:
495 mismatch
= matcher
.match(matchee
)
496 if mismatch
is not None:
497 results
.append(mismatch
)
499 return MismatchesAll(results
)
504 class MismatchesAll(Mismatch
):
505 """A mismatch with many child mismatches."""
507 def __init__(self
, mismatches
):
508 self
.mismatches
= mismatches
511 descriptions
= ["Differences: ["]
512 for mismatch
in self
.mismatches
:
513 descriptions
.append(mismatch
.describe())
514 descriptions
.append("]")
515 return '\n'.join(descriptions
)
519 """Inverts a matcher."""
521 def __init__(self
, matcher
):
522 self
.matcher
= matcher
525 return 'Not(%s)' % (self
.matcher
,)
527 def match(self
, other
):
528 mismatch
= self
.matcher
.match(other
)
530 return MatchedUnexpectedly(self
.matcher
, other
)
535 class MatchedUnexpectedly(Mismatch
):
536 """A thing matched when it wasn't supposed to."""
538 def __init__(self
, matcher
, other
):
539 self
.matcher
= matcher
543 return "%r matches %s" % (self
.other
, self
.matcher
)
546 class MatchesException(Matcher
):
547 """Match an exc_info tuple against an exception instance or type."""
549 def __init__(self
, exception
, value_re
=None):
550 """Create a MatchesException that will match exc_info's for exception.
552 :param exception: Either an exception instance or type.
553 If an instance is given, the type and arguments of the exception
554 are checked. If a type is given only the type of the exception is
555 checked. If a tuple is given, then as with isinstance, any of the
556 types in the tuple matching is sufficient to match.
557 :param value_re: If 'exception' is a type, and the matchee exception
558 is of the right type, then match against this. If value_re is a
559 string, then assume value_re is a regular expression and match
560 the str() of the exception against it. Otherwise, assume value_re
561 is a matcher, and match the exception against it.
563 Matcher
.__init
__(self
)
564 self
.expected
= exception
566 value_re
= AfterPreproccessing(str, MatchesRegex(value_re
), False)
567 self
.value_re
= value_re
568 self
._is
_instance
= type(self
.expected
) not in classtypes() + (tuple,)
570 def match(self
, other
):
571 if type(other
) != tuple:
572 return Mismatch('%r is not an exc_info tuple' % other
)
573 expected_class
= self
.expected
574 if self
._is
_instance
:
575 expected_class
= expected_class
.__class
__
576 if not issubclass(other
[0], expected_class
):
577 return Mismatch('%r is not a %r' % (other
[0], expected_class
))
578 if self
._is
_instance
:
579 if other
[1].args
!= self
.expected
.args
:
580 return Mismatch('%s has different arguments to %s.' % (
581 _error_repr(other
[1]), _error_repr(self
.expected
)))
582 elif self
.value_re
is not None:
583 return self
.value_re
.match(other
[1])
586 if self
._is
_instance
:
587 return "MatchesException(%s)" % _error_repr(self
.expected
)
588 return "MatchesException(%s)" % repr(self
.expected
)
591 class Contains(Matcher
):
592 """Checks whether something is container in another thing."""
594 def __init__(self
, needle
):
595 """Create a Contains Matcher.
597 :param needle: the thing that needs to be contained by matchees.
602 return "Contains(%r)" % (self
.needle
,)
604 def match(self
, matchee
):
606 if self
.needle
not in matchee
:
607 return DoesNotContain(matchee
, self
.needle
)
609 # e.g. 1 in 2 will raise TypeError
610 return DoesNotContain(matchee
, self
.needle
)
614 class StartsWith(Matcher
):
615 """Checks whether one string starts with another."""
617 def __init__(self
, expected
):
618 """Create a StartsWith Matcher.
620 :param expected: the string that matchees should start with.
622 self
.expected
= expected
625 return "StartsWith(%r)" % (self
.expected
,)
627 def match(self
, matchee
):
628 if not matchee
.startswith(self
.expected
):
629 return DoesNotStartWith(matchee
, self
.expected
)
633 class EndsWith(Matcher
):
634 """Checks whether one string starts with another."""
636 def __init__(self
, expected
):
637 """Create a EndsWith Matcher.
639 :param expected: the string that matchees should end with.
641 self
.expected
= expected
644 return "EndsWith(%r)" % (self
.expected
,)
646 def match(self
, matchee
):
647 if not matchee
.endswith(self
.expected
):
648 return DoesNotEndWith(matchee
, self
.expected
)
652 class KeysEqual(Matcher
):
653 """Checks whether a dict has particular keys."""
655 def __init__(self
, *expected
):
656 """Create a `KeysEqual` Matcher.
658 :param expected: The keys the dict is expected to have. If a dict,
659 then we use the keys of that dict, if a collection, we assume it
660 is a collection of expected keys.
663 self
.expected
= expected
.keys()
664 except AttributeError:
665 self
.expected
= list(expected
)
668 return "KeysEqual(%s)" % ', '.join(map(repr, self
.expected
))
670 def match(self
, matchee
):
671 expected
= sorted(self
.expected
)
672 matched
= Equals(expected
).match(sorted(matchee
.keys()))
674 return AnnotatedMismatch(
676 _BinaryMismatch(expected
, 'does not match', matchee
))
680 class Annotate(object):
681 """Annotates a matcher with a descriptive string.
683 Mismatches are then described as '<mismatch>: <annotation>'.
686 def __init__(self
, annotation
, matcher
):
687 self
.annotation
= annotation
688 self
.matcher
= matcher
691 def if_message(cls
, annotation
, matcher
):
692 """Annotate ``matcher`` only if ``annotation`` is non-empty."""
695 return cls(annotation
, matcher
)
698 return 'Annotate(%r, %s)' % (self
.annotation
, self
.matcher
)
700 def match(self
, other
):
701 mismatch
= self
.matcher
.match(other
)
702 if mismatch
is not None:
703 return AnnotatedMismatch(self
.annotation
, mismatch
)
706 class AnnotatedMismatch(MismatchDecorator
):
707 """A mismatch annotated with a descriptive string."""
709 def __init__(self
, annotation
, mismatch
):
710 super(AnnotatedMismatch
, self
).__init
__(mismatch
)
711 self
.annotation
= annotation
712 self
.mismatch
= mismatch
715 return '%s: %s' % (self
.original
.describe(), self
.annotation
)
718 class Raises(Matcher
):
719 """Match if the matchee raises an exception when called.
721 Exceptions which are not subclasses of Exception propogate out of the
722 Raises.match call unless they are explicitly matched.
725 def __init__(self
, exception_matcher
=None):
726 """Create a Raises matcher.
728 :param exception_matcher: Optional validator for the exception raised
729 by matchee. If supplied the exc_info tuple for the exception raised
730 is passed into that matcher. If no exception_matcher is supplied
731 then the simple fact of raising an exception is considered enough
734 self
.exception_matcher
= exception_matcher
736 def match(self
, matchee
):
739 return Mismatch('%r returned %r' % (matchee
, result
))
740 # Catch all exceptions: Raises() should be able to match a
741 # KeyboardInterrupt or SystemExit.
743 exc_info
= sys
.exc_info()
744 if self
.exception_matcher
:
745 mismatch
= self
.exception_matcher
.match(exc_info
)
751 # The exception did not match, or no explicit matching logic was
752 # performed. If the exception is a non-user exception (that is, not
753 # a subclass of Exception on Python 2.5+) then propogate it.
754 if isbaseexception(exc_info
[1]):
763 def raises(exception
):
764 """Make a matcher that checks that a callable raises an exception.
766 This is a convenience function, exactly equivalent to::
768 return Raises(MatchesException(exception))
770 See `Raises` and `MatchesException` for more information.
772 return Raises(MatchesException(exception
))
775 class MatchesListwise(object):
776 """Matches if each matcher matches the corresponding value.
778 More easily explained by example than in words:
780 >>> MatchesListwise([Equals(1)]).match([1])
781 >>> MatchesListwise([Equals(1), Equals(2)]).match([1, 2])
782 >>> print (MatchesListwise([Equals(1), Equals(2)]).match([2, 1]).describe())
789 def __init__(self
, matchers
):
790 self
.matchers
= matchers
792 def match(self
, values
):
794 length_mismatch
= Annotate(
795 "Length mismatch", Equals(len(self
.matchers
))).match(len(values
))
797 mismatches
.append(length_mismatch
)
798 for matcher
, value
in zip(self
.matchers
, values
):
799 mismatch
= matcher
.match(value
)
801 mismatches
.append(mismatch
)
803 return MismatchesAll(mismatches
)
806 class MatchesStructure(object):
807 """Matcher that matches an object structurally.
809 'Structurally' here means that attributes of the object being matched are
810 compared against given matchers.
812 `fromExample` allows the creation of a matcher from a prototype object and
813 then modified versions can be created with `update`.
815 `byEquality` creates a matcher in much the same way as the constructor,
816 except that the matcher for each of the attributes is assumed to be
819 `byMatcher` creates a similar matcher to `byEquality`, but you get to pick
820 the matcher, rather than just using `Equals`.
823 def __init__(self
, **kwargs
):
824 """Construct a `MatchesStructure`.
826 :param kwargs: A mapping of attributes to matchers.
831 def byEquality(cls
, **kwargs
):
832 """Matches an object where the attributes equal the keyword values.
834 Similar to the constructor, except that the matcher is assumed to be
837 return cls
.byMatcher(Equals
, **kwargs
)
840 def byMatcher(cls
, matcher
, **kwargs
):
841 """Matches an object where the attributes match the keyword values.
843 Similar to the constructor, except that the provided matcher is used
844 to match all of the values.
847 **dict((name
, matcher(value
)) for name
, value
in kwargs
.items()))
850 def fromExample(cls
, example
, *attributes
):
852 for attr
in attributes
:
853 kwargs
[attr
] = Equals(getattr(example
, attr
))
856 def update(self
, **kws
):
857 new_kws
= self
.kws
.copy()
858 for attr
, matcher
in kws
.items():
860 new_kws
.pop(attr
, None)
862 new_kws
[attr
] = matcher
863 return type(self
)(**new_kws
)
867 for attr
, matcher
in sorted(self
.kws
.items()):
868 kws
.append("%s=%s" % (attr
, matcher
))
869 return "%s(%s)" % (self
.__class
__.__name
__, ', '.join(kws
))
871 def match(self
, value
):
874 for attr
, matcher
in sorted(self
.kws
.items()):
875 matchers
.append(Annotate(attr
, matcher
))
876 values
.append(getattr(value
, attr
))
877 return MatchesListwise(matchers
).match(values
)
880 class MatchesRegex(object):
881 """Matches if the matchee is matched by a regular expression."""
883 def __init__(self
, pattern
, flags
=0):
884 self
.pattern
= pattern
888 args
= ['%r' % self
.pattern
]
890 # dir() sorts the attributes for us, so we don't need to do it again.
893 if self
.flags
& getattr(re
, flag
):
894 flag_arg
.append('re.%s' % flag
)
896 args
.append('|'.join(flag_arg
))
897 return '%s(%s)' % (self
.__class
__.__name
__, ', '.join(args
))
899 def match(self
, value
):
900 if not re
.match(self
.pattern
, value
, self
.flags
):
901 pattern
= self
.pattern
902 if not isinstance(pattern
, str_is_unicode
and str or unicode):
903 pattern
= pattern
.decode("latin1")
904 pattern
= pattern
.encode("unicode_escape").decode("ascii")
905 return Mismatch("%r does not match /%s/" % (
906 value
, pattern
.replace("\\\\", "\\")))
909 class MatchesSetwise(object):
910 """Matches if all the matchers match elements of the value being matched.
912 That is, each element in the 'observed' set must match exactly one matcher
913 from the set of matchers, with no matchers left over.
915 The difference compared to `MatchesListwise` is that the order of the
916 matchings does not matter.
919 def __init__(self
, *matchers
):
920 self
.matchers
= matchers
922 def match(self
, observed
):
923 remaining_matchers
= set(self
.matchers
)
925 for value
in observed
:
926 for matcher
in remaining_matchers
:
927 if matcher
.match(value
) is None:
928 remaining_matchers
.remove(matcher
)
931 not_matched
.append(value
)
932 if not_matched
or remaining_matchers
:
933 remaining_matchers
= list(remaining_matchers
)
934 # There are various cases that all should be reported somewhat
937 # There are two trivial cases:
938 # 1) There are just some matchers left over.
939 # 2) There are just some values left over.
941 # Then there are three more interesting cases:
942 # 3) There are the same number of matchers and values left over.
943 # 4) There are more matchers left over than values.
944 # 5) There are more values left over than matchers.
946 if len(not_matched
) == 0:
947 if len(remaining_matchers
) > 1:
948 msg
= "There were %s matchers left over: " % (
949 len(remaining_matchers
),)
951 msg
= "There was 1 matcher left over: "
952 msg
+= ', '.join(map(str, remaining_matchers
))
954 elif len(remaining_matchers
) == 0:
955 if len(not_matched
) > 1:
957 "There were %s values left over: %s" % (
958 len(not_matched
), not_matched
))
961 "There was 1 value left over: %s" % (
964 common_length
= min(len(remaining_matchers
), len(not_matched
))
965 if common_length
== 0:
966 raise AssertionError("common_length can't be 0 here")
967 if common_length
> 1:
968 msg
= "There were %s mismatches" % (common_length
,)
970 msg
= "There was 1 mismatch"
971 if len(remaining_matchers
) > len(not_matched
):
972 extra_matchers
= remaining_matchers
[common_length
:]
973 msg
+= " and %s extra matcher" % (len(extra_matchers
), )
974 if len(extra_matchers
) > 1:
976 msg
+= ': ' + ', '.join(map(str, extra_matchers
))
977 elif len(not_matched
) > len(remaining_matchers
):
978 extra_values
= not_matched
[common_length
:]
979 msg
+= " and %s extra value" % (len(extra_values
), )
980 if len(extra_values
) > 1:
982 msg
+= ': ' + str(extra_values
)
984 msg
, MatchesListwise(remaining_matchers
[:common_length
])
985 ).match(not_matched
[:common_length
])
988 class AfterPreprocessing(object):
989 """Matches if the value matches after passing through a function.
991 This can be used to aid in creating trivial matchers as functions, for
994 def PathHasFileContent(content):
996 return open(path).read()
997 return AfterPreprocessing(_read, Equals(content))
1000 def __init__(self
, preprocessor
, matcher
, annotate
=True):
1001 """Create an AfterPreprocessing matcher.
1003 :param preprocessor: A function called with the matchee before
1005 :param matcher: What to match the preprocessed matchee against.
1006 :param annotate: Whether or not to annotate the matcher with
1007 something explaining how we transformed the matchee. Defaults
1010 self
.preprocessor
= preprocessor
1011 self
.matcher
= matcher
1012 self
.annotate
= annotate
1014 def _str_preprocessor(self
):
1015 if isinstance(self
.preprocessor
, types
.FunctionType
):
1016 return '<function %s>' % self
.preprocessor
.__name
__
1017 return str(self
.preprocessor
)
1020 return "AfterPreprocessing(%s, %s)" % (
1021 self
._str
_preprocessor
(), self
.matcher
)
1023 def match(self
, value
):
1024 after
= self
.preprocessor(value
)
1027 "after %s on %r" % (self
._str
_preprocessor
(), value
),
1030 matcher
= self
.matcher
1031 return matcher
.match(after
)
1033 # This is the old, deprecated. spelling of the name, kept for backwards
1035 AfterPreproccessing
= AfterPreprocessing
1038 class AllMatch(object):
1039 """Matches if all provided values match the given matcher."""
1041 def __init__(self
, matcher
):
1042 self
.matcher
= matcher
1045 return 'AllMatch(%s)' % (self
.matcher
,)
1047 def match(self
, values
):
1049 for value
in values
:
1050 mismatch
= self
.matcher
.match(value
)
1052 mismatches
.append(mismatch
)
1054 return MismatchesAll(mismatches
)
1057 # Signal that this is part of the testing framework, and that code from this
1058 # should not normally appear in tracebacks.