summaryrefslogtreecommitdiff
path: root/lib/testtools/testtools/matchers.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/testtools/testtools/matchers.py')
-rw-r--r--lib/testtools/testtools/matchers.py120
1 files changed, 82 insertions, 38 deletions
diff --git a/lib/testtools/testtools/matchers.py b/lib/testtools/testtools/matchers.py
index 039c84b7c7..6a4c82a2fe 100644
--- a/lib/testtools/testtools/matchers.py
+++ b/lib/testtools/testtools/matchers.py
@@ -15,6 +15,8 @@ __all__ = [
'Annotate',
'DocTestMatches',
'Equals',
+ 'Is',
+ 'LessThan',
'MatchesAll',
'MatchesAny',
'NotEquals',
@@ -22,9 +24,10 @@ __all__ = [
]
import doctest
+import operator
-class Matcher:
+class Matcher(object):
"""A pattern matcher.
A Matcher must implement match and __str__ to be used by
@@ -52,18 +55,53 @@ class Matcher:
raise NotImplementedError(self.__str__)
-class Mismatch:
+class Mismatch(object):
"""An object describing a mismatch detected by a Matcher."""
+ def __init__(self, description=None, details=None):
+ """Construct a `Mismatch`.
+
+ :param description: A description to use. If not provided,
+ `Mismatch.describe` must be implemented.
+ :param details: Extra details about the mismatch. Defaults
+ to the empty dict.
+ """
+ if description:
+ self._description = description
+ if details is None:
+ details = {}
+ self._details = details
+
def describe(self):
"""Describe the mismatch.
This should be either a human-readable string or castable to a string.
"""
- raise NotImplementedError(self.describe_difference)
+ try:
+ return self._description
+ except AttributeError:
+ raise NotImplementedError(self.describe)
+
+ def get_details(self):
+ """Get extra details about the mismatch.
+
+ This allows the mismatch to provide extra information beyond the basic
+ description, including large text or binary files, or debugging internals
+ without having to force it to fit in the output of 'describe'.
+
+ The testtools assertion assertThat will query get_details and attach
+ all its values to the test, permitting them to be reported in whatever
+ manner the test environment chooses.
+
+ :return: a dict mapping names to Content objects. name is a string to
+ name the detail, and the Content object is the detail to add
+ to the result. For more information see the API to which items from
+ this dict are passed testtools.TestCase.addDetail.
+ """
+ return getattr(self, '_details', {})
-class DocTestMatches:
+class DocTestMatches(object):
"""See if a string matches a doctest example."""
def __init__(self, example, flags=0):
@@ -102,7 +140,7 @@ class DocTestMatches:
return self._checker.output_difference(self, with_nl, self.flags)
-class DocTestMismatch:
+class DocTestMismatch(Mismatch):
"""Mismatch object for DocTestMatches."""
def __init__(self, matcher, with_nl):
@@ -113,63 +151,69 @@ class DocTestMismatch:
return self.matcher._describe_difference(self.with_nl)
-class Equals:
- """Matches if the items are equal."""
+class _BinaryComparison(object):
+ """Matcher that compares an object to another object."""
def __init__(self, expected):
self.expected = expected
+ def __str__(self):
+ return "%s(%r)" % (self.__class__.__name__, self.expected)
+
def match(self, other):
- if self.expected == other:
+ if self.comparator(other, self.expected):
return None
- return EqualsMismatch(self.expected, other)
+ return _BinaryMismatch(self.expected, self.mismatch_string, other)
- def __str__(self):
- return "Equals(%r)" % self.expected
+ def comparator(self, expected, other):
+ raise NotImplementedError(self.comparator)
-class EqualsMismatch:
- """Two things differed."""
+class _BinaryMismatch(Mismatch):
+ """Two things did not match."""
- def __init__(self, expected, other):
+ def __init__(self, expected, mismatch_string, other):
self.expected = expected
+ self._mismatch_string = mismatch_string
self.other = other
def describe(self):
- return "%r != %r" % (self.expected, self.other)
+ return "%r %s %r" % (self.expected, self._mismatch_string, self.other)
+
+
+class Equals(_BinaryComparison):
+ """Matches if the items are equal."""
+
+ comparator = operator.eq
+ mismatch_string = '!='
-class NotEquals:
+class NotEquals(_BinaryComparison):
"""Matches if the items are not equal.
In most cases, this is equivalent to `Not(Equals(foo))`. The difference
only matters when testing `__ne__` implementations.
"""
- def __init__(self, expected):
- self.expected = expected
+ comparator = operator.ne
+ mismatch_string = '=='
- def __str__(self):
- return 'NotEquals(%r)' % (self.expected,)
- def match(self, other):
- if self.expected != other:
- return None
- return NotEqualsMismatch(self.expected, other)
+class Is(_BinaryComparison):
+ """Matches if the items are identical."""
+ comparator = operator.is_
+ mismatch_string = 'is not'
-class NotEqualsMismatch:
- """Two things are the same."""
- def __init__(self, expected, other):
- self.expected = expected
- self.other = other
+class LessThan(_BinaryComparison):
+ """Matches if the item is less than the matchers reference object."""
- def describe(self):
- return '%r == %r' % (self.expected, self.other)
+ comparator = operator.__lt__
+ mismatch_string = 'is >='
-class MatchesAny:
+class MatchesAny(object):
"""Matches if any of the matchers it is created with match."""
def __init__(self, *matchers):
@@ -189,7 +233,7 @@ class MatchesAny:
str(matcher) for matcher in self.matchers])
-class MatchesAll:
+class MatchesAll(object):
"""Matches if all of the matchers it is created with match."""
def __init__(self, *matchers):
@@ -210,7 +254,7 @@ class MatchesAll:
return None
-class MismatchesAll:
+class MismatchesAll(Mismatch):
"""A mismatch with many child mismatches."""
def __init__(self, mismatches):
@@ -224,7 +268,7 @@ class MismatchesAll:
return '\n'.join(descriptions)
-class Not:
+class Not(object):
"""Inverts a matcher."""
def __init__(self, matcher):
@@ -241,7 +285,7 @@ class Not:
return None
-class MatchedUnexpectedly:
+class MatchedUnexpectedly(Mismatch):
"""A thing matched when it wasn't supposed to."""
def __init__(self, matcher, other):
@@ -252,7 +296,7 @@ class MatchedUnexpectedly:
return "%r matches %s" % (self.other, self.matcher)
-class Annotate:
+class Annotate(object):
"""Annotates a matcher with a descriptive string.
Mismatches are then described as '<mismatch>: <annotation>'.
@@ -271,7 +315,7 @@ class Annotate:
return AnnotatedMismatch(self.annotation, mismatch)
-class AnnotatedMismatch:
+class AnnotatedMismatch(Mismatch):
"""A mismatch annotated with a descriptive string."""
def __init__(self, annotation, mismatch):