testtools-dev team mailing list archive
-
testtools-dev team
-
Mailing list archive
-
Message #01084
[Merge] lp:~jml/testtools/split-matchers into lp:testtools
Jonathan Lange has proposed merging lp:~jml/testtools/split-matchers into lp:testtools.
Requested reviews:
testtools committers (testtools-committers)
For more details, see:
https://code.launchpad.net/~jml/testtools/split-matchers/+merge/123427
The matchers module is getting too big for me to think about clearly.
Here, I've split it up into what I think are fairly logical groupings.
The names should be mostly clear, except probably for '_core' and '_basic'.
_core is the actual matchers system. _basic is a bunch of matchers for
very common Python concepts.
Thoughts welcome.
jml
--
https://code.launchpad.net/~jml/testtools/split-matchers/+merge/123427
Your team testtools developers is subscribed to branch lp:testtools.
=== modified file 'NEWS'
--- NEWS 2012-08-10 14:32:06 +0000
+++ NEWS 2012-09-08 17:30:27 +0000
@@ -6,6 +6,13 @@
NEXT
~~~~
+Changes
+-------
+
+* The ``testtools.matchers`` package has been split up. No change to the
+ public interface. (Jonathan Lange)
+
+
0.9.16
~~~~~~
=== modified file 'testtools/__init__.py'
--- testtools/__init__.py 2012-08-10 14:32:06 +0000
+++ testtools/__init__.py 2012-09-08 17:30:27 +0000
@@ -34,7 +34,7 @@
try_import,
try_imports,
)
-from testtools.matchers import (
+from testtools.matchers._core import (
Matcher,
)
# Shut up, pyflakes. We are importing for documentation, not for namespacing.
=== added directory 'testtools/matchers'
=== added file 'testtools/matchers/__init__.py'
--- testtools/matchers/__init__.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/__init__.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,105 @@
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
+
+"""All the matchers.
+
+Matchers, a way to express complex assertions outside the testcase.
+
+Inspired by 'hamcrest'.
+
+Matcher provides the abstract API that all matchers need to implement.
+
+Bundled matchers are listed in __all__: a list can be obtained by running
+$ python -c 'import testtools.matchers; print testtools.matchers.__all__'
+"""
+
+__all__ = [
+ 'AfterPreprocessing',
+ 'AllMatch',
+ 'Annotate',
+ 'Contains',
+ 'ContainsAll',
+ 'DirExists',
+ 'DocTestMatches',
+ 'EndsWith',
+ 'Equals',
+ 'FileContains',
+ 'FileExists',
+ 'GreaterThan',
+ 'HasPermissions',
+ 'Is',
+ 'IsInstance',
+ 'KeysEqual',
+ 'LessThan',
+ 'MatchesAll',
+ 'MatchesAny',
+ 'MatchesException',
+ 'MatchesListwise',
+ 'MatchesPredicate',
+ 'MatchesRegex',
+ 'MatchesSetwise',
+ 'MatchesStructure',
+ 'NotEquals',
+ 'Not',
+ 'PathExists',
+ 'Raises',
+ 'raises',
+ 'SamePath',
+ 'StartsWith',
+ 'TarballContains',
+ ]
+
+from ._basic import (
+ Contains,
+ EndsWith,
+ Equals,
+ GreaterThan,
+ Is,
+ IsInstance,
+ LessThan,
+ MatchesRegex,
+ NotEquals,
+ StartsWith,
+ )
+from ._datastructures import (
+ ContainsAll,
+ MatchesListwise,
+ MatchesSetwise,
+ MatchesStructure,
+ )
+from ._dict import (
+ KeysEqual,
+ )
+from ._doctest import (
+ DocTestMatches,
+ )
+from ._exception import (
+ MatchesException,
+ Raises,
+ raises,
+ )
+from ._filesystem import (
+ DirExists,
+ FileContains,
+ FileExists,
+ HasPermissions,
+ PathExists,
+ SamePath,
+ TarballContains,
+ )
+from ._higherorder import (
+ AfterPreprocessing,
+ AllMatch,
+ Annotate,
+ MatchesAll,
+ MatchesAny,
+ MatchesPredicate,
+ Not,
+ )
+
+# XXX: These are not explicitly included in __all__. It's unclear how much of
+# the public interface they really are.
+from ._core import (
+ Matcher,
+ Mismatch,
+ MismatchError,
+ )
=== added file 'testtools/matchers/_basic.py'
--- testtools/matchers/_basic.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/_basic.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,315 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+__all__ = [
+ 'Contains',
+ 'EndsWith',
+ 'Equals',
+ 'GreaterThan',
+ 'Is',
+ 'IsInstance',
+ 'LessThan',
+ 'MatchesRegex',
+ 'NotEquals',
+ 'StartsWith',
+ ]
+
+import operator
+from pprint import pformat
+import re
+
+from ..compat import (
+ _isbytes,
+ istext,
+ str_is_unicode,
+ text_repr,
+ )
+from ..helpers import list_subtract
+from ._core import (
+ Matcher,
+ Mismatch,
+ )
+from ._higherorder import PostfixedMismatch
+
+
+def _format(thing):
+ """
+ Blocks of text with newlines are formatted as triple-quote
+ strings. Everything else is pretty-printed.
+ """
+ if istext(thing) or _isbytes(thing):
+ return text_repr(thing)
+ return pformat(thing)
+
+
+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.comparator(other, self.expected):
+ return None
+ return _BinaryMismatch(self.expected, self.mismatch_string, other)
+
+ def comparator(self, expected, other):
+ raise NotImplementedError(self.comparator)
+
+
+class _BinaryMismatch(Mismatch):
+ """Two things did not match."""
+
+ def __init__(self, expected, mismatch_string, other):
+ self.expected = expected
+ self._mismatch_string = mismatch_string
+ self.other = other
+
+ def describe(self):
+ left = repr(self.expected)
+ right = repr(self.other)
+ if len(left) + len(right) > 70:
+ return "%s:\nreference = %s\nactual = %s\n" % (
+ self._mismatch_string, _format(self.expected),
+ _format(self.other))
+ else:
+ return "%s %s %s" % (left, self._mismatch_string, right)
+
+
+class Equals(_BinaryComparison):
+ """Matches if the items are equal."""
+
+ comparator = operator.eq
+ mismatch_string = '!='
+
+
+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.
+ """
+
+ comparator = operator.ne
+ mismatch_string = '=='
+
+
+class Is(_BinaryComparison):
+ """Matches if the items are identical."""
+
+ comparator = operator.is_
+ mismatch_string = 'is not'
+
+
+class LessThan(_BinaryComparison):
+ """Matches if the item is less than the matchers reference object."""
+
+ comparator = operator.__lt__
+ mismatch_string = 'is not >'
+
+
+class GreaterThan(_BinaryComparison):
+ """Matches if the item is greater than the matchers reference object."""
+
+ comparator = operator.__gt__
+ mismatch_string = 'is not <'
+
+
+class SameMembers(Matcher):
+ """Matches if two iterators have the same members.
+
+ This is not the same as set equivalence. The two iterators must be of the
+ same length and have the same repetitions.
+ """
+
+ def __init__(self, expected):
+ super(SameMembers, self).__init__()
+ self.expected = expected
+
+ def __str__(self):
+ return '%s(%r)' % (self.__class__.__name__, self.expected)
+
+ def match(self, observed):
+ expected_only = list_subtract(self.expected, observed)
+ observed_only = list_subtract(observed, self.expected)
+ if expected_only == observed_only == []:
+ return
+ return PostfixedMismatch(
+ "\nmissing: %s\nextra: %s" % (
+ _format(expected_only), _format(observed_only)),
+ _BinaryMismatch(self.expected, 'elements differ', observed))
+
+
+class DoesNotStartWith(Mismatch):
+
+ def __init__(self, matchee, expected):
+ """Create a DoesNotStartWith Mismatch.
+
+ :param matchee: the string that did not match.
+ :param expected: the string that 'matchee' was expected to start with.
+ """
+ self.matchee = matchee
+ self.expected = expected
+
+ def describe(self):
+ return "%s does not start with %s." % (
+ text_repr(self.matchee), text_repr(self.expected))
+
+
+class StartsWith(Matcher):
+ """Checks whether one string starts with another."""
+
+ def __init__(self, expected):
+ """Create a StartsWith Matcher.
+
+ :param expected: the string that matchees should start with.
+ """
+ self.expected = expected
+
+ def __str__(self):
+ return "StartsWith(%r)" % (self.expected,)
+
+ def match(self, matchee):
+ if not matchee.startswith(self.expected):
+ return DoesNotStartWith(matchee, self.expected)
+ return None
+
+
+class DoesNotEndWith(Mismatch):
+
+ def __init__(self, matchee, expected):
+ """Create a DoesNotEndWith Mismatch.
+
+ :param matchee: the string that did not match.
+ :param expected: the string that 'matchee' was expected to end with.
+ """
+ self.matchee = matchee
+ self.expected = expected
+
+ def describe(self):
+ return "%s does not end with %s." % (
+ text_repr(self.matchee), text_repr(self.expected))
+
+
+class EndsWith(Matcher):
+ """Checks whether one string ends with another."""
+
+ def __init__(self, expected):
+ """Create a EndsWith Matcher.
+
+ :param expected: the string that matchees should end with.
+ """
+ self.expected = expected
+
+ def __str__(self):
+ return "EndsWith(%r)" % (self.expected,)
+
+ def match(self, matchee):
+ if not matchee.endswith(self.expected):
+ return DoesNotEndWith(matchee, self.expected)
+ return None
+
+
+class IsInstance(object):
+ """Matcher that wraps isinstance."""
+
+ def __init__(self, *types):
+ self.types = tuple(types)
+
+ def __str__(self):
+ return "%s(%s)" % (self.__class__.__name__,
+ ', '.join(type.__name__ for type in self.types))
+
+ def match(self, other):
+ if isinstance(other, self.types):
+ return None
+ return NotAnInstance(other, self.types)
+
+
+class NotAnInstance(Mismatch):
+
+ def __init__(self, matchee, types):
+ """Create a NotAnInstance Mismatch.
+
+ :param matchee: the thing which is not an instance of any of types.
+ :param types: A tuple of the types which were expected.
+ """
+ self.matchee = matchee
+ self.types = types
+
+ def describe(self):
+ if len(self.types) == 1:
+ typestr = self.types[0].__name__
+ else:
+ typestr = 'any of (%s)' % ', '.join(type.__name__ for type in
+ self.types)
+ return "'%s' is not an instance of %s" % (self.matchee, typestr)
+
+
+class DoesNotContain(Mismatch):
+
+ def __init__(self, matchee, needle):
+ """Create a DoesNotContain Mismatch.
+
+ :param matchee: the object that did not contain needle.
+ :param needle: the needle that 'matchee' was expected to contain.
+ """
+ self.matchee = matchee
+ self.needle = needle
+
+ def describe(self):
+ return "%r not in %r" % (self.needle, self.matchee)
+
+
+class Contains(Matcher):
+ """Checks whether something is contained in another thing."""
+
+ def __init__(self, needle):
+ """Create a Contains Matcher.
+
+ :param needle: the thing that needs to be contained by matchees.
+ """
+ self.needle = needle
+
+ def __str__(self):
+ return "Contains(%r)" % (self.needle,)
+
+ def match(self, matchee):
+ try:
+ if self.needle not in matchee:
+ return DoesNotContain(matchee, self.needle)
+ except TypeError:
+ # e.g. 1 in 2 will raise TypeError
+ return DoesNotContain(matchee, self.needle)
+ return None
+
+
+class MatchesRegex(object):
+ """Matches if the matchee is matched by a regular expression."""
+
+ def __init__(self, pattern, flags=0):
+ self.pattern = pattern
+ self.flags = flags
+
+ def __str__(self):
+ args = ['%r' % self.pattern]
+ flag_arg = []
+ # dir() sorts the attributes for us, so we don't need to do it again.
+ for flag in dir(re):
+ if len(flag) == 1:
+ if self.flags & getattr(re, flag):
+ flag_arg.append('re.%s' % flag)
+ if flag_arg:
+ args.append('|'.join(flag_arg))
+ return '%s(%s)' % (self.__class__.__name__, ', '.join(args))
+
+ def match(self, value):
+ if not re.match(self.pattern, value, self.flags):
+ pattern = self.pattern
+ if not isinstance(pattern, str_is_unicode and str or unicode):
+ pattern = pattern.decode("latin1")
+ pattern = pattern.encode("unicode_escape").decode("ascii")
+ return Mismatch("%r does not match /%s/" % (
+ value, pattern.replace("\\\\", "\\")))
=== renamed file 'testtools/matchers.py' => 'testtools/matchers/_core.py'
--- testtools/matchers.py 2012-08-10 14:05:57 +0000
+++ testtools/matchers/_core.py 2012-09-08 17:30:27 +0000
@@ -1,4 +1,4 @@
-# Copyright (c) 2009-2011 testtools developers. See LICENSE for details.
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
"""Matchers, a way to express complex assertions outside the testcase.
@@ -10,67 +10,19 @@
$ python -c 'import testtools.matchers; print testtools.matchers.__all__'
"""
-__metaclass__ = type
__all__ = [
- 'AfterPreprocessing',
- 'AllMatch',
- 'Annotate',
- 'Contains',
- 'ContainsAll',
- 'DirExists',
- 'DocTestMatches',
- 'EndsWith',
- 'Equals',
- 'FileContains',
- 'FileExists',
- 'GreaterThan',
- 'HasPermissions',
- 'Is',
- 'IsInstance',
- 'KeysEqual',
- 'LessThan',
- 'MatchesAll',
- 'MatchesAny',
- 'MatchesException',
- 'MatchesListwise',
- 'MatchesPredicate',
- 'MatchesRegex',
- 'MatchesSetwise',
- 'MatchesStructure',
- 'NotEquals',
- 'Not',
- 'PathExists',
- 'Raises',
- 'raises',
- 'SamePath',
- 'StartsWith',
- 'TarballContains',
+ 'Matcher',
+ 'Mismatch',
+ 'MismatchDecorator',
+ 'MismatchError',
]
-import doctest
-import operator
-from pprint import pformat
-import re
-import os
-import sys
-import tarfile
-import types
-
from testtools.compat import (
- classtypes,
- _error_repr,
- isbaseexception,
_isbytes,
istext,
str_is_unicode,
text_repr
)
-from testtools.helpers import (
- dict_subtract,
- filter_values,
- list_subtract,
- map_values,
- )
class Matcher(object):
@@ -218,1349 +170,6 @@
return self.original.get_details()
-def _format(thing):
- """
- Blocks of text with newlines are formatted as triple-quote
- strings. Everything else is pretty-printed.
- """
- if istext(thing) or _isbytes(thing):
- return text_repr(thing)
- return pformat(thing)
-
-
-class _NonManglingOutputChecker(doctest.OutputChecker):
- """Doctest checker that works with unicode rather than mangling strings
-
- This is needed because current Python versions have tried to fix string
- encoding related problems, but regressed the default behaviour with
- unicode inputs in the process.
-
- In Python 2.6 and 2.7 ``OutputChecker.output_difference`` is was changed
- to return a bytestring encoded as per ``sys.stdout.encoding``, or utf-8 if
- that can't be determined. Worse, that encoding process happens in the
- innocent looking `_indent` global function. Because the
- `DocTestMismatch.describe` result may well not be destined for printing to
- stdout, this is no good for us. To get a unicode return as before, the
- method is monkey patched if ``doctest._encoding`` exists.
-
- Python 3 has a different problem. For some reason both inputs are encoded
- to ascii with 'backslashreplace', making an escaped string matches its
- unescaped form. Overriding the offending ``OutputChecker._toAscii`` method
- is sufficient to revert this.
- """
-
- def _toAscii(self, s):
- """Return ``s`` unchanged rather than mangling it to ascii"""
- return s
-
- # Only do this overriding hackery if doctest has a broken _input function
- if getattr(doctest, "_encoding", None) is not None:
- from types import FunctionType as __F
- __f = doctest.OutputChecker.output_difference.im_func
- __g = dict(__f.func_globals)
- def _indent(s, indent=4, _pattern=re.compile("^(?!$)", re.MULTILINE)):
- """Prepend non-empty lines in ``s`` with ``indent`` number of spaces"""
- return _pattern.sub(indent*" ", s)
- __g["_indent"] = _indent
- output_difference = __F(__f.func_code, __g, "output_difference")
- del __F, __f, __g, _indent
-
-
-class DocTestMatches(object):
- """See if a string matches a doctest example."""
-
- def __init__(self, example, flags=0):
- """Create a DocTestMatches to match example.
-
- :param example: The example to match e.g. 'foo bar baz'
- :param flags: doctest comparison flags to match on. e.g.
- doctest.ELLIPSIS.
- """
- if not example.endswith('\n'):
- example += '\n'
- self.want = example # required variable name by doctest.
- self.flags = flags
- self._checker = _NonManglingOutputChecker()
-
- def __str__(self):
- if self.flags:
- flagstr = ", flags=%d" % self.flags
- else:
- flagstr = ""
- return 'DocTestMatches(%r%s)' % (self.want, flagstr)
-
- def _with_nl(self, actual):
- result = self.want.__class__(actual)
- if not result.endswith('\n'):
- result += '\n'
- return result
-
- def match(self, actual):
- with_nl = self._with_nl(actual)
- if self._checker.check_output(self.want, with_nl, self.flags):
- return None
- return DocTestMismatch(self, with_nl)
-
- def _describe_difference(self, with_nl):
- return self._checker.output_difference(self, with_nl, self.flags)
-
-
-class DocTestMismatch(Mismatch):
- """Mismatch object for DocTestMatches."""
-
- def __init__(self, matcher, with_nl):
- self.matcher = matcher
- self.with_nl = with_nl
-
- def describe(self):
- s = self.matcher._describe_difference(self.with_nl)
- if str_is_unicode or isinstance(s, unicode):
- return s
- # GZ 2011-08-24: This is actually pretty bogus, most C0 codes should
- # be escaped, in addition to non-ascii bytes.
- return s.decode("latin1").encode("ascii", "backslashreplace")
-
-
-class DoesNotContain(Mismatch):
-
- def __init__(self, matchee, needle):
- """Create a DoesNotContain Mismatch.
-
- :param matchee: the object that did not contain needle.
- :param needle: the needle that 'matchee' was expected to contain.
- """
- self.matchee = matchee
- self.needle = needle
-
- def describe(self):
- return "%r not in %r" % (self.needle, self.matchee)
-
-
-class DoesNotStartWith(Mismatch):
-
- def __init__(self, matchee, expected):
- """Create a DoesNotStartWith Mismatch.
-
- :param matchee: the string that did not match.
- :param expected: the string that 'matchee' was expected to start with.
- """
- self.matchee = matchee
- self.expected = expected
-
- def describe(self):
- return "%s does not start with %s." % (
- text_repr(self.matchee), text_repr(self.expected))
-
-
-class DoesNotEndWith(Mismatch):
-
- def __init__(self, matchee, expected):
- """Create a DoesNotEndWith Mismatch.
-
- :param matchee: the string that did not match.
- :param expected: the string that 'matchee' was expected to end with.
- """
- self.matchee = matchee
- self.expected = expected
-
- def describe(self):
- return "%s does not end with %s." % (
- text_repr(self.matchee), text_repr(self.expected))
-
-
-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.comparator(other, self.expected):
- return None
- return _BinaryMismatch(self.expected, self.mismatch_string, other)
-
- def comparator(self, expected, other):
- raise NotImplementedError(self.comparator)
-
-
-class _BinaryMismatch(Mismatch):
- """Two things did not match."""
-
- def __init__(self, expected, mismatch_string, other):
- self.expected = expected
- self._mismatch_string = mismatch_string
- self.other = other
-
- def describe(self):
- left = repr(self.expected)
- right = repr(self.other)
- if len(left) + len(right) > 70:
- return "%s:\nreference = %s\nactual = %s\n" % (
- self._mismatch_string, _format(self.expected),
- _format(self.other))
- else:
- return "%s %s %s" % (left, self._mismatch_string, right)
-
-
-class MatchesPredicate(Matcher):
- """Match if a given function returns True.
-
- It is reasonably common to want to make a very simple matcher based on a
- function that you already have that returns True or False given a single
- argument (i.e. a predicate function). This matcher makes it very easy to
- do so. e.g.::
-
- IsEven = MatchesPredicate(lambda x: x % 2 == 0, '%s is not even')
- self.assertThat(4, IsEven)
- """
-
- def __init__(self, predicate, message):
- """Create a ``MatchesPredicate`` matcher.
-
- :param predicate: A function that takes a single argument and returns
- a value that will be interpreted as a boolean.
- :param message: A message to describe a mismatch. It will be formatted
- with '%' and be given whatever was passed to ``match()``. Thus, it
- needs to contain exactly one thing like '%s', '%d' or '%f'.
- """
- self.predicate = predicate
- self.message = message
-
- def __str__(self):
- return '%s(%r, %r)' % (
- self.__class__.__name__, self.predicate, self.message)
-
- def match(self, x):
- if not self.predicate(x):
- return Mismatch(self.message % x)
-
-
-class Equals(_BinaryComparison):
- """Matches if the items are equal."""
-
- comparator = operator.eq
- mismatch_string = '!='
-
-
-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.
- """
-
- comparator = operator.ne
- mismatch_string = '=='
-
-
-class Is(_BinaryComparison):
- """Matches if the items are identical."""
-
- comparator = operator.is_
- mismatch_string = 'is not'
-
-
-class IsInstance(object):
- """Matcher that wraps isinstance."""
-
- def __init__(self, *types):
- self.types = tuple(types)
-
- def __str__(self):
- return "%s(%s)" % (self.__class__.__name__,
- ', '.join(type.__name__ for type in self.types))
-
- def match(self, other):
- if isinstance(other, self.types):
- return None
- return NotAnInstance(other, self.types)
-
-
-class NotAnInstance(Mismatch):
-
- def __init__(self, matchee, types):
- """Create a NotAnInstance Mismatch.
-
- :param matchee: the thing which is not an instance of any of types.
- :param types: A tuple of the types which were expected.
- """
- self.matchee = matchee
- self.types = types
-
- def describe(self):
- if len(self.types) == 1:
- typestr = self.types[0].__name__
- else:
- typestr = 'any of (%s)' % ', '.join(type.__name__ for type in
- self.types)
- return "'%s' is not an instance of %s" % (self.matchee, typestr)
-
-
-class LessThan(_BinaryComparison):
- """Matches if the item is less than the matchers reference object."""
-
- comparator = operator.__lt__
- mismatch_string = 'is not >'
-
-
-class GreaterThan(_BinaryComparison):
- """Matches if the item is greater than the matchers reference object."""
-
- comparator = operator.__gt__
- mismatch_string = 'is not <'
-
-
-class MatchesAny(object):
- """Matches if any of the matchers it is created with match."""
-
- def __init__(self, *matchers):
- self.matchers = matchers
-
- def match(self, matchee):
- results = []
- for matcher in self.matchers:
- mismatch = matcher.match(matchee)
- if mismatch is None:
- return None
- results.append(mismatch)
- return MismatchesAll(results)
-
- def __str__(self):
- return "MatchesAny(%s)" % ', '.join([
- str(matcher) for matcher in self.matchers])
-
-
-class MatchesAll(object):
- """Matches if all of the matchers it is created with match."""
-
- def __init__(self, *matchers, **options):
- """Construct a MatchesAll matcher.
-
- Just list the component matchers as arguments in the ``*args``
- style. If you want only the first mismatch to be reported, past in
- first_only=True as a keyword argument. By default, all mismatches are
- reported.
- """
- self.matchers = matchers
- self.first_only = options.get('first_only', False)
-
- def __str__(self):
- return 'MatchesAll(%s)' % ', '.join(map(str, self.matchers))
-
- def match(self, matchee):
- results = []
- for matcher in self.matchers:
- mismatch = matcher.match(matchee)
- if mismatch is not None:
- if self.first_only:
- return mismatch
- results.append(mismatch)
- if results:
- return MismatchesAll(results)
- else:
- return None
-
-
-class MismatchesAll(Mismatch):
- """A mismatch with many child mismatches."""
-
- def __init__(self, mismatches, wrap=True):
- self.mismatches = mismatches
- self._wrap = wrap
-
- def describe(self):
- descriptions = []
- if self._wrap:
- descriptions = ["Differences: ["]
- for mismatch in self.mismatches:
- descriptions.append(mismatch.describe())
- if self._wrap:
- descriptions.append("]")
- return '\n'.join(descriptions)
-
-
-class MatchesAllDict(Matcher):
- """Matches if all of the matchers it is created with match.
-
- A lot like ``MatchesAll``, but takes a dict of Matchers and labels any
- mismatches with the key of the dictionary.
- """
-
- def __init__(self, matchers):
- super(MatchesAllDict, self).__init__()
- self.matchers = matchers
-
- def __str__(self):
- return 'MatchesAllDict({%s})' % (
- ', '.join('%r: %s' % (k, v) for k, v in self.matchers.items()))
-
- def match(self, observed):
- mismatches = {}
- for label in self.matchers:
- mismatches[label] = self.matchers[label].match(observed)
- return _dict_to_mismatch(
- mismatches, result_mismatch=LabelledMismatches)
-
-
-class DictMismatches(Mismatch):
- """A mismatch with a dict of child mismatches."""
-
- def __init__(self, mismatches, details=None):
- super(DictMismatches, self).__init__(None, details=details)
- self.mismatches = mismatches
-
- def describe(self):
- lines = ['{']
- lines.extend(
- [' %r: %s,' % (key, mismatch.describe())
- for (key, mismatch) in sorted(self.mismatches.items())])
- lines.append('}')
- return '\n'.join(lines)
-
-
-def LabelledMismatches(mismatches, details=None):
- """A collection of mismatches, each labelled."""
- return MismatchesAll(
- (PrefixedMismatch(k, v) for (k, v) in sorted(mismatches.items())),
- wrap=False)
-
-
-def _dict_to_mismatch(data, to_mismatch=None,
- result_mismatch=DictMismatches):
- if to_mismatch:
- data = map_values(to_mismatch, data)
- mismatches = filter_values(bool, data)
- if mismatches:
- return result_mismatch(mismatches)
-
-
-class Not(object):
- """Inverts a matcher."""
-
- def __init__(self, matcher):
- self.matcher = matcher
-
- def __str__(self):
- return 'Not(%s)' % (self.matcher,)
-
- def match(self, other):
- mismatch = self.matcher.match(other)
- if mismatch is None:
- return MatchedUnexpectedly(self.matcher, other)
- else:
- return None
-
-
-class MatchedUnexpectedly(Mismatch):
- """A thing matched when it wasn't supposed to."""
-
- def __init__(self, matcher, other):
- self.matcher = matcher
- self.other = other
-
- def describe(self):
- return "%r matches %s" % (self.other, self.matcher)
-
-
-class MatchesException(Matcher):
- """Match an exc_info tuple against an exception instance or type."""
-
- def __init__(self, exception, value_re=None):
- """Create a MatchesException that will match exc_info's for exception.
-
- :param exception: Either an exception instance or type.
- If an instance is given, the type and arguments of the exception
- are checked. If a type is given only the type of the exception is
- checked. If a tuple is given, then as with isinstance, any of the
- types in the tuple matching is sufficient to match.
- :param value_re: If 'exception' is a type, and the matchee exception
- is of the right type, then match against this. If value_re is a
- string, then assume value_re is a regular expression and match
- the str() of the exception against it. Otherwise, assume value_re
- is a matcher, and match the exception against it.
- """
- Matcher.__init__(self)
- self.expected = exception
- if istext(value_re):
- value_re = AfterPreproccessing(str, MatchesRegex(value_re), False)
- self.value_re = value_re
- self._is_instance = type(self.expected) not in classtypes() + (tuple,)
-
- def match(self, other):
- if type(other) != tuple:
- return Mismatch('%r is not an exc_info tuple' % other)
- expected_class = self.expected
- if self._is_instance:
- expected_class = expected_class.__class__
- if not issubclass(other[0], expected_class):
- return Mismatch('%r is not a %r' % (other[0], expected_class))
- if self._is_instance:
- if other[1].args != self.expected.args:
- return Mismatch('%s has different arguments to %s.' % (
- _error_repr(other[1]), _error_repr(self.expected)))
- elif self.value_re is not None:
- return self.value_re.match(other[1])
-
- def __str__(self):
- if self._is_instance:
- return "MatchesException(%s)" % _error_repr(self.expected)
- return "MatchesException(%s)" % repr(self.expected)
-
-
-class Contains(Matcher):
- """Checks whether something is contained in another thing."""
-
- def __init__(self, needle):
- """Create a Contains Matcher.
-
- :param needle: the thing that needs to be contained by matchees.
- """
- self.needle = needle
-
- def __str__(self):
- return "Contains(%r)" % (self.needle,)
-
- def match(self, matchee):
- try:
- if self.needle not in matchee:
- return DoesNotContain(matchee, self.needle)
- except TypeError:
- # e.g. 1 in 2 will raise TypeError
- return DoesNotContain(matchee, self.needle)
- return None
-
-
-def ContainsAll(items):
- """Make a matcher that checks whether a list of things is contained
- in another thing.
-
- The matcher effectively checks that the provided sequence is a subset of
- the matchee.
- """
- return MatchesAll(*map(Contains, items), first_only=False)
-
-
-class StartsWith(Matcher):
- """Checks whether one string starts with another."""
-
- def __init__(self, expected):
- """Create a StartsWith Matcher.
-
- :param expected: the string that matchees should start with.
- """
- self.expected = expected
-
- def __str__(self):
- return "StartsWith(%r)" % (self.expected,)
-
- def match(self, matchee):
- if not matchee.startswith(self.expected):
- return DoesNotStartWith(matchee, self.expected)
- return None
-
-
-class EndsWith(Matcher):
- """Checks whether one string ends with another."""
-
- def __init__(self, expected):
- """Create a EndsWith Matcher.
-
- :param expected: the string that matchees should end with.
- """
- self.expected = expected
-
- def __str__(self):
- return "EndsWith(%r)" % (self.expected,)
-
- def match(self, matchee):
- if not matchee.endswith(self.expected):
- return DoesNotEndWith(matchee, self.expected)
- return None
-
-
-class KeysEqual(Matcher):
- """Checks whether a dict has particular keys."""
-
- def __init__(self, *expected):
- """Create a `KeysEqual` Matcher.
-
- :param expected: The keys the dict is expected to have. If a dict,
- then we use the keys of that dict, if a collection, we assume it
- is a collection of expected keys.
- """
- super(KeysEqual, self).__init__()
- try:
- self.expected = expected.keys()
- except AttributeError:
- self.expected = list(expected)
-
- def __str__(self):
- return "KeysEqual(%s)" % ', '.join(map(repr, self.expected))
-
- def match(self, matchee):
- expected = sorted(self.expected)
- matched = Equals(expected).match(sorted(matchee.keys()))
- if matched:
- return AnnotatedMismatch(
- 'Keys not equal',
- _BinaryMismatch(expected, 'does not match', matchee))
- return None
-
-
-class Annotate(object):
- """Annotates a matcher with a descriptive string.
-
- Mismatches are then described as '<mismatch>: <annotation>'.
- """
-
- def __init__(self, annotation, matcher):
- self.annotation = annotation
- self.matcher = matcher
-
- @classmethod
- def if_message(cls, annotation, matcher):
- """Annotate ``matcher`` only if ``annotation`` is non-empty."""
- if not annotation:
- return matcher
- return cls(annotation, matcher)
-
- def __str__(self):
- return 'Annotate(%r, %s)' % (self.annotation, self.matcher)
-
- def match(self, other):
- mismatch = self.matcher.match(other)
- if mismatch is not None:
- return AnnotatedMismatch(self.annotation, mismatch)
-
-
-class PostfixedMismatch(MismatchDecorator):
- """A mismatch annotated with a descriptive string."""
-
- def __init__(self, annotation, mismatch):
- super(PostfixedMismatch, self).__init__(mismatch)
- self.annotation = annotation
- self.mismatch = mismatch
-
- def describe(self):
- return '%s: %s' % (self.original.describe(), self.annotation)
-
-
-AnnotatedMismatch = PostfixedMismatch
-
-
-class PrefixedMismatch(MismatchDecorator):
-
- def __init__(self, prefix, mismatch):
- super(PrefixedMismatch, self).__init__(mismatch)
- self.prefix = prefix
-
- def describe(self):
- return '%s: %s' % (self.prefix, self.original.describe())
-
-
-class Raises(Matcher):
- """Match if the matchee raises an exception when called.
-
- Exceptions which are not subclasses of Exception propogate out of the
- Raises.match call unless they are explicitly matched.
- """
-
- def __init__(self, exception_matcher=None):
- """Create a Raises matcher.
-
- :param exception_matcher: Optional validator for the exception raised
- by matchee. If supplied the exc_info tuple for the exception raised
- is passed into that matcher. If no exception_matcher is supplied
- then the simple fact of raising an exception is considered enough
- to match on.
- """
- self.exception_matcher = exception_matcher
-
- def match(self, matchee):
- try:
- result = matchee()
- return Mismatch('%r returned %r' % (matchee, result))
- # Catch all exceptions: Raises() should be able to match a
- # KeyboardInterrupt or SystemExit.
- except:
- exc_info = sys.exc_info()
- if self.exception_matcher:
- mismatch = self.exception_matcher.match(exc_info)
- if not mismatch:
- del exc_info
- return
- else:
- mismatch = None
- # The exception did not match, or no explicit matching logic was
- # performed. If the exception is a non-user exception (that is, not
- # a subclass of Exception on Python 2.5+) then propogate it.
- if isbaseexception(exc_info[1]):
- del exc_info
- raise
- return mismatch
-
- def __str__(self):
- return 'Raises()'
-
-
-def raises(exception):
- """Make a matcher that checks that a callable raises an exception.
-
- This is a convenience function, exactly equivalent to::
-
- return Raises(MatchesException(exception))
-
- See `Raises` and `MatchesException` for more information.
- """
- return Raises(MatchesException(exception))
-
-
-class MatchesListwise(object):
- """Matches if each matcher matches the corresponding value.
-
- More easily explained by example than in words:
-
- >>> MatchesListwise([Equals(1)]).match([1])
- >>> MatchesListwise([Equals(1), Equals(2)]).match([1, 2])
- >>> print (MatchesListwise([Equals(1), Equals(2)]).match([2, 1]).describe())
- Differences: [
- 1 != 2
- 2 != 1
- ]
- >>> matcher = MatchesListwise([Equals(1), Equals(2)], first_only=True)
- >>> print (matcher.match([3, 4]).describe())
- 1 != 3
- """
-
- def __init__(self, matchers, first_only=False):
- """Construct a MatchesListwise matcher.
-
- :param matchers: A list of matcher that the matched values must match.
- :param first_only: If True, then only report the first mismatch,
- otherwise report all of them. Defaults to False.
- """
- self.matchers = matchers
- self.first_only = first_only
-
- def match(self, values):
- mismatches = []
- length_mismatch = Annotate(
- "Length mismatch", Equals(len(self.matchers))).match(len(values))
- if length_mismatch:
- mismatches.append(length_mismatch)
- for matcher, value in zip(self.matchers, values):
- mismatch = matcher.match(value)
- if mismatch:
- if self.first_only:
- return mismatch
- mismatches.append(mismatch)
- if mismatches:
- return MismatchesAll(mismatches)
-
-
-class MatchesStructure(object):
- """Matcher that matches an object structurally.
-
- 'Structurally' here means that attributes of the object being matched are
- compared against given matchers.
-
- `fromExample` allows the creation of a matcher from a prototype object and
- then modified versions can be created with `update`.
-
- `byEquality` creates a matcher in much the same way as the constructor,
- except that the matcher for each of the attributes is assumed to be
- `Equals`.
-
- `byMatcher` creates a similar matcher to `byEquality`, but you get to pick
- the matcher, rather than just using `Equals`.
- """
-
- def __init__(self, **kwargs):
- """Construct a `MatchesStructure`.
-
- :param kwargs: A mapping of attributes to matchers.
- """
- self.kws = kwargs
-
- @classmethod
- def byEquality(cls, **kwargs):
- """Matches an object where the attributes equal the keyword values.
-
- Similar to the constructor, except that the matcher is assumed to be
- Equals.
- """
- return cls.byMatcher(Equals, **kwargs)
-
- @classmethod
- def byMatcher(cls, matcher, **kwargs):
- """Matches an object where the attributes match the keyword values.
-
- Similar to the constructor, except that the provided matcher is used
- to match all of the values.
- """
- return cls(**map_values(matcher, kwargs))
-
- @classmethod
- def fromExample(cls, example, *attributes):
- kwargs = {}
- for attr in attributes:
- kwargs[attr] = Equals(getattr(example, attr))
- return cls(**kwargs)
-
- def update(self, **kws):
- new_kws = self.kws.copy()
- for attr, matcher in kws.items():
- if matcher is None:
- new_kws.pop(attr, None)
- else:
- new_kws[attr] = matcher
- return type(self)(**new_kws)
-
- def __str__(self):
- kws = []
- for attr, matcher in sorted(self.kws.items()):
- kws.append("%s=%s" % (attr, matcher))
- return "%s(%s)" % (self.__class__.__name__, ', '.join(kws))
-
- def match(self, value):
- matchers = []
- values = []
- for attr, matcher in sorted(self.kws.items()):
- matchers.append(Annotate(attr, matcher))
- values.append(getattr(value, attr))
- return MatchesListwise(matchers).match(values)
-
-
-class MatchesRegex(object):
- """Matches if the matchee is matched by a regular expression."""
-
- def __init__(self, pattern, flags=0):
- self.pattern = pattern
- self.flags = flags
-
- def __str__(self):
- args = ['%r' % self.pattern]
- flag_arg = []
- # dir() sorts the attributes for us, so we don't need to do it again.
- for flag in dir(re):
- if len(flag) == 1:
- if self.flags & getattr(re, flag):
- flag_arg.append('re.%s' % flag)
- if flag_arg:
- args.append('|'.join(flag_arg))
- return '%s(%s)' % (self.__class__.__name__, ', '.join(args))
-
- def match(self, value):
- if not re.match(self.pattern, value, self.flags):
- pattern = self.pattern
- if not isinstance(pattern, str_is_unicode and str or unicode):
- pattern = pattern.decode("latin1")
- pattern = pattern.encode("unicode_escape").decode("ascii")
- return Mismatch("%r does not match /%s/" % (
- value, pattern.replace("\\\\", "\\")))
-
-
-class MatchesSetwise(object):
- """Matches if all the matchers match elements of the value being matched.
-
- That is, each element in the 'observed' set must match exactly one matcher
- from the set of matchers, with no matchers left over.
-
- The difference compared to `MatchesListwise` is that the order of the
- matchings does not matter.
- """
-
- def __init__(self, *matchers):
- self.matchers = matchers
-
- def match(self, observed):
- remaining_matchers = set(self.matchers)
- not_matched = []
- for value in observed:
- for matcher in remaining_matchers:
- if matcher.match(value) is None:
- remaining_matchers.remove(matcher)
- break
- else:
- not_matched.append(value)
- if not_matched or remaining_matchers:
- remaining_matchers = list(remaining_matchers)
- # There are various cases that all should be reported somewhat
- # differently.
-
- # There are two trivial cases:
- # 1) There are just some matchers left over.
- # 2) There are just some values left over.
-
- # Then there are three more interesting cases:
- # 3) There are the same number of matchers and values left over.
- # 4) There are more matchers left over than values.
- # 5) There are more values left over than matchers.
-
- if len(not_matched) == 0:
- if len(remaining_matchers) > 1:
- msg = "There were %s matchers left over: " % (
- len(remaining_matchers),)
- else:
- msg = "There was 1 matcher left over: "
- msg += ', '.join(map(str, remaining_matchers))
- return Mismatch(msg)
- elif len(remaining_matchers) == 0:
- if len(not_matched) > 1:
- return Mismatch(
- "There were %s values left over: %s" % (
- len(not_matched), not_matched))
- else:
- return Mismatch(
- "There was 1 value left over: %s" % (
- not_matched, ))
- else:
- common_length = min(len(remaining_matchers), len(not_matched))
- if common_length == 0:
- raise AssertionError("common_length can't be 0 here")
- if common_length > 1:
- msg = "There were %s mismatches" % (common_length,)
- else:
- msg = "There was 1 mismatch"
- if len(remaining_matchers) > len(not_matched):
- extra_matchers = remaining_matchers[common_length:]
- msg += " and %s extra matcher" % (len(extra_matchers), )
- if len(extra_matchers) > 1:
- msg += "s"
- msg += ': ' + ', '.join(map(str, extra_matchers))
- elif len(not_matched) > len(remaining_matchers):
- extra_values = not_matched[common_length:]
- msg += " and %s extra value" % (len(extra_values), )
- if len(extra_values) > 1:
- msg += "s"
- msg += ': ' + str(extra_values)
- return Annotate(
- msg, MatchesListwise(remaining_matchers[:common_length])
- ).match(not_matched[:common_length])
-
-
-class AfterPreprocessing(object):
- """Matches if the value matches after passing through a function.
-
- This can be used to aid in creating trivial matchers as functions, for
- example::
-
- def PathHasFileContent(content):
- def _read(path):
- return open(path).read()
- return AfterPreprocessing(_read, Equals(content))
- """
-
- def __init__(self, preprocessor, matcher, annotate=True):
- """Create an AfterPreprocessing matcher.
-
- :param preprocessor: A function called with the matchee before
- matching.
- :param matcher: What to match the preprocessed matchee against.
- :param annotate: Whether or not to annotate the matcher with
- something explaining how we transformed the matchee. Defaults
- to True.
- """
- self.preprocessor = preprocessor
- self.matcher = matcher
- self.annotate = annotate
-
- def _str_preprocessor(self):
- if isinstance(self.preprocessor, types.FunctionType):
- return '<function %s>' % self.preprocessor.__name__
- return str(self.preprocessor)
-
- def __str__(self):
- return "AfterPreprocessing(%s, %s)" % (
- self._str_preprocessor(), self.matcher)
-
- def match(self, value):
- after = self.preprocessor(value)
- if self.annotate:
- matcher = Annotate(
- "after %s on %r" % (self._str_preprocessor(), value),
- self.matcher)
- else:
- matcher = self.matcher
- return matcher.match(after)
-
-# This is the old, deprecated. spelling of the name, kept for backwards
-# compatibility.
-AfterPreproccessing = AfterPreprocessing
-
-
-class AllMatch(object):
- """Matches if all provided values match the given matcher."""
-
- def __init__(self, matcher):
- self.matcher = matcher
-
- def __str__(self):
- return 'AllMatch(%s)' % (self.matcher,)
-
- def match(self, values):
- mismatches = []
- for value in values:
- mismatch = self.matcher.match(value)
- if mismatch:
- mismatches.append(mismatch)
- if mismatches:
- return MismatchesAll(mismatches)
-
-
-def PathExists():
- """Matches if the given path exists.
-
- Use like this::
-
- assertThat('/some/path', PathExists())
- """
- return MatchesPredicate(os.path.exists, "%s does not exist.")
-
-
-def DirExists():
- """Matches if the path exists and is a directory."""
- return MatchesAll(
- PathExists(),
- MatchesPredicate(os.path.isdir, "%s is not a directory."),
- first_only=True)
-
-
-def FileExists():
- """Matches if the given path exists and is a file."""
- return MatchesAll(
- PathExists(),
- MatchesPredicate(os.path.isfile, "%s is not a file."),
- first_only=True)
-
-
-class DirContains(Matcher):
- """Matches if the given directory contains files with the given names.
-
- That is, is the directory listing exactly equal to the given files?
- """
-
- def __init__(self, filenames=None, matcher=None):
- """Construct a ``DirContains`` matcher.
-
- Can be used in a basic mode where the whole directory listing is
- matched against an expected directory listing (by passing
- ``filenames``). Can also be used in a more advanced way where the
- whole directory listing is matched against an arbitrary matcher (by
- passing ``matcher`` instead).
-
- :param filenames: If specified, match the sorted directory listing
- against this list of filenames, sorted.
- :param matcher: If specified, match the sorted directory listing
- against this matcher.
- """
- if filenames == matcher == None:
- raise AssertionError(
- "Must provide one of `filenames` or `matcher`.")
- if None not in (filenames, matcher):
- raise AssertionError(
- "Must provide either `filenames` or `matcher`, not both.")
- if filenames is None:
- self.matcher = matcher
- else:
- self.matcher = Equals(sorted(filenames))
-
- def match(self, path):
- mismatch = DirExists().match(path)
- if mismatch is not None:
- return mismatch
- return self.matcher.match(sorted(os.listdir(path)))
-
-
-class FileContains(Matcher):
- """Matches if the given file has the specified contents."""
-
- def __init__(self, contents=None, matcher=None):
- """Construct a ``FileContains`` matcher.
-
- Can be used in a basic mode where the file contents are compared for
- equality against the expected file contents (by passing ``contents``).
- Can also be used in a more advanced way where the file contents are
- matched against an arbitrary matcher (by passing ``matcher`` instead).
-
- :param contents: If specified, match the contents of the file with
- these contents.
- :param matcher: If specified, match the contents of the file against
- this matcher.
- """
- if contents == matcher == None:
- raise AssertionError(
- "Must provide one of `contents` or `matcher`.")
- if None not in (contents, matcher):
- raise AssertionError(
- "Must provide either `contents` or `matcher`, not both.")
- if matcher is None:
- self.matcher = Equals(contents)
- else:
- self.matcher = matcher
-
- def match(self, path):
- mismatch = PathExists().match(path)
- if mismatch is not None:
- return mismatch
- f = open(path)
- try:
- actual_contents = f.read()
- return self.matcher.match(actual_contents)
- finally:
- f.close()
-
- def __str__(self):
- return "File at path exists and contains %s" % self.contents
-
-
-class TarballContains(Matcher):
- """Matches if the given tarball contains the given paths.
-
- Uses TarFile.getnames() to get the paths out of the tarball.
- """
-
- def __init__(self, paths):
- super(TarballContains, self).__init__()
- self.paths = paths
- self.path_matcher = Equals(sorted(self.paths))
-
- def match(self, tarball_path):
- # Open underlying file first to ensure it's always closed:
- # <http://bugs.python.org/issue10233>
- f = open(tarball_path, "rb")
- try:
- tarball = tarfile.open(tarball_path, fileobj=f)
- try:
- return self.path_matcher.match(sorted(tarball.getnames()))
- finally:
- tarball.close()
- finally:
- f.close()
-
-
-class SamePath(Matcher):
- """Matches if two paths are the same.
-
- That is, the paths are equal, or they point to the same file but in
- different ways. The paths do not have to exist.
- """
-
- def __init__(self, path):
- super(SamePath, self).__init__()
- self.path = path
-
- def match(self, other_path):
- f = lambda x: os.path.abspath(os.path.realpath(x))
- return Equals(f(self.path)).match(f(other_path))
-
-
-class SameMembers(Matcher):
- """Matches if two iterators have the same members.
-
- This is not the same as set equivalence. The two iterators must be of the
- same length and have the same repetitions.
- """
-
- def __init__(self, expected):
- super(SameMembers, self).__init__()
- self.expected = expected
-
- def __str__(self):
- return '%s(%r)' % (self.__class__.__name__, self.expected)
-
- def match(self, observed):
- expected_only = list_subtract(self.expected, observed)
- observed_only = list_subtract(observed, self.expected)
- if expected_only == observed_only == []:
- return
- return PostfixedMismatch(
- "\nmissing: %s\nextra: %s" % (
- _format(expected_only), _format(observed_only)),
- _BinaryMismatch(self.expected, 'elements differ', observed))
-
-
-class HasPermissions(Matcher):
- """Matches if a file has the given permissions.
-
- Permissions are specified and matched as a four-digit octal string.
- """
-
- def __init__(self, octal_permissions):
- """Construct a HasPermissions matcher.
-
- :param octal_permissions: A four digit octal string, representing the
- intended access permissions. e.g. '0775' for rwxrwxr-x.
- """
- super(HasPermissions, self).__init__()
- self.octal_permissions = octal_permissions
-
- def match(self, filename):
- permissions = oct(os.stat(filename).st_mode)[-4:]
- return Equals(self.octal_permissions).match(permissions)
-
-
-class _MatchCommonKeys(Matcher):
- """Match on keys in a dictionary.
-
- Given a dictionary where the values are matchers, this will look for
- common keys in the matched dictionary and match if and only if all common
- keys match the given matchers.
-
- Thus::
-
- >>> structure = {'a': Equals('x'), 'b': Equals('y')}
- >>> _MatchCommonKeys(structure).match({'a': 'x', 'c': 'z'})
- None
- """
-
- def __init__(self, dict_of_matchers):
- super(_MatchCommonKeys, self).__init__()
- self._matchers = dict_of_matchers
-
- def _compare_dicts(self, expected, observed):
- common_keys = set(expected.keys()) & set(observed.keys())
- mismatches = {}
- for key in common_keys:
- mismatch = expected[key].match(observed[key])
- if mismatch:
- mismatches[key] = mismatch
- return mismatches
-
- def match(self, observed):
- mismatches = self._compare_dicts(self._matchers, observed)
- if mismatches:
- return DictMismatches(mismatches)
-
-
-class _SubDictOf(Matcher):
- """Matches if the matched dict only has keys that are in given dict."""
-
- def __init__(self, super_dict, format_value=repr):
- super(_SubDictOf, self).__init__()
- self.super_dict = super_dict
- self.format_value = format_value
-
- def match(self, observed):
- excess = dict_subtract(observed, self.super_dict)
- return _dict_to_mismatch(
- excess, lambda v: Mismatch(self.format_value(v)))
-
-
-class _SuperDictOf(Matcher):
- """Matches if all of the keys in the given dict are in the matched dict.
- """
-
- def __init__(self, sub_dict, format_value=repr):
- super(_SuperDictOf, self).__init__()
- self.sub_dict = sub_dict
- self.format_value = format_value
-
- def match(self, super_dict):
- return _SubDictOf(super_dict, self.format_value).match(self.sub_dict)
-
-
-def _format_matcher_dict(matchers):
- return '{%s}' % (
- ', '.join('%r: %s' % (k, v) for k, v in matchers.items()))
-
-
-class _CombinedMatcher(Matcher):
- """Many matchers labelled and combined into one uber-matcher.
-
- Subclass this and then specify a dict of matcher factories that take a
- single 'expected' value and return a matcher. The subclass will match
- only if all of the matchers made from factories match.
-
- Not **entirely** dissimilar from ``MatchesAll``.
- """
-
- matcher_factories = {}
-
- def __init__(self, expected):
- super(_CombinedMatcher, self).__init__()
- self._expected = expected
-
- def format_expected(self, expected):
- return repr(expected)
-
- def __str__(self):
- return '%s(%s)' % (
- self.__class__.__name__, self.format_expected(self._expected))
-
- def match(self, observed):
- matchers = dict(
- (k, v(self._expected)) for k, v in self.matcher_factories.items())
- return MatchesAllDict(matchers).match(observed)
-
-
-class MatchesDict(_CombinedMatcher):
- """Match a dictionary exactly, by its keys.
-
- Specify a dictionary mapping keys (often strings) to matchers. This is
- the 'expected' dict. Any dictionary that matches this must have exactly
- the same keys, and the values must match the corresponding matchers in the
- expected dict.
- """
-
- matcher_factories = {
- 'Extra': _SubDictOf,
- 'Missing': lambda m: _SuperDictOf(m, format_value=str),
- 'Differences': _MatchCommonKeys,
- }
-
- format_expected = lambda self, expected: _format_matcher_dict(expected)
-
-
-class ContainsDict(_CombinedMatcher):
- """Match a dictionary for that contains a specified sub-dictionary.
-
- Specify a dictionary mapping keys (often strings) to matchers. This is
- the 'expected' dict. Any dictionary that matches this must have **at
- least** these keys, and the values must match the corresponding matchers
- in the expected dict. Dictionaries that have more keys will also match.
-
- In other words, any matching dictionary must contain the dictionary given
- to the constructor.
-
- Does not check for strict sub-dictionary. That is, equal dictionaries
- match.
- """
-
- matcher_factories = {
- 'Missing': lambda m: _SuperDictOf(m, format_value=str),
- 'Differences': _MatchCommonKeys,
- }
-
- format_expected = lambda self, expected: _format_matcher_dict(expected)
-
-
-class ContainedByDict(_CombinedMatcher):
- """Match a dictionary for which this is a super-dictionary.
-
- Specify a dictionary mapping keys (often strings) to matchers. This is
- the 'expected' dict. Any dictionary that matches this must have **only**
- these keys, and the values must match the corresponding matchers in the
- expected dict. Dictionaries that have fewer keys can also match.
-
- In other words, any matching dictionary must be contained by the
- dictionary given to the constructor.
-
- Does not check for strict super-dictionary. That is, equal dictionaries
- match.
- """
-
- matcher_factories = {
- 'Extra': _SubDictOf,
- 'Differences': _MatchCommonKeys,
- }
-
- format_expected = lambda self, expected: _format_matcher_dict(expected)
-
-
# Signal that this is part of the testing framework, and that code from this
# should not normally appear in tracebacks.
__unittest = True
=== added file 'testtools/matchers/_datastructures.py'
--- testtools/matchers/_datastructures.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/_datastructures.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,228 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+__all__ = [
+ 'ContainsAll',
+ 'MatchesListwise',
+ 'MatchesSetwise',
+ 'MatchesStructure',
+ ]
+
+"""Matchers that operate with knowledge of Python data structures."""
+
+from ..helpers import map_values
+from ._core import Mismatch
+from ._higherorder import (
+ Annotate,
+ MatchesAll,
+ MismatchesAll,
+ )
+
+
+def ContainsAll(items):
+ """Make a matcher that checks whether a list of things is contained
+ in another thing.
+
+ The matcher effectively checks that the provided sequence is a subset of
+ the matchee.
+ """
+ from ._basic import Contains
+ return MatchesAll(*map(Contains, items), first_only=False)
+
+
+class MatchesListwise(object):
+ """Matches if each matcher matches the corresponding value.
+
+ More easily explained by example than in words:
+
+ >>> from ._basic import Equals
+ >>> MatchesListwise([Equals(1)]).match([1])
+ >>> MatchesListwise([Equals(1), Equals(2)]).match([1, 2])
+ >>> print (MatchesListwise([Equals(1), Equals(2)]).match([2, 1]).describe())
+ Differences: [
+ 1 != 2
+ 2 != 1
+ ]
+ >>> matcher = MatchesListwise([Equals(1), Equals(2)], first_only=True)
+ >>> print (matcher.match([3, 4]).describe())
+ 1 != 3
+ """
+
+ def __init__(self, matchers, first_only=False):
+ """Construct a MatchesListwise matcher.
+
+ :param matchers: A list of matcher that the matched values must match.
+ :param first_only: If True, then only report the first mismatch,
+ otherwise report all of them. Defaults to False.
+ """
+ self.matchers = matchers
+ self.first_only = first_only
+
+ def match(self, values):
+ from ._basic import Equals
+ mismatches = []
+ length_mismatch = Annotate(
+ "Length mismatch", Equals(len(self.matchers))).match(len(values))
+ if length_mismatch:
+ mismatches.append(length_mismatch)
+ for matcher, value in zip(self.matchers, values):
+ mismatch = matcher.match(value)
+ if mismatch:
+ if self.first_only:
+ return mismatch
+ mismatches.append(mismatch)
+ if mismatches:
+ return MismatchesAll(mismatches)
+
+
+class MatchesStructure(object):
+ """Matcher that matches an object structurally.
+
+ 'Structurally' here means that attributes of the object being matched are
+ compared against given matchers.
+
+ `fromExample` allows the creation of a matcher from a prototype object and
+ then modified versions can be created with `update`.
+
+ `byEquality` creates a matcher in much the same way as the constructor,
+ except that the matcher for each of the attributes is assumed to be
+ `Equals`.
+
+ `byMatcher` creates a similar matcher to `byEquality`, but you get to pick
+ the matcher, rather than just using `Equals`.
+ """
+
+ def __init__(self, **kwargs):
+ """Construct a `MatchesStructure`.
+
+ :param kwargs: A mapping of attributes to matchers.
+ """
+ self.kws = kwargs
+
+ @classmethod
+ def byEquality(cls, **kwargs):
+ """Matches an object where the attributes equal the keyword values.
+
+ Similar to the constructor, except that the matcher is assumed to be
+ Equals.
+ """
+ from ._basic import Equals
+ return cls.byMatcher(Equals, **kwargs)
+
+ @classmethod
+ def byMatcher(cls, matcher, **kwargs):
+ """Matches an object where the attributes match the keyword values.
+
+ Similar to the constructor, except that the provided matcher is used
+ to match all of the values.
+ """
+ return cls(**map_values(matcher, kwargs))
+
+ @classmethod
+ def fromExample(cls, example, *attributes):
+ from ._basic import Equals
+ kwargs = {}
+ for attr in attributes:
+ kwargs[attr] = Equals(getattr(example, attr))
+ return cls(**kwargs)
+
+ def update(self, **kws):
+ new_kws = self.kws.copy()
+ for attr, matcher in kws.items():
+ if matcher is None:
+ new_kws.pop(attr, None)
+ else:
+ new_kws[attr] = matcher
+ return type(self)(**new_kws)
+
+ def __str__(self):
+ kws = []
+ for attr, matcher in sorted(self.kws.items()):
+ kws.append("%s=%s" % (attr, matcher))
+ return "%s(%s)" % (self.__class__.__name__, ', '.join(kws))
+
+ def match(self, value):
+ matchers = []
+ values = []
+ for attr, matcher in sorted(self.kws.items()):
+ matchers.append(Annotate(attr, matcher))
+ values.append(getattr(value, attr))
+ return MatchesListwise(matchers).match(values)
+
+
+class MatchesSetwise(object):
+ """Matches if all the matchers match elements of the value being matched.
+
+ That is, each element in the 'observed' set must match exactly one matcher
+ from the set of matchers, with no matchers left over.
+
+ The difference compared to `MatchesListwise` is that the order of the
+ matchings does not matter.
+ """
+
+ def __init__(self, *matchers):
+ self.matchers = matchers
+
+ def match(self, observed):
+ remaining_matchers = set(self.matchers)
+ not_matched = []
+ for value in observed:
+ for matcher in remaining_matchers:
+ if matcher.match(value) is None:
+ remaining_matchers.remove(matcher)
+ break
+ else:
+ not_matched.append(value)
+ if not_matched or remaining_matchers:
+ remaining_matchers = list(remaining_matchers)
+ # There are various cases that all should be reported somewhat
+ # differently.
+
+ # There are two trivial cases:
+ # 1) There are just some matchers left over.
+ # 2) There are just some values left over.
+
+ # Then there are three more interesting cases:
+ # 3) There are the same number of matchers and values left over.
+ # 4) There are more matchers left over than values.
+ # 5) There are more values left over than matchers.
+
+ if len(not_matched) == 0:
+ if len(remaining_matchers) > 1:
+ msg = "There were %s matchers left over: " % (
+ len(remaining_matchers),)
+ else:
+ msg = "There was 1 matcher left over: "
+ msg += ', '.join(map(str, remaining_matchers))
+ return Mismatch(msg)
+ elif len(remaining_matchers) == 0:
+ if len(not_matched) > 1:
+ return Mismatch(
+ "There were %s values left over: %s" % (
+ len(not_matched), not_matched))
+ else:
+ return Mismatch(
+ "There was 1 value left over: %s" % (
+ not_matched, ))
+ else:
+ common_length = min(len(remaining_matchers), len(not_matched))
+ if common_length == 0:
+ raise AssertionError("common_length can't be 0 here")
+ if common_length > 1:
+ msg = "There were %s mismatches" % (common_length,)
+ else:
+ msg = "There was 1 mismatch"
+ if len(remaining_matchers) > len(not_matched):
+ extra_matchers = remaining_matchers[common_length:]
+ msg += " and %s extra matcher" % (len(extra_matchers), )
+ if len(extra_matchers) > 1:
+ msg += "s"
+ msg += ': ' + ', '.join(map(str, extra_matchers))
+ elif len(not_matched) > len(remaining_matchers):
+ extra_values = not_matched[common_length:]
+ msg += " and %s extra value" % (len(extra_values), )
+ if len(extra_values) > 1:
+ msg += "s"
+ msg += ': ' + str(extra_values)
+ return Annotate(
+ msg, MatchesListwise(remaining_matchers[:common_length])
+ ).match(not_matched[:common_length])
=== added file 'testtools/matchers/_dict.py'
--- testtools/matchers/_dict.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/_dict.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,260 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+__all__ = [
+ 'KeysEqual',
+ ]
+
+from ..helpers import (
+ dict_subtract,
+ filter_values,
+ map_values,
+ )
+from ._core import Matcher, Mismatch
+from ._higherorder import (
+ AnnotatedMismatch,
+ PrefixedMismatch,
+ MismatchesAll,
+ )
+
+
+def LabelledMismatches(mismatches, details=None):
+ """A collection of mismatches, each labelled."""
+ return MismatchesAll(
+ (PrefixedMismatch(k, v) for (k, v) in sorted(mismatches.items())),
+ wrap=False)
+
+
+class MatchesAllDict(Matcher):
+ """Matches if all of the matchers it is created with match.
+
+ A lot like ``MatchesAll``, but takes a dict of Matchers and labels any
+ mismatches with the key of the dictionary.
+ """
+
+ def __init__(self, matchers):
+ super(MatchesAllDict, self).__init__()
+ self.matchers = matchers
+
+ def __str__(self):
+ return 'MatchesAllDict({%s})' % (
+ ', '.join('%r: %s' % (k, v) for k, v in self.matchers.items()))
+
+ def match(self, observed):
+ mismatches = {}
+ for label in self.matchers:
+ mismatches[label] = self.matchers[label].match(observed)
+ return _dict_to_mismatch(
+ mismatches, result_mismatch=LabelledMismatches)
+
+
+class DictMismatches(Mismatch):
+ """A mismatch with a dict of child mismatches."""
+
+ def __init__(self, mismatches, details=None):
+ super(DictMismatches, self).__init__(None, details=details)
+ self.mismatches = mismatches
+
+ def describe(self):
+ lines = ['{']
+ lines.extend(
+ [' %r: %s,' % (key, mismatch.describe())
+ for (key, mismatch) in sorted(self.mismatches.items())])
+ lines.append('}')
+ return '\n'.join(lines)
+
+
+def _dict_to_mismatch(data, to_mismatch=None,
+ result_mismatch=DictMismatches):
+ if to_mismatch:
+ data = map_values(to_mismatch, data)
+ mismatches = filter_values(bool, data)
+ if mismatches:
+ return result_mismatch(mismatches)
+
+
+class _MatchCommonKeys(Matcher):
+ """Match on keys in a dictionary.
+
+ Given a dictionary where the values are matchers, this will look for
+ common keys in the matched dictionary and match if and only if all common
+ keys match the given matchers.
+
+ Thus::
+
+ >>> structure = {'a': Equals('x'), 'b': Equals('y')}
+ >>> _MatchCommonKeys(structure).match({'a': 'x', 'c': 'z'})
+ None
+ """
+
+ def __init__(self, dict_of_matchers):
+ super(_MatchCommonKeys, self).__init__()
+ self._matchers = dict_of_matchers
+
+ def _compare_dicts(self, expected, observed):
+ common_keys = set(expected.keys()) & set(observed.keys())
+ mismatches = {}
+ for key in common_keys:
+ mismatch = expected[key].match(observed[key])
+ if mismatch:
+ mismatches[key] = mismatch
+ return mismatches
+
+ def match(self, observed):
+ mismatches = self._compare_dicts(self._matchers, observed)
+ if mismatches:
+ return DictMismatches(mismatches)
+
+
+class _SubDictOf(Matcher):
+ """Matches if the matched dict only has keys that are in given dict."""
+
+ def __init__(self, super_dict, format_value=repr):
+ super(_SubDictOf, self).__init__()
+ self.super_dict = super_dict
+ self.format_value = format_value
+
+ def match(self, observed):
+ excess = dict_subtract(observed, self.super_dict)
+ return _dict_to_mismatch(
+ excess, lambda v: Mismatch(self.format_value(v)))
+
+
+class _SuperDictOf(Matcher):
+ """Matches if all of the keys in the given dict are in the matched dict.
+ """
+
+ def __init__(self, sub_dict, format_value=repr):
+ super(_SuperDictOf, self).__init__()
+ self.sub_dict = sub_dict
+ self.format_value = format_value
+
+ def match(self, super_dict):
+ return _SubDictOf(super_dict, self.format_value).match(self.sub_dict)
+
+
+def _format_matcher_dict(matchers):
+ return '{%s}' % (
+ ', '.join('%r: %s' % (k, v) for k, v in matchers.items()))
+
+
+class _CombinedMatcher(Matcher):
+ """Many matchers labelled and combined into one uber-matcher.
+
+ Subclass this and then specify a dict of matcher factories that take a
+ single 'expected' value and return a matcher. The subclass will match
+ only if all of the matchers made from factories match.
+
+ Not **entirely** dissimilar from ``MatchesAll``.
+ """
+
+ matcher_factories = {}
+
+ def __init__(self, expected):
+ super(_CombinedMatcher, self).__init__()
+ self._expected = expected
+
+ def format_expected(self, expected):
+ return repr(expected)
+
+ def __str__(self):
+ return '%s(%s)' % (
+ self.__class__.__name__, self.format_expected(self._expected))
+
+ def match(self, observed):
+ matchers = dict(
+ (k, v(self._expected)) for k, v in self.matcher_factories.items())
+ return MatchesAllDict(matchers).match(observed)
+
+
+class MatchesDict(_CombinedMatcher):
+ """Match a dictionary exactly, by its keys.
+
+ Specify a dictionary mapping keys (often strings) to matchers. This is
+ the 'expected' dict. Any dictionary that matches this must have exactly
+ the same keys, and the values must match the corresponding matchers in the
+ expected dict.
+ """
+
+ matcher_factories = {
+ 'Extra': _SubDictOf,
+ 'Missing': lambda m: _SuperDictOf(m, format_value=str),
+ 'Differences': _MatchCommonKeys,
+ }
+
+ format_expected = lambda self, expected: _format_matcher_dict(expected)
+
+
+class ContainsDict(_CombinedMatcher):
+ """Match a dictionary for that contains a specified sub-dictionary.
+
+ Specify a dictionary mapping keys (often strings) to matchers. This is
+ the 'expected' dict. Any dictionary that matches this must have **at
+ least** these keys, and the values must match the corresponding matchers
+ in the expected dict. Dictionaries that have more keys will also match.
+
+ In other words, any matching dictionary must contain the dictionary given
+ to the constructor.
+
+ Does not check for strict sub-dictionary. That is, equal dictionaries
+ match.
+ """
+
+ matcher_factories = {
+ 'Missing': lambda m: _SuperDictOf(m, format_value=str),
+ 'Differences': _MatchCommonKeys,
+ }
+
+ format_expected = lambda self, expected: _format_matcher_dict(expected)
+
+
+class ContainedByDict(_CombinedMatcher):
+ """Match a dictionary for which this is a super-dictionary.
+
+ Specify a dictionary mapping keys (often strings) to matchers. This is
+ the 'expected' dict. Any dictionary that matches this must have **only**
+ these keys, and the values must match the corresponding matchers in the
+ expected dict. Dictionaries that have fewer keys can also match.
+
+ In other words, any matching dictionary must be contained by the
+ dictionary given to the constructor.
+
+ Does not check for strict super-dictionary. That is, equal dictionaries
+ match.
+ """
+
+ matcher_factories = {
+ 'Extra': _SubDictOf,
+ 'Differences': _MatchCommonKeys,
+ }
+
+ format_expected = lambda self, expected: _format_matcher_dict(expected)
+
+
+class KeysEqual(Matcher):
+ """Checks whether a dict has particular keys."""
+
+ def __init__(self, *expected):
+ """Create a `KeysEqual` Matcher.
+
+ :param expected: The keys the dict is expected to have. If a dict,
+ then we use the keys of that dict, if a collection, we assume it
+ is a collection of expected keys.
+ """
+ super(KeysEqual, self).__init__()
+ try:
+ self.expected = expected.keys()
+ except AttributeError:
+ self.expected = list(expected)
+
+ def __str__(self):
+ return "KeysEqual(%s)" % ', '.join(map(repr, self.expected))
+
+ def match(self, matchee):
+ from ._basic import _BinaryMismatch, Equals
+ expected = sorted(self.expected)
+ matched = Equals(expected).match(sorted(matchee.keys()))
+ if matched:
+ return AnnotatedMismatch(
+ 'Keys not equal',
+ _BinaryMismatch(expected, 'does not match', matchee))
+ return None
=== added file 'testtools/matchers/_doctest.py'
--- testtools/matchers/_doctest.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/_doctest.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,104 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+__all__ = [
+ 'DocTestMatches',
+ ]
+
+import doctest
+import re
+
+from ..compat import str_is_unicode
+from ._core import Mismatch
+
+
+class _NonManglingOutputChecker(doctest.OutputChecker):
+ """Doctest checker that works with unicode rather than mangling strings
+
+ This is needed because current Python versions have tried to fix string
+ encoding related problems, but regressed the default behaviour with
+ unicode inputs in the process.
+
+ In Python 2.6 and 2.7 ``OutputChecker.output_difference`` is was changed
+ to return a bytestring encoded as per ``sys.stdout.encoding``, or utf-8 if
+ that can't be determined. Worse, that encoding process happens in the
+ innocent looking `_indent` global function. Because the
+ `DocTestMismatch.describe` result may well not be destined for printing to
+ stdout, this is no good for us. To get a unicode return as before, the
+ method is monkey patched if ``doctest._encoding`` exists.
+
+ Python 3 has a different problem. For some reason both inputs are encoded
+ to ascii with 'backslashreplace', making an escaped string matches its
+ unescaped form. Overriding the offending ``OutputChecker._toAscii`` method
+ is sufficient to revert this.
+ """
+
+ def _toAscii(self, s):
+ """Return ``s`` unchanged rather than mangling it to ascii"""
+ return s
+
+ # Only do this overriding hackery if doctest has a broken _input function
+ if getattr(doctest, "_encoding", None) is not None:
+ from types import FunctionType as __F
+ __f = doctest.OutputChecker.output_difference.im_func
+ __g = dict(__f.func_globals)
+ def _indent(s, indent=4, _pattern=re.compile("^(?!$)", re.MULTILINE)):
+ """Prepend non-empty lines in ``s`` with ``indent`` number of spaces"""
+ return _pattern.sub(indent*" ", s)
+ __g["_indent"] = _indent
+ output_difference = __F(__f.func_code, __g, "output_difference")
+ del __F, __f, __g, _indent
+
+
+class DocTestMatches(object):
+ """See if a string matches a doctest example."""
+
+ def __init__(self, example, flags=0):
+ """Create a DocTestMatches to match example.
+
+ :param example: The example to match e.g. 'foo bar baz'
+ :param flags: doctest comparison flags to match on. e.g.
+ doctest.ELLIPSIS.
+ """
+ if not example.endswith('\n'):
+ example += '\n'
+ self.want = example # required variable name by doctest.
+ self.flags = flags
+ self._checker = _NonManglingOutputChecker()
+
+ def __str__(self):
+ if self.flags:
+ flagstr = ", flags=%d" % self.flags
+ else:
+ flagstr = ""
+ return 'DocTestMatches(%r%s)' % (self.want, flagstr)
+
+ def _with_nl(self, actual):
+ result = self.want.__class__(actual)
+ if not result.endswith('\n'):
+ result += '\n'
+ return result
+
+ def match(self, actual):
+ with_nl = self._with_nl(actual)
+ if self._checker.check_output(self.want, with_nl, self.flags):
+ return None
+ return DocTestMismatch(self, with_nl)
+
+ def _describe_difference(self, with_nl):
+ return self._checker.output_difference(self, with_nl, self.flags)
+
+
+class DocTestMismatch(Mismatch):
+ """Mismatch object for DocTestMatches."""
+
+ def __init__(self, matcher, with_nl):
+ self.matcher = matcher
+ self.with_nl = with_nl
+
+ def describe(self):
+ s = self.matcher._describe_difference(self.with_nl)
+ if str_is_unicode or isinstance(s, unicode):
+ return s
+ # GZ 2011-08-24: This is actually pretty bogus, most C0 codes should
+ # be escaped, in addition to non-ascii bytes.
+ return s.decode("latin1").encode("ascii", "backslashreplace")
=== added file 'testtools/matchers/_exception.py'
--- testtools/matchers/_exception.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/_exception.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,124 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+__all__ = [
+ 'MatchesException',
+ 'Raises',
+ 'raises',
+ ]
+
+import sys
+
+from testtools.compat import (
+ classtypes,
+ _error_repr,
+ isbaseexception,
+ istext,
+ )
+from ._basic import MatchesRegex
+from ._core import (
+ Matcher,
+ Mismatch,
+ )
+from ._higherorder import AfterPreproccessing
+
+
+class MatchesException(Matcher):
+ """Match an exc_info tuple against an exception instance or type."""
+
+ def __init__(self, exception, value_re=None):
+ """Create a MatchesException that will match exc_info's for exception.
+
+ :param exception: Either an exception instance or type.
+ If an instance is given, the type and arguments of the exception
+ are checked. If a type is given only the type of the exception is
+ checked. If a tuple is given, then as with isinstance, any of the
+ types in the tuple matching is sufficient to match.
+ :param value_re: If 'exception' is a type, and the matchee exception
+ is of the right type, then match against this. If value_re is a
+ string, then assume value_re is a regular expression and match
+ the str() of the exception against it. Otherwise, assume value_re
+ is a matcher, and match the exception against it.
+ """
+ Matcher.__init__(self)
+ self.expected = exception
+ if istext(value_re):
+ value_re = AfterPreproccessing(str, MatchesRegex(value_re), False)
+ self.value_re = value_re
+ self._is_instance = type(self.expected) not in classtypes() + (tuple,)
+
+ def match(self, other):
+ if type(other) != tuple:
+ return Mismatch('%r is not an exc_info tuple' % other)
+ expected_class = self.expected
+ if self._is_instance:
+ expected_class = expected_class.__class__
+ if not issubclass(other[0], expected_class):
+ return Mismatch('%r is not a %r' % (other[0], expected_class))
+ if self._is_instance:
+ if other[1].args != self.expected.args:
+ return Mismatch('%s has different arguments to %s.' % (
+ _error_repr(other[1]), _error_repr(self.expected)))
+ elif self.value_re is not None:
+ return self.value_re.match(other[1])
+
+ def __str__(self):
+ if self._is_instance:
+ return "MatchesException(%s)" % _error_repr(self.expected)
+ return "MatchesException(%s)" % repr(self.expected)
+
+
+class Raises(Matcher):
+ """Match if the matchee raises an exception when called.
+
+ Exceptions which are not subclasses of Exception propogate out of the
+ Raises.match call unless they are explicitly matched.
+ """
+
+ def __init__(self, exception_matcher=None):
+ """Create a Raises matcher.
+
+ :param exception_matcher: Optional validator for the exception raised
+ by matchee. If supplied the exc_info tuple for the exception raised
+ is passed into that matcher. If no exception_matcher is supplied
+ then the simple fact of raising an exception is considered enough
+ to match on.
+ """
+ self.exception_matcher = exception_matcher
+
+ def match(self, matchee):
+ try:
+ result = matchee()
+ return Mismatch('%r returned %r' % (matchee, result))
+ # Catch all exceptions: Raises() should be able to match a
+ # KeyboardInterrupt or SystemExit.
+ except:
+ exc_info = sys.exc_info()
+ if self.exception_matcher:
+ mismatch = self.exception_matcher.match(exc_info)
+ if not mismatch:
+ del exc_info
+ return
+ else:
+ mismatch = None
+ # The exception did not match, or no explicit matching logic was
+ # performed. If the exception is a non-user exception (that is, not
+ # a subclass of Exception on Python 2.5+) then propogate it.
+ if isbaseexception(exc_info[1]):
+ del exc_info
+ raise
+ return mismatch
+
+ def __str__(self):
+ return 'Raises()'
+
+
+def raises(exception):
+ """Make a matcher that checks that a callable raises an exception.
+
+ This is a convenience function, exactly equivalent to::
+
+ return Raises(MatchesException(exception))
+
+ See `Raises` and `MatchesException` for more information.
+ """
+ return Raises(MatchesException(exception))
=== added file 'testtools/matchers/_filesystem.py'
--- testtools/matchers/_filesystem.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/_filesystem.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,192 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+"""Matchers for things related to the filesystem."""
+
+__all__ = [
+ 'FileContains',
+ 'DirExists',
+ 'FileExists',
+ 'HasPermissions',
+ 'PathExists',
+ 'SamePath',
+ 'TarballContains',
+ ]
+
+import os
+import tarfile
+
+from ._basic import Equals
+from ._core import (
+ Matcher,
+ )
+from ._higherorder import (
+ MatchesAll,
+ MatchesPredicate,
+ )
+
+
+def PathExists():
+ """Matches if the given path exists.
+
+ Use like this::
+
+ assertThat('/some/path', PathExists())
+ """
+ return MatchesPredicate(os.path.exists, "%s does not exist.")
+
+
+def DirExists():
+ """Matches if the path exists and is a directory."""
+ return MatchesAll(
+ PathExists(),
+ MatchesPredicate(os.path.isdir, "%s is not a directory."),
+ first_only=True)
+
+
+def FileExists():
+ """Matches if the given path exists and is a file."""
+ return MatchesAll(
+ PathExists(),
+ MatchesPredicate(os.path.isfile, "%s is not a file."),
+ first_only=True)
+
+
+class DirContains(Matcher):
+ """Matches if the given directory contains files with the given names.
+
+ That is, is the directory listing exactly equal to the given files?
+ """
+
+ def __init__(self, filenames=None, matcher=None):
+ """Construct a ``DirContains`` matcher.
+
+ Can be used in a basic mode where the whole directory listing is
+ matched against an expected directory listing (by passing
+ ``filenames``). Can also be used in a more advanced way where the
+ whole directory listing is matched against an arbitrary matcher (by
+ passing ``matcher`` instead).
+
+ :param filenames: If specified, match the sorted directory listing
+ against this list of filenames, sorted.
+ :param matcher: If specified, match the sorted directory listing
+ against this matcher.
+ """
+ if filenames == matcher == None:
+ raise AssertionError(
+ "Must provide one of `filenames` or `matcher`.")
+ if None not in (filenames, matcher):
+ raise AssertionError(
+ "Must provide either `filenames` or `matcher`, not both.")
+ if filenames is None:
+ self.matcher = matcher
+ else:
+ self.matcher = Equals(sorted(filenames))
+
+ def match(self, path):
+ mismatch = DirExists().match(path)
+ if mismatch is not None:
+ return mismatch
+ return self.matcher.match(sorted(os.listdir(path)))
+
+
+class FileContains(Matcher):
+ """Matches if the given file has the specified contents."""
+
+ def __init__(self, contents=None, matcher=None):
+ """Construct a ``FileContains`` matcher.
+
+ Can be used in a basic mode where the file contents are compared for
+ equality against the expected file contents (by passing ``contents``).
+ Can also be used in a more advanced way where the file contents are
+ matched against an arbitrary matcher (by passing ``matcher`` instead).
+
+ :param contents: If specified, match the contents of the file with
+ these contents.
+ :param matcher: If specified, match the contents of the file against
+ this matcher.
+ """
+ if contents == matcher == None:
+ raise AssertionError(
+ "Must provide one of `contents` or `matcher`.")
+ if None not in (contents, matcher):
+ raise AssertionError(
+ "Must provide either `contents` or `matcher`, not both.")
+ if matcher is None:
+ self.matcher = Equals(contents)
+ else:
+ self.matcher = matcher
+
+ def match(self, path):
+ mismatch = PathExists().match(path)
+ if mismatch is not None:
+ return mismatch
+ f = open(path)
+ try:
+ actual_contents = f.read()
+ return self.matcher.match(actual_contents)
+ finally:
+ f.close()
+
+ def __str__(self):
+ return "File at path exists and contains %s" % self.contents
+
+
+class HasPermissions(Matcher):
+ """Matches if a file has the given permissions.
+
+ Permissions are specified and matched as a four-digit octal string.
+ """
+
+ def __init__(self, octal_permissions):
+ """Construct a HasPermissions matcher.
+
+ :param octal_permissions: A four digit octal string, representing the
+ intended access permissions. e.g. '0775' for rwxrwxr-x.
+ """
+ super(HasPermissions, self).__init__()
+ self.octal_permissions = octal_permissions
+
+ def match(self, filename):
+ permissions = oct(os.stat(filename).st_mode)[-4:]
+ return Equals(self.octal_permissions).match(permissions)
+
+
+class SamePath(Matcher):
+ """Matches if two paths are the same.
+
+ That is, the paths are equal, or they point to the same file but in
+ different ways. The paths do not have to exist.
+ """
+
+ def __init__(self, path):
+ super(SamePath, self).__init__()
+ self.path = path
+
+ def match(self, other_path):
+ f = lambda x: os.path.abspath(os.path.realpath(x))
+ return Equals(f(self.path)).match(f(other_path))
+
+
+class TarballContains(Matcher):
+ """Matches if the given tarball contains the given paths.
+
+ Uses TarFile.getnames() to get the paths out of the tarball.
+ """
+
+ def __init__(self, paths):
+ super(TarballContains, self).__init__()
+ self.paths = paths
+ self.path_matcher = Equals(sorted(self.paths))
+
+ def match(self, tarball_path):
+ # Open underlying file first to ensure it's always closed:
+ # <http://bugs.python.org/issue10233>
+ f = open(tarball_path, "rb")
+ try:
+ tarball = tarfile.open(tarball_path, fileobj=f)
+ try:
+ return self.path_matcher.match(sorted(tarball.getnames()))
+ finally:
+ tarball.close()
+ finally:
+ f.close()
=== added file 'testtools/matchers/_higherorder.py'
--- testtools/matchers/_higherorder.py 1970-01-01 00:00:00 +0000
+++ testtools/matchers/_higherorder.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,269 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+__all__ = [
+ 'AfterPreprocessing',
+ 'AllMatch',
+ 'Annotate',
+ 'MatchesAny',
+ 'MatchesAll',
+ 'Not',
+ ]
+
+import types
+
+from ._core import (
+ Matcher,
+ Mismatch,
+ MismatchDecorator,
+ )
+
+
+class MatchesAny(object):
+ """Matches if any of the matchers it is created with match."""
+
+ def __init__(self, *matchers):
+ self.matchers = matchers
+
+ def match(self, matchee):
+ results = []
+ for matcher in self.matchers:
+ mismatch = matcher.match(matchee)
+ if mismatch is None:
+ return None
+ results.append(mismatch)
+ return MismatchesAll(results)
+
+ def __str__(self):
+ return "MatchesAny(%s)" % ', '.join([
+ str(matcher) for matcher in self.matchers])
+
+
+class MatchesAll(object):
+ """Matches if all of the matchers it is created with match."""
+
+ def __init__(self, *matchers, **options):
+ """Construct a MatchesAll matcher.
+
+ Just list the component matchers as arguments in the ``*args``
+ style. If you want only the first mismatch to be reported, past in
+ first_only=True as a keyword argument. By default, all mismatches are
+ reported.
+ """
+ self.matchers = matchers
+ self.first_only = options.get('first_only', False)
+
+ def __str__(self):
+ return 'MatchesAll(%s)' % ', '.join(map(str, self.matchers))
+
+ def match(self, matchee):
+ results = []
+ for matcher in self.matchers:
+ mismatch = matcher.match(matchee)
+ if mismatch is not None:
+ if self.first_only:
+ return mismatch
+ results.append(mismatch)
+ if results:
+ return MismatchesAll(results)
+ else:
+ return None
+
+
+class MismatchesAll(Mismatch):
+ """A mismatch with many child mismatches."""
+
+ def __init__(self, mismatches, wrap=True):
+ self.mismatches = mismatches
+ self._wrap = wrap
+
+ def describe(self):
+ descriptions = []
+ if self._wrap:
+ descriptions = ["Differences: ["]
+ for mismatch in self.mismatches:
+ descriptions.append(mismatch.describe())
+ if self._wrap:
+ descriptions.append("]")
+ return '\n'.join(descriptions)
+
+
+class Not(object):
+ """Inverts a matcher."""
+
+ def __init__(self, matcher):
+ self.matcher = matcher
+
+ def __str__(self):
+ return 'Not(%s)' % (self.matcher,)
+
+ def match(self, other):
+ mismatch = self.matcher.match(other)
+ if mismatch is None:
+ return MatchedUnexpectedly(self.matcher, other)
+ else:
+ return None
+
+
+class MatchedUnexpectedly(Mismatch):
+ """A thing matched when it wasn't supposed to."""
+
+ def __init__(self, matcher, other):
+ self.matcher = matcher
+ self.other = other
+
+ def describe(self):
+ return "%r matches %s" % (self.other, self.matcher)
+
+
+class Annotate(object):
+ """Annotates a matcher with a descriptive string.
+
+ Mismatches are then described as '<mismatch>: <annotation>'.
+ """
+
+ def __init__(self, annotation, matcher):
+ self.annotation = annotation
+ self.matcher = matcher
+
+ @classmethod
+ def if_message(cls, annotation, matcher):
+ """Annotate ``matcher`` only if ``annotation`` is non-empty."""
+ if not annotation:
+ return matcher
+ return cls(annotation, matcher)
+
+ def __str__(self):
+ return 'Annotate(%r, %s)' % (self.annotation, self.matcher)
+
+ def match(self, other):
+ mismatch = self.matcher.match(other)
+ if mismatch is not None:
+ return AnnotatedMismatch(self.annotation, mismatch)
+
+
+class PostfixedMismatch(MismatchDecorator):
+ """A mismatch annotated with a descriptive string."""
+
+ def __init__(self, annotation, mismatch):
+ super(PostfixedMismatch, self).__init__(mismatch)
+ self.annotation = annotation
+ self.mismatch = mismatch
+
+ def describe(self):
+ return '%s: %s' % (self.original.describe(), self.annotation)
+
+
+AnnotatedMismatch = PostfixedMismatch
+
+
+class PrefixedMismatch(MismatchDecorator):
+
+ def __init__(self, prefix, mismatch):
+ super(PrefixedMismatch, self).__init__(mismatch)
+ self.prefix = prefix
+
+ def describe(self):
+ return '%s: %s' % (self.prefix, self.original.describe())
+
+
+class AfterPreprocessing(object):
+ """Matches if the value matches after passing through a function.
+
+ This can be used to aid in creating trivial matchers as functions, for
+ example::
+
+ def PathHasFileContent(content):
+ def _read(path):
+ return open(path).read()
+ return AfterPreprocessing(_read, Equals(content))
+ """
+
+ def __init__(self, preprocessor, matcher, annotate=True):
+ """Create an AfterPreprocessing matcher.
+
+ :param preprocessor: A function called with the matchee before
+ matching.
+ :param matcher: What to match the preprocessed matchee against.
+ :param annotate: Whether or not to annotate the matcher with
+ something explaining how we transformed the matchee. Defaults
+ to True.
+ """
+ self.preprocessor = preprocessor
+ self.matcher = matcher
+ self.annotate = annotate
+
+ def _str_preprocessor(self):
+ if isinstance(self.preprocessor, types.FunctionType):
+ return '<function %s>' % self.preprocessor.__name__
+ return str(self.preprocessor)
+
+ def __str__(self):
+ return "AfterPreprocessing(%s, %s)" % (
+ self._str_preprocessor(), self.matcher)
+
+ def match(self, value):
+ after = self.preprocessor(value)
+ if self.annotate:
+ matcher = Annotate(
+ "after %s on %r" % (self._str_preprocessor(), value),
+ self.matcher)
+ else:
+ matcher = self.matcher
+ return matcher.match(after)
+
+
+# This is the old, deprecated. spelling of the name, kept for backwards
+# compatibility.
+AfterPreproccessing = AfterPreprocessing
+
+
+class AllMatch(object):
+ """Matches if all provided values match the given matcher."""
+
+ def __init__(self, matcher):
+ self.matcher = matcher
+
+ def __str__(self):
+ return 'AllMatch(%s)' % (self.matcher,)
+
+ def match(self, values):
+ mismatches = []
+ for value in values:
+ mismatch = self.matcher.match(value)
+ if mismatch:
+ mismatches.append(mismatch)
+ if mismatches:
+ return MismatchesAll(mismatches)
+
+
+class MatchesPredicate(Matcher):
+ """Match if a given function returns True.
+
+ It is reasonably common to want to make a very simple matcher based on a
+ function that you already have that returns True or False given a single
+ argument (i.e. a predicate function). This matcher makes it very easy to
+ do so. e.g.::
+
+ IsEven = MatchesPredicate(lambda x: x % 2 == 0, '%s is not even')
+ self.assertThat(4, IsEven)
+ """
+
+ def __init__(self, predicate, message):
+ """Create a ``MatchesPredicate`` matcher.
+
+ :param predicate: A function that takes a single argument and returns
+ a value that will be interpreted as a boolean.
+ :param message: A message to describe a mismatch. It will be formatted
+ with '%' and be given whatever was passed to ``match()``. Thus, it
+ needs to contain exactly one thing like '%s', '%d' or '%f'.
+ """
+ self.predicate = predicate
+ self.message = message
+
+ def __str__(self):
+ return '%s(%r, %r)' % (
+ self.__class__.__name__, self.predicate, self.message)
+
+ def match(self, x):
+ if not self.predicate(x):
+ return Mismatch(self.message % x)
=== modified file 'testtools/tests/__init__.py'
--- testtools/tests/__init__.py 2012-04-11 11:00:52 +0000
+++ testtools/tests/__init__.py 2012-09-08 17:30:27 +0000
@@ -7,6 +7,7 @@
def test_suite():
from testtools.tests import (
+ matchers,
test_compat,
test_content,
test_content_type,
@@ -14,7 +15,6 @@
test_distutilscmd,
test_fixturesupport,
test_helpers,
- test_matchers,
test_monkey,
test_run,
test_runtest,
@@ -25,6 +25,7 @@
test_testsuite,
)
modules = [
+ matchers,
test_compat,
test_content,
test_content_type,
@@ -32,7 +33,6 @@
test_distutilscmd,
test_fixturesupport,
test_helpers,
- test_matchers,
test_monkey,
test_run,
test_runtest,
=== added directory 'testtools/tests/matchers'
=== added file 'testtools/tests/matchers/__init__.py'
--- testtools/tests/matchers/__init__.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/__init__.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,29 @@
+# Copyright (c) 2009-2012 testtools developers. See LICENSE for details.
+
+
+from unittest import TestSuite
+
+
+def test_suite():
+ from testtools.tests.matchers import (
+ test_basic,
+ test_core,
+ test_datastructures,
+ test_dict,
+ test_doctest,
+ test_exception,
+ test_filesystem,
+ test_higherorder,
+ )
+ modules = [
+ test_basic,
+ test_core,
+ test_datastructures,
+ test_dict,
+ test_doctest,
+ test_exception,
+ test_filesystem,
+ test_higherorder,
+ ]
+ suites = map(lambda x: x.test_suite(), modules)
+ return TestSuite(suites)
=== added file 'testtools/tests/matchers/helpers.py'
--- testtools/tests/matchers/helpers.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/helpers.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,42 @@
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
+
+from testtools.tests.helpers import FullStackRunTest
+
+
+class TestMatchersInterface(object):
+
+ run_tests_with = FullStackRunTest
+
+ def test_matches_match(self):
+ matcher = self.matches_matcher
+ matches = self.matches_matches
+ mismatches = self.matches_mismatches
+ for candidate in matches:
+ self.assertEqual(None, matcher.match(candidate))
+ for candidate in mismatches:
+ mismatch = matcher.match(candidate)
+ self.assertNotEqual(None, mismatch)
+ self.assertNotEqual(None, getattr(mismatch, 'describe', None))
+
+ def test__str__(self):
+ # [(expected, object to __str__)].
+ from testtools.matchers._doctest import DocTestMatches
+ examples = self.str_examples
+ for expected, matcher in examples:
+ self.assertThat(matcher, DocTestMatches(expected))
+
+ def test_describe_difference(self):
+ # [(expected, matchee, matcher), ...]
+ examples = self.describe_examples
+ for difference, matchee, matcher in examples:
+ mismatch = matcher.match(matchee)
+ self.assertEqual(difference, mismatch.describe())
+
+ def test_mismatch_details(self):
+ # The mismatch object must provide get_details, which must return a
+ # dictionary mapping names to Content objects.
+ examples = self.describe_examples
+ for difference, matchee, matcher in examples:
+ mismatch = matcher.match(matchee)
+ details = mismatch.get_details()
+ self.assertEqual(dict(details), details)
=== added file 'testtools/tests/matchers/test_basic.py'
--- testtools/tests/matchers/test_basic.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/test_basic.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,374 @@
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
+
+import re
+
+from testtools import TestCase
+from testtools.compat import (
+ text_repr,
+ _b,
+ _u,
+ )
+from testtools.matchers._basic import (
+ _BinaryMismatch,
+ Contains,
+ DoesNotEndWith,
+ DoesNotStartWith,
+ EndsWith,
+ Equals,
+ Is,
+ IsInstance,
+ LessThan,
+ GreaterThan,
+ MatchesRegex,
+ NotEquals,
+ SameMembers,
+ StartsWith,
+ )
+from testtools.tests.helpers import FullStackRunTest
+from testtools.tests.matchers.helpers import TestMatchersInterface
+
+
+class Test_BinaryMismatch(TestCase):
+ """Mismatches from binary comparisons need useful describe output"""
+
+ _long_string = "This is a longish multiline non-ascii string\n\xa7"
+ _long_b = _b(_long_string)
+ _long_u = _u(_long_string)
+
+ def test_short_objects(self):
+ o1, o2 = object(), object()
+ mismatch = _BinaryMismatch(o1, "!~", o2)
+ self.assertEqual(mismatch.describe(), "%r !~ %r" % (o1, o2))
+
+ def test_short_mixed_strings(self):
+ b, u = _b("\xa7"), _u("\xa7")
+ mismatch = _BinaryMismatch(b, "!~", u)
+ self.assertEqual(mismatch.describe(), "%r !~ %r" % (b, u))
+
+ def test_long_bytes(self):
+ one_line_b = self._long_b.replace(_b("\n"), _b(" "))
+ mismatch = _BinaryMismatch(one_line_b, "!~", self._long_b)
+ self.assertEqual(mismatch.describe(),
+ "%s:\nreference = %s\nactual = %s\n" % ("!~",
+ text_repr(one_line_b),
+ text_repr(self._long_b, multiline=True)))
+
+ def test_long_unicode(self):
+ one_line_u = self._long_u.replace("\n", " ")
+ mismatch = _BinaryMismatch(one_line_u, "!~", self._long_u)
+ self.assertEqual(mismatch.describe(),
+ "%s:\nreference = %s\nactual = %s\n" % ("!~",
+ text_repr(one_line_u),
+ text_repr(self._long_u, multiline=True)))
+
+ def test_long_mixed_strings(self):
+ mismatch = _BinaryMismatch(self._long_b, "!~", self._long_u)
+ self.assertEqual(mismatch.describe(),
+ "%s:\nreference = %s\nactual = %s\n" % ("!~",
+ text_repr(self._long_b, multiline=True),
+ text_repr(self._long_u, multiline=True)))
+
+ def test_long_bytes_and_object(self):
+ obj = object()
+ mismatch = _BinaryMismatch(self._long_b, "!~", obj)
+ self.assertEqual(mismatch.describe(),
+ "%s:\nreference = %s\nactual = %s\n" % ("!~",
+ text_repr(self._long_b, multiline=True),
+ repr(obj)))
+
+ def test_long_unicode_and_object(self):
+ obj = object()
+ mismatch = _BinaryMismatch(self._long_u, "!~", obj)
+ self.assertEqual(mismatch.describe(),
+ "%s:\nreference = %s\nactual = %s\n" % ("!~",
+ text_repr(self._long_u, multiline=True),
+ repr(obj)))
+
+
+class TestEqualsInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = Equals(1)
+ matches_matches = [1]
+ matches_mismatches = [2]
+
+ str_examples = [("Equals(1)", Equals(1)), ("Equals('1')", Equals('1'))]
+
+ describe_examples = [("1 != 2", 2, Equals(1))]
+
+
+class TestNotEqualsInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = NotEquals(1)
+ matches_matches = [2]
+ matches_mismatches = [1]
+
+ str_examples = [
+ ("NotEquals(1)", NotEquals(1)), ("NotEquals('1')", NotEquals('1'))]
+
+ describe_examples = [("1 == 1", 1, NotEquals(1))]
+
+
+class TestIsInterface(TestCase, TestMatchersInterface):
+
+ foo = object()
+ bar = object()
+
+ matches_matcher = Is(foo)
+ matches_matches = [foo]
+ matches_mismatches = [bar, 1]
+
+ str_examples = [("Is(2)", Is(2))]
+
+ describe_examples = [("1 is not 2", 2, Is(1))]
+
+
+class TestIsInstanceInterface(TestCase, TestMatchersInterface):
+
+ class Foo:pass
+
+ matches_matcher = IsInstance(Foo)
+ matches_matches = [Foo()]
+ matches_mismatches = [object(), 1, Foo]
+
+ str_examples = [
+ ("IsInstance(str)", IsInstance(str)),
+ ("IsInstance(str, int)", IsInstance(str, int)),
+ ]
+
+ describe_examples = [
+ ("'foo' is not an instance of int", 'foo', IsInstance(int)),
+ ("'foo' is not an instance of any of (int, type)", 'foo',
+ IsInstance(int, type)),
+ ]
+
+
+class TestLessThanInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = LessThan(4)
+ matches_matches = [-5, 3]
+ matches_mismatches = [4, 5, 5000]
+
+ str_examples = [
+ ("LessThan(12)", LessThan(12)),
+ ]
+
+ describe_examples = [
+ ('4 is not > 5', 5, LessThan(4)),
+ ('4 is not > 4', 4, LessThan(4)),
+ ]
+
+
+class TestGreaterThanInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = GreaterThan(4)
+ matches_matches = [5, 8]
+ matches_mismatches = [-2, 0, 4]
+
+ str_examples = [
+ ("GreaterThan(12)", GreaterThan(12)),
+ ]
+
+ describe_examples = [
+ ('5 is not < 4', 4, GreaterThan(5)),
+ ('4 is not < 4', 4, GreaterThan(4)),
+ ]
+
+
+class TestContainsInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = Contains('foo')
+ matches_matches = ['foo', 'afoo', 'fooa']
+ matches_mismatches = ['f', 'fo', 'oo', 'faoo', 'foao']
+
+ str_examples = [
+ ("Contains(1)", Contains(1)),
+ ("Contains('foo')", Contains('foo')),
+ ]
+
+ describe_examples = [("1 not in 2", 2, Contains(1))]
+
+
+class DoesNotStartWithTests(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test_describe(self):
+ mismatch = DoesNotStartWith("fo", "bo")
+ self.assertEqual("'fo' does not start with 'bo'.", mismatch.describe())
+
+ def test_describe_non_ascii_unicode(self):
+ string = _u("A\xA7")
+ suffix = _u("B\xA7")
+ mismatch = DoesNotStartWith(string, suffix)
+ self.assertEqual("%s does not start with %s." % (
+ text_repr(string), text_repr(suffix)),
+ mismatch.describe())
+
+ def test_describe_non_ascii_bytes(self):
+ string = _b("A\xA7")
+ suffix = _b("B\xA7")
+ mismatch = DoesNotStartWith(string, suffix)
+ self.assertEqual("%r does not start with %r." % (string, suffix),
+ mismatch.describe())
+
+
+class StartsWithTests(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test_str(self):
+ matcher = StartsWith("bar")
+ self.assertEqual("StartsWith('bar')", str(matcher))
+
+ def test_str_with_bytes(self):
+ b = _b("\xA7")
+ matcher = StartsWith(b)
+ self.assertEqual("StartsWith(%r)" % (b,), str(matcher))
+
+ def test_str_with_unicode(self):
+ u = _u("\xA7")
+ matcher = StartsWith(u)
+ self.assertEqual("StartsWith(%r)" % (u,), str(matcher))
+
+ def test_match(self):
+ matcher = StartsWith("bar")
+ self.assertIs(None, matcher.match("barf"))
+
+ def test_mismatch_returns_does_not_start_with(self):
+ matcher = StartsWith("bar")
+ self.assertIsInstance(matcher.match("foo"), DoesNotStartWith)
+
+ def test_mismatch_sets_matchee(self):
+ matcher = StartsWith("bar")
+ mismatch = matcher.match("foo")
+ self.assertEqual("foo", mismatch.matchee)
+
+ def test_mismatch_sets_expected(self):
+ matcher = StartsWith("bar")
+ mismatch = matcher.match("foo")
+ self.assertEqual("bar", mismatch.expected)
+
+
+class DoesNotEndWithTests(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test_describe(self):
+ mismatch = DoesNotEndWith("fo", "bo")
+ self.assertEqual("'fo' does not end with 'bo'.", mismatch.describe())
+
+ def test_describe_non_ascii_unicode(self):
+ string = _u("A\xA7")
+ suffix = _u("B\xA7")
+ mismatch = DoesNotEndWith(string, suffix)
+ self.assertEqual("%s does not end with %s." % (
+ text_repr(string), text_repr(suffix)),
+ mismatch.describe())
+
+ def test_describe_non_ascii_bytes(self):
+ string = _b("A\xA7")
+ suffix = _b("B\xA7")
+ mismatch = DoesNotEndWith(string, suffix)
+ self.assertEqual("%r does not end with %r." % (string, suffix),
+ mismatch.describe())
+
+
+class EndsWithTests(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test_str(self):
+ matcher = EndsWith("bar")
+ self.assertEqual("EndsWith('bar')", str(matcher))
+
+ def test_str_with_bytes(self):
+ b = _b("\xA7")
+ matcher = EndsWith(b)
+ self.assertEqual("EndsWith(%r)" % (b,), str(matcher))
+
+ def test_str_with_unicode(self):
+ u = _u("\xA7")
+ matcher = EndsWith(u)
+ self.assertEqual("EndsWith(%r)" % (u,), str(matcher))
+
+ def test_match(self):
+ matcher = EndsWith("arf")
+ self.assertIs(None, matcher.match("barf"))
+
+ def test_mismatch_returns_does_not_end_with(self):
+ matcher = EndsWith("bar")
+ self.assertIsInstance(matcher.match("foo"), DoesNotEndWith)
+
+ def test_mismatch_sets_matchee(self):
+ matcher = EndsWith("bar")
+ mismatch = matcher.match("foo")
+ self.assertEqual("foo", mismatch.matchee)
+
+ def test_mismatch_sets_expected(self):
+ matcher = EndsWith("bar")
+ mismatch = matcher.match("foo")
+ self.assertEqual("bar", mismatch.expected)
+
+
+class TestSameMembers(TestCase, TestMatchersInterface):
+
+ matches_matcher = SameMembers([1, 1, 2, 3, {'foo': 'bar'}])
+ matches_matches = [
+ [1, 1, 2, 3, {'foo': 'bar'}],
+ [3, {'foo': 'bar'}, 1, 2, 1],
+ [3, 2, 1, {'foo': 'bar'}, 1],
+ (2, {'foo': 'bar'}, 3, 1, 1),
+ ]
+ matches_mismatches = [
+ set([1, 2, 3]),
+ [1, 1, 2, 3, 5],
+ [1, 2, 3, {'foo': 'bar'}],
+ 'foo',
+ ]
+
+ describe_examples = [
+ (("elements differ:\n"
+ "reference = ['apple', 'orange', 'canteloupe', 'watermelon', 'lemon', 'banana']\n"
+ "actual = ['orange', 'apple', 'banana', 'sparrow', 'lemon', 'canteloupe']\n"
+ ": \n"
+ "missing: ['watermelon']\n"
+ "extra: ['sparrow']"
+ ),
+ ['orange', 'apple', 'banana', 'sparrow', 'lemon', 'canteloupe',],
+ SameMembers(
+ ['apple', 'orange', 'canteloupe', 'watermelon',
+ 'lemon', 'banana',])),
+ ]
+
+ str_examples = [
+ ('SameMembers([1, 2, 3])', SameMembers([1, 2, 3])),
+ ]
+
+
+class TestMatchesRegex(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesRegex('a|b')
+ matches_matches = ['a', 'b']
+ matches_mismatches = ['c']
+
+ str_examples = [
+ ("MatchesRegex('a|b')", MatchesRegex('a|b')),
+ ("MatchesRegex('a|b', re.M)", MatchesRegex('a|b', re.M)),
+ ("MatchesRegex('a|b', re.I|re.M)", MatchesRegex('a|b', re.I|re.M)),
+ ("MatchesRegex(%r)" % (_b("\xA7"),), MatchesRegex(_b("\xA7"))),
+ ("MatchesRegex(%r)" % (_u("\xA7"),), MatchesRegex(_u("\xA7"))),
+ ]
+
+ describe_examples = [
+ ("'c' does not match /a|b/", 'c', MatchesRegex('a|b')),
+ ("'c' does not match /a\d/", 'c', MatchesRegex(r'a\d')),
+ ("%r does not match /\\s+\\xa7/" % (_b('c'),),
+ _b('c'), MatchesRegex(_b("\\s+\xA7"))),
+ ("%r does not match /\\s+\\xa7/" % (_u('c'),),
+ _u('c'), MatchesRegex(_u("\\s+\xA7"))),
+ ]
+
+
+def test_suite():
+ from unittest import TestLoader
+ return TestLoader().loadTestsFromName(__name__)
=== renamed file 'testtools/tests/test_matchers.py' => 'testtools/tests/matchers/test_core.py'
--- testtools/tests/test_matchers.py 2012-08-10 14:05:57 +0000
+++ testtools/tests/matchers/test_core.py 2012-09-08 17:30:27 +0000
@@ -1,74 +1,25 @@
-# Copyright (c) 2008-2011 testtools developers. See LICENSE for details.
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
"""Tests for matchers."""
-import doctest
-import re
-import os
-import shutil
-import sys
-import tarfile
-import tempfile
-
from testtools import (
Matcher, # check that Matcher is exposed at the top level for docs.
TestCase,
)
from testtools.compat import (
- StringIO,
str_is_unicode,
text_repr,
- _b,
_u,
)
from testtools.matchers import (
- AfterPreprocessing,
- AllMatch,
- Annotate,
- AnnotatedMismatch,
- _BinaryMismatch,
- Contains,
- ContainsAll,
- ContainedByDict,
- ContainsDict,
- DirContains,
- DirExists,
- DocTestMatches,
- DoesNotEndWith,
- DoesNotStartWith,
- EndsWith,
Equals,
- FileContains,
- FileExists,
- HasPermissions,
- KeysEqual,
- Is,
- IsInstance,
- LessThan,
- GreaterThan,
- MatchesAny,
- MatchesAll,
- MatchesAllDict,
- MatchesDict,
MatchesException,
- MatchesListwise,
- MatchesPredicate,
- MatchesRegex,
- MatchesSetwise,
- MatchesStructure,
+ Raises,
+ )
+from testtools.matchers._core import (
Mismatch,
MismatchDecorator,
MismatchError,
- Not,
- NotEquals,
- PathExists,
- Raises,
- raises,
- SameMembers,
- SamePath,
- StartsWith,
- _SubDictOf,
- TarballContains,
)
from testtools.tests.helpers import FullStackRunTest
@@ -154,925 +105,6 @@
self.assertEqual(expected, actual)
-class Test_BinaryMismatch(TestCase):
- """Mismatches from binary comparisons need useful describe output"""
-
- _long_string = "This is a longish multiline non-ascii string\n\xa7"
- _long_b = _b(_long_string)
- _long_u = _u(_long_string)
-
- def test_short_objects(self):
- o1, o2 = object(), object()
- mismatch = _BinaryMismatch(o1, "!~", o2)
- self.assertEqual(mismatch.describe(), "%r !~ %r" % (o1, o2))
-
- def test_short_mixed_strings(self):
- b, u = _b("\xa7"), _u("\xa7")
- mismatch = _BinaryMismatch(b, "!~", u)
- self.assertEqual(mismatch.describe(), "%r !~ %r" % (b, u))
-
- def test_long_bytes(self):
- one_line_b = self._long_b.replace(_b("\n"), _b(" "))
- mismatch = _BinaryMismatch(one_line_b, "!~", self._long_b)
- self.assertEqual(mismatch.describe(),
- "%s:\nreference = %s\nactual = %s\n" % ("!~",
- text_repr(one_line_b),
- text_repr(self._long_b, multiline=True)))
-
- def test_long_unicode(self):
- one_line_u = self._long_u.replace("\n", " ")
- mismatch = _BinaryMismatch(one_line_u, "!~", self._long_u)
- self.assertEqual(mismatch.describe(),
- "%s:\nreference = %s\nactual = %s\n" % ("!~",
- text_repr(one_line_u),
- text_repr(self._long_u, multiline=True)))
-
- def test_long_mixed_strings(self):
- mismatch = _BinaryMismatch(self._long_b, "!~", self._long_u)
- self.assertEqual(mismatch.describe(),
- "%s:\nreference = %s\nactual = %s\n" % ("!~",
- text_repr(self._long_b, multiline=True),
- text_repr(self._long_u, multiline=True)))
-
- def test_long_bytes_and_object(self):
- obj = object()
- mismatch = _BinaryMismatch(self._long_b, "!~", obj)
- self.assertEqual(mismatch.describe(),
- "%s:\nreference = %s\nactual = %s\n" % ("!~",
- text_repr(self._long_b, multiline=True),
- repr(obj)))
-
- def test_long_unicode_and_object(self):
- obj = object()
- mismatch = _BinaryMismatch(self._long_u, "!~", obj)
- self.assertEqual(mismatch.describe(),
- "%s:\nreference = %s\nactual = %s\n" % ("!~",
- text_repr(self._long_u, multiline=True),
- repr(obj)))
-
-
-class TestMatchersInterface(object):
-
- run_tests_with = FullStackRunTest
-
- def test_matches_match(self):
- matcher = self.matches_matcher
- matches = self.matches_matches
- mismatches = self.matches_mismatches
- for candidate in matches:
- self.assertEqual(None, matcher.match(candidate))
- for candidate in mismatches:
- mismatch = matcher.match(candidate)
- self.assertNotEqual(None, mismatch)
- self.assertNotEqual(None, getattr(mismatch, 'describe', None))
-
- def test__str__(self):
- # [(expected, object to __str__)].
- examples = self.str_examples
- for expected, matcher in examples:
- self.assertThat(matcher, DocTestMatches(expected))
-
- def test_describe_difference(self):
- # [(expected, matchee, matcher), ...]
- examples = self.describe_examples
- for difference, matchee, matcher in examples:
- mismatch = matcher.match(matchee)
- self.assertEqual(difference, mismatch.describe())
-
- def test_mismatch_details(self):
- # The mismatch object must provide get_details, which must return a
- # dictionary mapping names to Content objects.
- examples = self.describe_examples
- for difference, matchee, matcher in examples:
- mismatch = matcher.match(matchee)
- details = mismatch.get_details()
- self.assertEqual(dict(details), details)
-
-
-class TestDocTestMatchesInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = DocTestMatches("Ran 1 test in ...s", doctest.ELLIPSIS)
- matches_matches = ["Ran 1 test in 0.000s", "Ran 1 test in 1.234s"]
- matches_mismatches = ["Ran 1 tests in 0.000s", "Ran 2 test in 0.000s"]
-
- str_examples = [("DocTestMatches('Ran 1 test in ...s\\n')",
- DocTestMatches("Ran 1 test in ...s")),
- ("DocTestMatches('foo\\n', flags=8)", DocTestMatches("foo", flags=8)),
- ]
-
- describe_examples = [('Expected:\n Ran 1 tests in ...s\nGot:\n'
- ' Ran 1 test in 0.123s\n', "Ran 1 test in 0.123s",
- DocTestMatches("Ran 1 tests in ...s", doctest.ELLIPSIS))]
-
-
-class TestDocTestMatchesInterfaceUnicode(TestCase, TestMatchersInterface):
-
- matches_matcher = DocTestMatches(_u("\xa7..."), doctest.ELLIPSIS)
- matches_matches = [_u("\xa7"), _u("\xa7 more\n")]
- matches_mismatches = ["\\xa7", _u("more \xa7"), _u("\n\xa7")]
-
- str_examples = [("DocTestMatches(%r)" % (_u("\xa7\n"),),
- DocTestMatches(_u("\xa7"))),
- ]
-
- describe_examples = [(
- _u("Expected:\n \xa7\nGot:\n a\n"),
- "a",
- DocTestMatches(_u("\xa7"), doctest.ELLIPSIS))]
-
-
-class TestDocTestMatchesSpecific(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test___init__simple(self):
- matcher = DocTestMatches("foo")
- self.assertEqual("foo\n", matcher.want)
-
- def test___init__flags(self):
- matcher = DocTestMatches("bar\n", doctest.ELLIPSIS)
- self.assertEqual("bar\n", matcher.want)
- self.assertEqual(doctest.ELLIPSIS, matcher.flags)
-
- def test_describe_non_ascii_bytes(self):
- """Even with bytestrings, the mismatch should be coercible to unicode
-
- DocTestMatches is intended for text, but the Python 2 str type also
- permits arbitrary binary inputs. This is a slightly bogus thing to do,
- and under Python 3 using bytes objects will reasonably raise an error.
- """
- header = _b("\x89PNG\r\n\x1a\n...")
- if str_is_unicode:
- self.assertRaises(TypeError,
- DocTestMatches, header, doctest.ELLIPSIS)
- return
- matcher = DocTestMatches(header, doctest.ELLIPSIS)
- mismatch = matcher.match(_b("GIF89a\1\0\1\0\0\0\0;"))
- # Must be treatable as unicode text, the exact output matters less
- self.assertTrue(unicode(mismatch.describe()))
-
-
-class TestEqualsInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = Equals(1)
- matches_matches = [1]
- matches_mismatches = [2]
-
- str_examples = [("Equals(1)", Equals(1)), ("Equals('1')", Equals('1'))]
-
- describe_examples = [("1 != 2", 2, Equals(1))]
-
-
-class TestNotEqualsInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = NotEquals(1)
- matches_matches = [2]
- matches_mismatches = [1]
-
- str_examples = [
- ("NotEquals(1)", NotEquals(1)), ("NotEquals('1')", NotEquals('1'))]
-
- describe_examples = [("1 == 1", 1, NotEquals(1))]
-
-
-class TestIsInterface(TestCase, TestMatchersInterface):
-
- foo = object()
- bar = object()
-
- matches_matcher = Is(foo)
- matches_matches = [foo]
- matches_mismatches = [bar, 1]
-
- str_examples = [("Is(2)", Is(2))]
-
- describe_examples = [("1 is not 2", 2, Is(1))]
-
-
-class TestIsInstanceInterface(TestCase, TestMatchersInterface):
-
- class Foo:pass
-
- matches_matcher = IsInstance(Foo)
- matches_matches = [Foo()]
- matches_mismatches = [object(), 1, Foo]
-
- str_examples = [
- ("IsInstance(str)", IsInstance(str)),
- ("IsInstance(str, int)", IsInstance(str, int)),
- ]
-
- describe_examples = [
- ("'foo' is not an instance of int", 'foo', IsInstance(int)),
- ("'foo' is not an instance of any of (int, type)", 'foo',
- IsInstance(int, type)),
- ]
-
-
-class TestLessThanInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = LessThan(4)
- matches_matches = [-5, 3]
- matches_mismatches = [4, 5, 5000]
-
- str_examples = [
- ("LessThan(12)", LessThan(12)),
- ]
-
- describe_examples = [
- ('4 is not > 5', 5, LessThan(4)),
- ('4 is not > 4', 4, LessThan(4)),
- ]
-
-
-class TestGreaterThanInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = GreaterThan(4)
- matches_matches = [5, 8]
- matches_mismatches = [-2, 0, 4]
-
- str_examples = [
- ("GreaterThan(12)", GreaterThan(12)),
- ]
-
- describe_examples = [
- ('5 is not < 4', 4, GreaterThan(5)),
- ('4 is not < 4', 4, GreaterThan(4)),
- ]
-
-
-class TestContainsInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = Contains('foo')
- matches_matches = ['foo', 'afoo', 'fooa']
- matches_mismatches = ['f', 'fo', 'oo', 'faoo', 'foao']
-
- str_examples = [
- ("Contains(1)", Contains(1)),
- ("Contains('foo')", Contains('foo')),
- ]
-
- describe_examples = [("1 not in 2", 2, Contains(1))]
-
-
-class TestContainsAllInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = ContainsAll(['foo', 'bar'])
- matches_matches = [['foo', 'bar'], ['foo', 'z', 'bar'], ['bar', 'foo']]
- matches_mismatches = [['f', 'g'], ['foo', 'baz'], []]
-
- str_examples = [(
- "MatchesAll(Contains('foo'), Contains('bar'))",
- ContainsAll(['foo', 'bar'])),
- ]
-
- describe_examples = [("""Differences: [
-'baz' not in 'foo'
-]""",
- 'foo', ContainsAll(['foo', 'baz']))]
-
-
-def make_error(type, *args, **kwargs):
- try:
- raise type(*args, **kwargs)
- except type:
- return sys.exc_info()
-
-
-class TestMatchesExceptionInstanceInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesException(ValueError("foo"))
- error_foo = make_error(ValueError, 'foo')
- error_bar = make_error(ValueError, 'bar')
- error_base_foo = make_error(Exception, 'foo')
- matches_matches = [error_foo]
- matches_mismatches = [error_bar, error_base_foo]
-
- str_examples = [
- ("MatchesException(Exception('foo',))",
- MatchesException(Exception('foo')))
- ]
- describe_examples = [
- ("%r is not a %r" % (Exception, ValueError),
- error_base_foo,
- MatchesException(ValueError("foo"))),
- ("ValueError('bar',) has different arguments to ValueError('foo',).",
- error_bar,
- MatchesException(ValueError("foo"))),
- ]
-
-
-class TestMatchesExceptionTypeInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesException(ValueError)
- error_foo = make_error(ValueError, 'foo')
- error_sub = make_error(UnicodeError, 'bar')
- error_base_foo = make_error(Exception, 'foo')
- matches_matches = [error_foo, error_sub]
- matches_mismatches = [error_base_foo]
-
- str_examples = [
- ("MatchesException(%r)" % Exception,
- MatchesException(Exception))
- ]
- describe_examples = [
- ("%r is not a %r" % (Exception, ValueError),
- error_base_foo,
- MatchesException(ValueError)),
- ]
-
-
-class TestMatchesExceptionTypeReInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesException(ValueError, 'fo.')
- error_foo = make_error(ValueError, 'foo')
- error_sub = make_error(UnicodeError, 'foo')
- error_bar = make_error(ValueError, 'bar')
- matches_matches = [error_foo, error_sub]
- matches_mismatches = [error_bar]
-
- str_examples = [
- ("MatchesException(%r)" % Exception,
- MatchesException(Exception, 'fo.'))
- ]
- describe_examples = [
- ("'bar' does not match /fo./",
- error_bar, MatchesException(ValueError, "fo.")),
- ]
-
-
-class TestMatchesExceptionTypeMatcherInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesException(
- ValueError, AfterPreprocessing(str, Equals('foo')))
- error_foo = make_error(ValueError, 'foo')
- error_sub = make_error(UnicodeError, 'foo')
- error_bar = make_error(ValueError, 'bar')
- matches_matches = [error_foo, error_sub]
- matches_mismatches = [error_bar]
-
- str_examples = [
- ("MatchesException(%r)" % Exception,
- MatchesException(Exception, Equals('foo')))
- ]
- describe_examples = [
- ("5 != %r" % (error_bar[1],),
- error_bar, MatchesException(ValueError, Equals(5))),
- ]
-
-
-class TestNotInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = Not(Equals(1))
- matches_matches = [2]
- matches_mismatches = [1]
-
- str_examples = [
- ("Not(Equals(1))", Not(Equals(1))),
- ("Not(Equals('1'))", Not(Equals('1')))]
-
- describe_examples = [('1 matches Equals(1)', 1, Not(Equals(1)))]
-
-
-class TestMatchersAnyInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesAny(DocTestMatches("1"), DocTestMatches("2"))
- matches_matches = ["1", "2"]
- matches_mismatches = ["3"]
-
- str_examples = [(
- "MatchesAny(DocTestMatches('1\\n'), DocTestMatches('2\\n'))",
- MatchesAny(DocTestMatches("1"), DocTestMatches("2"))),
- ]
-
- describe_examples = [("""Differences: [
-Expected:
- 1
-Got:
- 3
-
-Expected:
- 2
-Got:
- 3
-
-]""",
- "3", MatchesAny(DocTestMatches("1"), DocTestMatches("2")))]
-
-
-class TestMatchesAllInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesAll(NotEquals(1), NotEquals(2))
- matches_matches = [3, 4]
- matches_mismatches = [1, 2]
-
- str_examples = [
- ("MatchesAll(NotEquals(1), NotEquals(2))",
- MatchesAll(NotEquals(1), NotEquals(2)))]
-
- describe_examples = [
- ("""Differences: [
-1 == 1
-]""",
- 1, MatchesAll(NotEquals(1), NotEquals(2))),
- ("1 == 1", 1,
- MatchesAll(NotEquals(2), NotEquals(1), Equals(3), first_only=True)),
- ]
-
-
-class TestMatchesAllDictInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesAllDict({'a': NotEquals(1), 'b': NotEquals(2)})
- matches_matches = [3, 4]
- matches_mismatches = [1, 2]
-
- str_examples = [
- ("MatchesAllDict({'a': NotEquals(1), 'b': NotEquals(2)})",
- matches_matcher)]
-
- describe_examples = [
- ("""a: 1 == 1""", 1, matches_matcher),
- ]
-
-
-class TestKeysEqual(TestCase, TestMatchersInterface):
-
- matches_matcher = KeysEqual('foo', 'bar')
- matches_matches = [
- {'foo': 0, 'bar': 1},
- ]
- matches_mismatches = [
- {},
- {'foo': 0},
- {'bar': 1},
- {'foo': 0, 'bar': 1, 'baz': 2},
- {'a': None, 'b': None, 'c': None},
- ]
-
- str_examples = [
- ("KeysEqual('foo', 'bar')", KeysEqual('foo', 'bar')),
- ]
-
- describe_examples = [
- ("['bar', 'foo'] does not match {'baz': 2, 'foo': 0, 'bar': 1}: "
- "Keys not equal",
- {'foo': 0, 'bar': 1, 'baz': 2}, KeysEqual('foo', 'bar')),
- ]
-
-
-class TestAnnotate(TestCase, TestMatchersInterface):
-
- matches_matcher = Annotate("foo", Equals(1))
- matches_matches = [1]
- matches_mismatches = [2]
-
- str_examples = [
- ("Annotate('foo', Equals(1))", Annotate("foo", Equals(1)))]
-
- describe_examples = [("1 != 2: foo", 2, Annotate('foo', Equals(1)))]
-
- def test_if_message_no_message(self):
- # Annotate.if_message returns the given matcher if there is no
- # message.
- matcher = Equals(1)
- not_annotated = Annotate.if_message('', matcher)
- self.assertIs(matcher, not_annotated)
-
- def test_if_message_given_message(self):
- # Annotate.if_message returns an annotated version of the matcher if a
- # message is provided.
- matcher = Equals(1)
- expected = Annotate('foo', matcher)
- annotated = Annotate.if_message('foo', matcher)
- self.assertThat(
- annotated,
- MatchesStructure.fromExample(expected, 'annotation', 'matcher'))
-
-
-class TestAnnotatedMismatch(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test_forwards_details(self):
- x = Mismatch('description', {'foo': 'bar'})
- annotated = AnnotatedMismatch("annotation", x)
- self.assertEqual(x.get_details(), annotated.get_details())
-
-
-class TestRaisesInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = Raises()
- def boom():
- raise Exception('foo')
- matches_matches = [boom]
- matches_mismatches = [lambda:None]
-
- # Tricky to get function objects to render constantly, and the interfaces
- # helper uses assertEqual rather than (for instance) DocTestMatches.
- str_examples = []
-
- describe_examples = []
-
-
-class TestRaisesExceptionMatcherInterface(TestCase, TestMatchersInterface):
-
- matches_matcher = Raises(
- exception_matcher=MatchesException(Exception('foo')))
- def boom_bar():
- raise Exception('bar')
- def boom_foo():
- raise Exception('foo')
- matches_matches = [boom_foo]
- matches_mismatches = [lambda:None, boom_bar]
-
- # Tricky to get function objects to render constantly, and the interfaces
- # helper uses assertEqual rather than (for instance) DocTestMatches.
- str_examples = []
-
- describe_examples = []
-
-
-class TestRaisesBaseTypes(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def raiser(self):
- raise KeyboardInterrupt('foo')
-
- def test_KeyboardInterrupt_matched(self):
- # When KeyboardInterrupt is matched, it is swallowed.
- matcher = Raises(MatchesException(KeyboardInterrupt))
- self.assertThat(self.raiser, matcher)
-
- def test_KeyboardInterrupt_propogates(self):
- # The default 'it raised' propogates KeyboardInterrupt.
- match_keyb = Raises(MatchesException(KeyboardInterrupt))
- def raise_keyb_from_match():
- matcher = Raises()
- matcher.match(self.raiser)
- self.assertThat(raise_keyb_from_match, match_keyb)
-
- def test_KeyboardInterrupt_match_Exception_propogates(self):
- # If the raised exception isn't matched, and it is not a subclass of
- # Exception, it is propogated.
- match_keyb = Raises(MatchesException(KeyboardInterrupt))
- def raise_keyb_from_match():
- if sys.version_info > (2, 5):
- matcher = Raises(MatchesException(Exception))
- else:
- # On Python 2.4 KeyboardInterrupt is a StandardError subclass
- # but should propogate from less generic exception matchers
- matcher = Raises(MatchesException(EnvironmentError))
- matcher.match(self.raiser)
- self.assertThat(raise_keyb_from_match, match_keyb)
-
-
-class TestRaisesConvenience(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test_exc_type(self):
- self.assertThat(lambda: 1/0, raises(ZeroDivisionError))
-
- def test_exc_value(self):
- e = RuntimeError("You lose!")
- def raiser():
- raise e
- self.assertThat(raiser, raises(e))
-
-
-class DoesNotStartWithTests(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test_describe(self):
- mismatch = DoesNotStartWith("fo", "bo")
- self.assertEqual("'fo' does not start with 'bo'.", mismatch.describe())
-
- def test_describe_non_ascii_unicode(self):
- string = _u("A\xA7")
- suffix = _u("B\xA7")
- mismatch = DoesNotStartWith(string, suffix)
- self.assertEqual("%s does not start with %s." % (
- text_repr(string), text_repr(suffix)),
- mismatch.describe())
-
- def test_describe_non_ascii_bytes(self):
- string = _b("A\xA7")
- suffix = _b("B\xA7")
- mismatch = DoesNotStartWith(string, suffix)
- self.assertEqual("%r does not start with %r." % (string, suffix),
- mismatch.describe())
-
-
-class StartsWithTests(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test_str(self):
- matcher = StartsWith("bar")
- self.assertEqual("StartsWith('bar')", str(matcher))
-
- def test_str_with_bytes(self):
- b = _b("\xA7")
- matcher = StartsWith(b)
- self.assertEqual("StartsWith(%r)" % (b,), str(matcher))
-
- def test_str_with_unicode(self):
- u = _u("\xA7")
- matcher = StartsWith(u)
- self.assertEqual("StartsWith(%r)" % (u,), str(matcher))
-
- def test_match(self):
- matcher = StartsWith("bar")
- self.assertIs(None, matcher.match("barf"))
-
- def test_mismatch_returns_does_not_start_with(self):
- matcher = StartsWith("bar")
- self.assertIsInstance(matcher.match("foo"), DoesNotStartWith)
-
- def test_mismatch_sets_matchee(self):
- matcher = StartsWith("bar")
- mismatch = matcher.match("foo")
- self.assertEqual("foo", mismatch.matchee)
-
- def test_mismatch_sets_expected(self):
- matcher = StartsWith("bar")
- mismatch = matcher.match("foo")
- self.assertEqual("bar", mismatch.expected)
-
-
-class DoesNotEndWithTests(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test_describe(self):
- mismatch = DoesNotEndWith("fo", "bo")
- self.assertEqual("'fo' does not end with 'bo'.", mismatch.describe())
-
- def test_describe_non_ascii_unicode(self):
- string = _u("A\xA7")
- suffix = _u("B\xA7")
- mismatch = DoesNotEndWith(string, suffix)
- self.assertEqual("%s does not end with %s." % (
- text_repr(string), text_repr(suffix)),
- mismatch.describe())
-
- def test_describe_non_ascii_bytes(self):
- string = _b("A\xA7")
- suffix = _b("B\xA7")
- mismatch = DoesNotEndWith(string, suffix)
- self.assertEqual("%r does not end with %r." % (string, suffix),
- mismatch.describe())
-
-
-class EndsWithTests(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test_str(self):
- matcher = EndsWith("bar")
- self.assertEqual("EndsWith('bar')", str(matcher))
-
- def test_str_with_bytes(self):
- b = _b("\xA7")
- matcher = EndsWith(b)
- self.assertEqual("EndsWith(%r)" % (b,), str(matcher))
-
- def test_str_with_unicode(self):
- u = _u("\xA7")
- matcher = EndsWith(u)
- self.assertEqual("EndsWith(%r)" % (u,), str(matcher))
-
- def test_match(self):
- matcher = EndsWith("arf")
- self.assertIs(None, matcher.match("barf"))
-
- def test_mismatch_returns_does_not_end_with(self):
- matcher = EndsWith("bar")
- self.assertIsInstance(matcher.match("foo"), DoesNotEndWith)
-
- def test_mismatch_sets_matchee(self):
- matcher = EndsWith("bar")
- mismatch = matcher.match("foo")
- self.assertEqual("foo", mismatch.matchee)
-
- def test_mismatch_sets_expected(self):
- matcher = EndsWith("bar")
- mismatch = matcher.match("foo")
- self.assertEqual("bar", mismatch.expected)
-
-
-def run_doctest(obj, name):
- p = doctest.DocTestParser()
- t = p.get_doctest(
- obj.__doc__, sys.modules[obj.__module__].__dict__, name, '', 0)
- r = doctest.DocTestRunner()
- output = StringIO()
- r.run(t, out=output.write)
- return r.failures, output.getvalue()
-
-
-class TestMatchesListwise(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def test_docstring(self):
- failure_count, output = run_doctest(
- MatchesListwise, "MatchesListwise")
- if failure_count:
- self.fail("Doctest failed with %s" % output)
-
-
-class TestMatchesStructure(TestCase, TestMatchersInterface):
-
- class SimpleClass:
- def __init__(self, x, y):
- self.x = x
- self.y = y
-
- matches_matcher = MatchesStructure(x=Equals(1), y=Equals(2))
- matches_matches = [SimpleClass(1, 2)]
- matches_mismatches = [
- SimpleClass(2, 2),
- SimpleClass(1, 1),
- SimpleClass(3, 3),
- ]
-
- str_examples = [
- ("MatchesStructure(x=Equals(1))", MatchesStructure(x=Equals(1))),
- ("MatchesStructure(y=Equals(2))", MatchesStructure(y=Equals(2))),
- ("MatchesStructure(x=Equals(1), y=Equals(2))",
- MatchesStructure(x=Equals(1), y=Equals(2))),
- ]
-
- describe_examples = [
- ("""\
-Differences: [
-3 != 1: x
-]""", SimpleClass(1, 2), MatchesStructure(x=Equals(3), y=Equals(2))),
- ("""\
-Differences: [
-3 != 2: y
-]""", SimpleClass(1, 2), MatchesStructure(x=Equals(1), y=Equals(3))),
- ("""\
-Differences: [
-0 != 1: x
-0 != 2: y
-]""", SimpleClass(1, 2), MatchesStructure(x=Equals(0), y=Equals(0))),
- ]
-
- def test_fromExample(self):
- self.assertThat(
- self.SimpleClass(1, 2),
- MatchesStructure.fromExample(self.SimpleClass(1, 3), 'x'))
-
- def test_byEquality(self):
- self.assertThat(
- self.SimpleClass(1, 2),
- MatchesStructure.byEquality(x=1))
-
- def test_withStructure(self):
- self.assertThat(
- self.SimpleClass(1, 2),
- MatchesStructure.byMatcher(LessThan, x=2))
-
- def test_update(self):
- self.assertThat(
- self.SimpleClass(1, 2),
- MatchesStructure(x=NotEquals(1)).update(x=Equals(1)))
-
- def test_update_none(self):
- self.assertThat(
- self.SimpleClass(1, 2),
- MatchesStructure(x=Equals(1), z=NotEquals(42)).update(
- z=None))
-
-
-class TestMatchesRegex(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesRegex('a|b')
- matches_matches = ['a', 'b']
- matches_mismatches = ['c']
-
- str_examples = [
- ("MatchesRegex('a|b')", MatchesRegex('a|b')),
- ("MatchesRegex('a|b', re.M)", MatchesRegex('a|b', re.M)),
- ("MatchesRegex('a|b', re.I|re.M)", MatchesRegex('a|b', re.I|re.M)),
- ("MatchesRegex(%r)" % (_b("\xA7"),), MatchesRegex(_b("\xA7"))),
- ("MatchesRegex(%r)" % (_u("\xA7"),), MatchesRegex(_u("\xA7"))),
- ]
-
- describe_examples = [
- ("'c' does not match /a|b/", 'c', MatchesRegex('a|b')),
- ("'c' does not match /a\d/", 'c', MatchesRegex(r'a\d')),
- ("%r does not match /\\s+\\xa7/" % (_b('c'),),
- _b('c'), MatchesRegex(_b("\\s+\xA7"))),
- ("%r does not match /\\s+\\xa7/" % (_u('c'),),
- _u('c'), MatchesRegex(_u("\\s+\xA7"))),
- ]
-
-
-class TestMatchesSetwise(TestCase):
-
- run_tests_with = FullStackRunTest
-
- def assertMismatchWithDescriptionMatching(self, value, matcher,
- description_matcher):
- mismatch = matcher.match(value)
- if mismatch is None:
- self.fail("%s matched %s" % (matcher, value))
- actual_description = mismatch.describe()
- self.assertThat(
- actual_description,
- Annotate(
- "%s matching %s" % (matcher, value),
- description_matcher))
-
- def test_matches(self):
- self.assertIs(
- None, MatchesSetwise(Equals(1), Equals(2)).match([2, 1]))
-
- def test_mismatches(self):
- self.assertMismatchWithDescriptionMatching(
- [2, 3], MatchesSetwise(Equals(1), Equals(2)),
- MatchesRegex('.*There was 1 mismatch$', re.S))
-
- def test_too_many_matchers(self):
- self.assertMismatchWithDescriptionMatching(
- [2, 3], MatchesSetwise(Equals(1), Equals(2), Equals(3)),
- Equals('There was 1 matcher left over: Equals(1)'))
-
- def test_too_many_values(self):
- self.assertMismatchWithDescriptionMatching(
- [1, 2, 3], MatchesSetwise(Equals(1), Equals(2)),
- Equals('There was 1 value left over: [3]'))
-
- def test_two_too_many_matchers(self):
- self.assertMismatchWithDescriptionMatching(
- [3], MatchesSetwise(Equals(1), Equals(2), Equals(3)),
- MatchesRegex(
- 'There were 2 matchers left over: Equals\([12]\), '
- 'Equals\([12]\)'))
-
- def test_two_too_many_values(self):
- self.assertMismatchWithDescriptionMatching(
- [1, 2, 3, 4], MatchesSetwise(Equals(1), Equals(2)),
- MatchesRegex(
- 'There were 2 values left over: \[[34], [34]\]'))
-
- def test_mismatch_and_too_many_matchers(self):
- self.assertMismatchWithDescriptionMatching(
- [2, 3], MatchesSetwise(Equals(0), Equals(1), Equals(2)),
- MatchesRegex(
- '.*There was 1 mismatch and 1 extra matcher: Equals\([01]\)',
- re.S))
-
- def test_mismatch_and_too_many_values(self):
- self.assertMismatchWithDescriptionMatching(
- [2, 3, 4], MatchesSetwise(Equals(1), Equals(2)),
- MatchesRegex(
- '.*There was 1 mismatch and 1 extra value: \[[34]\]',
- re.S))
-
- def test_mismatch_and_two_too_many_matchers(self):
- self.assertMismatchWithDescriptionMatching(
- [3, 4], MatchesSetwise(
- Equals(0), Equals(1), Equals(2), Equals(3)),
- MatchesRegex(
- '.*There was 1 mismatch and 2 extra matchers: '
- 'Equals\([012]\), Equals\([012]\)', re.S))
-
- def test_mismatch_and_two_too_many_values(self):
- self.assertMismatchWithDescriptionMatching(
- [2, 3, 4, 5], MatchesSetwise(Equals(1), Equals(2)),
- MatchesRegex(
- '.*There was 1 mismatch and 2 extra values: \[[145], [145]\]',
- re.S))
-
-
-class TestAfterPreprocessing(TestCase, TestMatchersInterface):
-
- def parity(x):
- return x % 2
-
- matches_matcher = AfterPreprocessing(parity, Equals(1))
- matches_matches = [3, 5]
- matches_mismatches = [2]
-
- str_examples = [
- ("AfterPreprocessing(<function parity>, Equals(1))",
- AfterPreprocessing(parity, Equals(1))),
- ]
-
- describe_examples = [
- ("1 != 0: after <function parity> on 2", 2,
- AfterPreprocessing(parity, Equals(1))),
- ("1 != 0", 2,
- AfterPreprocessing(parity, Equals(1), annotate=False)),
- ]
-
-
class TestMismatchDecorator(TestCase):
run_tests_with = FullStackRunTest
@@ -1095,462 +127,6 @@
repr(decorated))
-class TestAllMatch(TestCase, TestMatchersInterface):
-
- matches_matcher = AllMatch(LessThan(10))
- matches_matches = [
- [9, 9, 9],
- (9, 9),
- iter([9, 9, 9, 9, 9]),
- ]
- matches_mismatches = [
- [11, 9, 9],
- iter([9, 12, 9, 11]),
- ]
-
- str_examples = [
- ("AllMatch(LessThan(12))", AllMatch(LessThan(12))),
- ]
-
- describe_examples = [
- ('Differences: [\n'
- '10 is not > 11\n'
- '10 is not > 10\n'
- ']',
- [11, 9, 10],
- AllMatch(LessThan(10))),
- ]
-
-
-class TestSameMembers(TestCase, TestMatchersInterface):
-
- matches_matcher = SameMembers([1, 1, 2, 3, {'foo': 'bar'}])
- matches_matches = [
- [1, 1, 2, 3, {'foo': 'bar'}],
- [3, {'foo': 'bar'}, 1, 2, 1],
- [3, 2, 1, {'foo': 'bar'}, 1],
- (2, {'foo': 'bar'}, 3, 1, 1),
- ]
- matches_mismatches = [
- set([1, 2, 3]),
- [1, 1, 2, 3, 5],
- [1, 2, 3, {'foo': 'bar'}],
- 'foo',
- ]
-
- describe_examples = [
- (("elements differ:\n"
- "reference = ['apple', 'orange', 'canteloupe', 'watermelon', 'lemon', 'banana']\n"
- "actual = ['orange', 'apple', 'banana', 'sparrow', 'lemon', 'canteloupe']\n"
- ": \n"
- "missing: ['watermelon']\n"
- "extra: ['sparrow']"
- ),
- ['orange', 'apple', 'banana', 'sparrow', 'lemon', 'canteloupe',],
- SameMembers(
- ['apple', 'orange', 'canteloupe', 'watermelon',
- 'lemon', 'banana',])),
- ]
-
- str_examples = [
- ('SameMembers([1, 2, 3])', SameMembers([1, 2, 3])),
- ]
-
-
-class PathHelpers(object):
-
- def mkdtemp(self):
- directory = tempfile.mkdtemp()
- self.addCleanup(shutil.rmtree, directory)
- return directory
-
- def create_file(self, filename, contents=''):
- fp = open(filename, 'w')
- try:
- fp.write(contents)
- finally:
- fp.close()
-
- def touch(self, filename):
- return self.create_file(filename)
-
-
-class TestPathExists(TestCase, PathHelpers):
-
- def test_exists(self):
- tempdir = self.mkdtemp()
- self.assertThat(tempdir, PathExists())
-
- def test_not_exists(self):
- doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
- mismatch = PathExists().match(doesntexist)
- self.assertThat(
- "%s does not exist." % doesntexist, Equals(mismatch.describe()))
-
-
-class TestDirExists(TestCase, PathHelpers):
-
- def test_exists(self):
- tempdir = self.mkdtemp()
- self.assertThat(tempdir, DirExists())
-
- def test_not_exists(self):
- doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
- mismatch = DirExists().match(doesntexist)
- self.assertThat(
- PathExists().match(doesntexist).describe(),
- Equals(mismatch.describe()))
-
- def test_not_a_directory(self):
- filename = os.path.join(self.mkdtemp(), 'foo')
- self.touch(filename)
- mismatch = DirExists().match(filename)
- self.assertThat(
- "%s is not a directory." % filename, Equals(mismatch.describe()))
-
-
-class TestFileExists(TestCase, PathHelpers):
-
- def test_exists(self):
- tempdir = self.mkdtemp()
- filename = os.path.join(tempdir, 'filename')
- self.touch(filename)
- self.assertThat(filename, FileExists())
-
- def test_not_exists(self):
- doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
- mismatch = FileExists().match(doesntexist)
- self.assertThat(
- PathExists().match(doesntexist).describe(),
- Equals(mismatch.describe()))
-
- def test_not_a_file(self):
- tempdir = self.mkdtemp()
- mismatch = FileExists().match(tempdir)
- self.assertThat(
- "%s is not a file." % tempdir, Equals(mismatch.describe()))
-
-
-class TestDirContains(TestCase, PathHelpers):
-
- def test_empty(self):
- tempdir = self.mkdtemp()
- self.assertThat(tempdir, DirContains([]))
-
- def test_not_exists(self):
- doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
- mismatch = DirContains([]).match(doesntexist)
- self.assertThat(
- PathExists().match(doesntexist).describe(),
- Equals(mismatch.describe()))
-
- def test_contains_files(self):
- tempdir = self.mkdtemp()
- self.touch(os.path.join(tempdir, 'foo'))
- self.touch(os.path.join(tempdir, 'bar'))
- self.assertThat(tempdir, DirContains(['bar', 'foo']))
-
- def test_matcher(self):
- tempdir = self.mkdtemp()
- self.touch(os.path.join(tempdir, 'foo'))
- self.touch(os.path.join(tempdir, 'bar'))
- self.assertThat(tempdir, DirContains(matcher=Contains('bar')))
-
- def test_neither_specified(self):
- self.assertRaises(AssertionError, DirContains)
-
- def test_both_specified(self):
- self.assertRaises(
- AssertionError, DirContains, filenames=[], matcher=Contains('a'))
-
- def test_does_not_contain_files(self):
- tempdir = self.mkdtemp()
- self.touch(os.path.join(tempdir, 'foo'))
- mismatch = DirContains(['bar', 'foo']).match(tempdir)
- self.assertThat(
- Equals(['bar', 'foo']).match(['foo']).describe(),
- Equals(mismatch.describe()))
-
-
-class TestFileContains(TestCase, PathHelpers):
-
- def test_not_exists(self):
- doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
- mismatch = FileContains('').match(doesntexist)
- self.assertThat(
- PathExists().match(doesntexist).describe(),
- Equals(mismatch.describe()))
-
- def test_contains(self):
- tempdir = self.mkdtemp()
- filename = os.path.join(tempdir, 'foo')
- self.create_file(filename, 'Hello World!')
- self.assertThat(filename, FileContains('Hello World!'))
-
- def test_matcher(self):
- tempdir = self.mkdtemp()
- filename = os.path.join(tempdir, 'foo')
- self.create_file(filename, 'Hello World!')
- self.assertThat(
- filename, FileContains(matcher=DocTestMatches('Hello World!')))
-
- def test_neither_specified(self):
- self.assertRaises(AssertionError, FileContains)
-
- def test_both_specified(self):
- self.assertRaises(
- AssertionError, FileContains, contents=[], matcher=Contains('a'))
-
- def test_does_not_contain(self):
- tempdir = self.mkdtemp()
- filename = os.path.join(tempdir, 'foo')
- self.create_file(filename, 'Goodbye Cruel World!')
- mismatch = FileContains('Hello World!').match(filename)
- self.assertThat(
- Equals('Hello World!').match('Goodbye Cruel World!').describe(),
- Equals(mismatch.describe()))
-
-
-def is_even(x):
- return x % 2 == 0
-
-
-class TestMatchesPredicate(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesPredicate(is_even, "%s is not even")
- matches_matches = [2, 4, 6, 8]
- matches_mismatches = [3, 5, 7, 9]
-
- str_examples = [
- ("MatchesPredicate(%r, %r)" % (is_even, "%s is not even"),
- MatchesPredicate(is_even, "%s is not even")),
- ]
-
- describe_examples = [
- ('7 is not even', 7, MatchesPredicate(is_even, "%s is not even")),
- ]
-
-
-class TestTarballContains(TestCase, PathHelpers):
-
- def test_match(self):
- tempdir = self.mkdtemp()
- in_temp_dir = lambda x: os.path.join(tempdir, x)
- self.touch(in_temp_dir('a'))
- self.touch(in_temp_dir('b'))
- tarball = tarfile.open(in_temp_dir('foo.tar.gz'), 'w')
- tarball.add(in_temp_dir('a'), 'a')
- tarball.add(in_temp_dir('b'), 'b')
- tarball.close()
- self.assertThat(
- in_temp_dir('foo.tar.gz'), TarballContains(['b', 'a']))
-
- def test_mismatch(self):
- tempdir = self.mkdtemp()
- in_temp_dir = lambda x: os.path.join(tempdir, x)
- self.touch(in_temp_dir('a'))
- self.touch(in_temp_dir('b'))
- tarball = tarfile.open(in_temp_dir('foo.tar.gz'), 'w')
- tarball.add(in_temp_dir('a'), 'a')
- tarball.add(in_temp_dir('b'), 'b')
- tarball.close()
- mismatch = TarballContains(['d', 'c']).match(in_temp_dir('foo.tar.gz'))
- self.assertEqual(
- mismatch.describe(),
- Equals(['c', 'd']).match(['a', 'b']).describe())
-
-
-class TestSamePath(TestCase, PathHelpers):
-
- def test_same_string(self):
- self.assertThat('foo', SamePath('foo'))
-
- def test_relative_and_absolute(self):
- path = 'foo'
- abspath = os.path.abspath(path)
- self.assertThat(path, SamePath(abspath))
- self.assertThat(abspath, SamePath(path))
-
- def test_real_path(self):
- tempdir = self.mkdtemp()
- source = os.path.join(tempdir, 'source')
- self.touch(source)
- target = os.path.join(tempdir, 'target')
- try:
- os.symlink(source, target)
- except (AttributeError, NotImplementedError):
- self.skip("No symlink support")
- self.assertThat(source, SamePath(target))
- self.assertThat(target, SamePath(source))
-
-
-class TestHasPermissions(TestCase, PathHelpers):
-
- def test_match(self):
- tempdir = self.mkdtemp()
- filename = os.path.join(tempdir, 'filename')
- self.touch(filename)
- permissions = oct(os.stat(filename).st_mode)[-4:]
- self.assertThat(filename, HasPermissions(permissions))
-
-
-class TestSubDictOf(TestCase, TestMatchersInterface):
-
- matches_matcher = _SubDictOf({'foo': 'bar', 'baz': 'qux'})
-
- matches_matches = [
- {'foo': 'bar', 'baz': 'qux'},
- {'foo': 'bar'},
- ]
-
- matches_mismatches = [
- {'foo': 'bar', 'baz': 'qux', 'cat': 'dog'},
- {'foo': 'bar', 'cat': 'dog'},
- ]
-
- str_examples = []
- describe_examples = []
-
-
-class TestMatchesDict(TestCase, TestMatchersInterface):
-
- matches_matcher = MatchesDict(
- {'foo': Equals('bar'), 'baz': Not(Equals('qux'))})
-
- matches_matches = [
- {'foo': 'bar', 'baz': None},
- {'foo': 'bar', 'baz': 'quux'},
- ]
- matches_mismatches = [
- {},
- {'foo': 'bar', 'baz': 'qux'},
- {'foo': 'bop', 'baz': 'qux'},
- {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'},
- {'foo': 'bar', 'cat': 'dog'},
- ]
-
- str_examples = [
- ("MatchesDict({'foo': %s, 'baz': %s})" % (
- Equals('bar'), Not(Equals('qux'))),
- matches_matcher),
- ]
-
- describe_examples = [
- ("Missing: {\n"
- " 'baz': Not(Equals('qux')),\n"
- " 'foo': Equals('bar'),\n"
- "}",
- {}, matches_matcher),
- ("Differences: {\n"
- " 'baz': 'qux' matches Equals('qux'),\n"
- "}",
- {'foo': 'bar', 'baz': 'qux'}, matches_matcher),
- ("Differences: {\n"
- " 'baz': 'qux' matches Equals('qux'),\n"
- " 'foo': 'bar' != 'bop',\n"
- "}",
- {'foo': 'bop', 'baz': 'qux'}, matches_matcher),
- ("Extra: {\n"
- " 'cat': 'dog',\n"
- "}",
- {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'}, matches_matcher),
- ("Extra: {\n"
- " 'cat': 'dog',\n"
- "}\n"
- "Missing: {\n"
- " 'baz': Not(Equals('qux')),\n"
- "}",
- {'foo': 'bar', 'cat': 'dog'}, matches_matcher),
- ]
-
-
-class TestContainsDict(TestCase, TestMatchersInterface):
-
- matches_matcher = ContainsDict(
- {'foo': Equals('bar'), 'baz': Not(Equals('qux'))})
-
- matches_matches = [
- {'foo': 'bar', 'baz': None},
- {'foo': 'bar', 'baz': 'quux'},
- {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'},
- ]
- matches_mismatches = [
- {},
- {'foo': 'bar', 'baz': 'qux'},
- {'foo': 'bop', 'baz': 'qux'},
- {'foo': 'bar', 'cat': 'dog'},
- {'foo': 'bar'},
- ]
-
- str_examples = [
- ("ContainsDict({'foo': %s, 'baz': %s})" % (
- Equals('bar'), Not(Equals('qux'))),
- matches_matcher),
- ]
-
- describe_examples = [
- ("Missing: {\n"
- " 'baz': Not(Equals('qux')),\n"
- " 'foo': Equals('bar'),\n"
- "}",
- {}, matches_matcher),
- ("Differences: {\n"
- " 'baz': 'qux' matches Equals('qux'),\n"
- "}",
- {'foo': 'bar', 'baz': 'qux'}, matches_matcher),
- ("Differences: {\n"
- " 'baz': 'qux' matches Equals('qux'),\n"
- " 'foo': 'bar' != 'bop',\n"
- "}",
- {'foo': 'bop', 'baz': 'qux'}, matches_matcher),
- ("Missing: {\n"
- " 'baz': Not(Equals('qux')),\n"
- "}",
- {'foo': 'bar', 'cat': 'dog'}, matches_matcher),
- ]
-
-
-class TestContainedByDict(TestCase, TestMatchersInterface):
-
- matches_matcher = ContainedByDict(
- {'foo': Equals('bar'), 'baz': Not(Equals('qux'))})
-
- matches_matches = [
- {},
- {'foo': 'bar'},
- {'foo': 'bar', 'baz': 'quux'},
- {'baz': 'quux'},
- ]
- matches_mismatches = [
- {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'},
- {'foo': 'bar', 'baz': 'qux'},
- {'foo': 'bop', 'baz': 'qux'},
- {'foo': 'bar', 'cat': 'dog'},
- ]
-
- str_examples = [
- ("ContainedByDict({'foo': %s, 'baz': %s})" % (
- Equals('bar'), Not(Equals('qux'))),
- matches_matcher),
- ]
-
- describe_examples = [
- ("Differences: {\n"
- " 'baz': 'qux' matches Equals('qux'),\n"
- "}",
- {'foo': 'bar', 'baz': 'qux'}, matches_matcher),
- ("Differences: {\n"
- " 'baz': 'qux' matches Equals('qux'),\n"
- " 'foo': 'bar' != 'bop',\n"
- "}",
- {'foo': 'bop', 'baz': 'qux'}, matches_matcher),
- ("Extra: {\n"
- " 'cat': 'dog',\n"
- "}",
- {'foo': 'bar', 'cat': 'dog'}, matches_matcher),
- ]
-
-
def test_suite():
from unittest import TestLoader
return TestLoader().loadTestsFromName(__name__)
=== added file 'testtools/tests/matchers/test_datastructures.py'
--- testtools/tests/matchers/test_datastructures.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/test_datastructures.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,209 @@
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
+
+import doctest
+import re
+import sys
+
+from testtools import TestCase
+from testtools.compat import StringIO
+from testtools.matchers import (
+ Annotate,
+ Equals,
+ LessThan,
+ MatchesRegex,
+ NotEquals,
+ )
+from testtools.matchers._datastructures import (
+ ContainsAll,
+ MatchesListwise,
+ MatchesStructure,
+ MatchesSetwise,
+ )
+from testtools.tests.helpers import FullStackRunTest
+from testtools.tests.matchers.helpers import TestMatchersInterface
+
+
+def run_doctest(obj, name):
+ p = doctest.DocTestParser()
+ t = p.get_doctest(
+ obj.__doc__, sys.modules[obj.__module__].__dict__, name, '', 0)
+ r = doctest.DocTestRunner()
+ output = StringIO()
+ r.run(t, out=output.write)
+ return r.failures, output.getvalue()
+
+
+class TestMatchesListwise(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test_docstring(self):
+ failure_count, output = run_doctest(
+ MatchesListwise, "MatchesListwise")
+ if failure_count:
+ self.fail("Doctest failed with %s" % output)
+
+
+class TestMatchesStructure(TestCase, TestMatchersInterface):
+
+ class SimpleClass:
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+ matches_matcher = MatchesStructure(x=Equals(1), y=Equals(2))
+ matches_matches = [SimpleClass(1, 2)]
+ matches_mismatches = [
+ SimpleClass(2, 2),
+ SimpleClass(1, 1),
+ SimpleClass(3, 3),
+ ]
+
+ str_examples = [
+ ("MatchesStructure(x=Equals(1))", MatchesStructure(x=Equals(1))),
+ ("MatchesStructure(y=Equals(2))", MatchesStructure(y=Equals(2))),
+ ("MatchesStructure(x=Equals(1), y=Equals(2))",
+ MatchesStructure(x=Equals(1), y=Equals(2))),
+ ]
+
+ describe_examples = [
+ ("""\
+Differences: [
+3 != 1: x
+]""", SimpleClass(1, 2), MatchesStructure(x=Equals(3), y=Equals(2))),
+ ("""\
+Differences: [
+3 != 2: y
+]""", SimpleClass(1, 2), MatchesStructure(x=Equals(1), y=Equals(3))),
+ ("""\
+Differences: [
+0 != 1: x
+0 != 2: y
+]""", SimpleClass(1, 2), MatchesStructure(x=Equals(0), y=Equals(0))),
+ ]
+
+ def test_fromExample(self):
+ self.assertThat(
+ self.SimpleClass(1, 2),
+ MatchesStructure.fromExample(self.SimpleClass(1, 3), 'x'))
+
+ def test_byEquality(self):
+ self.assertThat(
+ self.SimpleClass(1, 2),
+ MatchesStructure.byEquality(x=1))
+
+ def test_withStructure(self):
+ self.assertThat(
+ self.SimpleClass(1, 2),
+ MatchesStructure.byMatcher(LessThan, x=2))
+
+ def test_update(self):
+ self.assertThat(
+ self.SimpleClass(1, 2),
+ MatchesStructure(x=NotEquals(1)).update(x=Equals(1)))
+
+ def test_update_none(self):
+ self.assertThat(
+ self.SimpleClass(1, 2),
+ MatchesStructure(x=Equals(1), z=NotEquals(42)).update(
+ z=None))
+
+
+class TestMatchesSetwise(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def assertMismatchWithDescriptionMatching(self, value, matcher,
+ description_matcher):
+ mismatch = matcher.match(value)
+ if mismatch is None:
+ self.fail("%s matched %s" % (matcher, value))
+ actual_description = mismatch.describe()
+ self.assertThat(
+ actual_description,
+ Annotate(
+ "%s matching %s" % (matcher, value),
+ description_matcher))
+
+ def test_matches(self):
+ self.assertIs(
+ None, MatchesSetwise(Equals(1), Equals(2)).match([2, 1]))
+
+ def test_mismatches(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex('.*There was 1 mismatch$', re.S))
+
+ def test_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3], MatchesSetwise(Equals(1), Equals(2), Equals(3)),
+ Equals('There was 1 matcher left over: Equals(1)'))
+
+ def test_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [1, 2, 3], MatchesSetwise(Equals(1), Equals(2)),
+ Equals('There was 1 value left over: [3]'))
+
+ def test_two_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [3], MatchesSetwise(Equals(1), Equals(2), Equals(3)),
+ MatchesRegex(
+ 'There were 2 matchers left over: Equals\([12]\), '
+ 'Equals\([12]\)'))
+
+ def test_two_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [1, 2, 3, 4], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex(
+ 'There were 2 values left over: \[[34], [34]\]'))
+
+ def test_mismatch_and_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3], MatchesSetwise(Equals(0), Equals(1), Equals(2)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 1 extra matcher: Equals\([01]\)',
+ re.S))
+
+ def test_mismatch_and_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3, 4], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 1 extra value: \[[34]\]',
+ re.S))
+
+ def test_mismatch_and_two_too_many_matchers(self):
+ self.assertMismatchWithDescriptionMatching(
+ [3, 4], MatchesSetwise(
+ Equals(0), Equals(1), Equals(2), Equals(3)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 2 extra matchers: '
+ 'Equals\([012]\), Equals\([012]\)', re.S))
+
+ def test_mismatch_and_two_too_many_values(self):
+ self.assertMismatchWithDescriptionMatching(
+ [2, 3, 4, 5], MatchesSetwise(Equals(1), Equals(2)),
+ MatchesRegex(
+ '.*There was 1 mismatch and 2 extra values: \[[145], [145]\]',
+ re.S))
+
+
+class TestContainsAllInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = ContainsAll(['foo', 'bar'])
+ matches_matches = [['foo', 'bar'], ['foo', 'z', 'bar'], ['bar', 'foo']]
+ matches_mismatches = [['f', 'g'], ['foo', 'baz'], []]
+
+ str_examples = [(
+ "MatchesAll(Contains('foo'), Contains('bar'))",
+ ContainsAll(['foo', 'bar'])),
+ ]
+
+ describe_examples = [("""Differences: [
+'baz' not in 'foo'
+]""",
+ 'foo', ContainsAll(['foo', 'baz']))]
+
+
+def test_suite():
+ from unittest import TestLoader
+ return TestLoader().loadTestsFromName(__name__)
=== added file 'testtools/tests/matchers/test_dict.py'
--- testtools/tests/matchers/test_dict.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/test_dict.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,217 @@
+from testtools import TestCase
+from testtools.matchers import (
+ Equals,
+ NotEquals,
+ Not,
+ )
+from testtools.matchers._dict import (
+ ContainedByDict,
+ ContainsDict,
+ KeysEqual,
+ MatchesAllDict,
+ MatchesDict,
+ _SubDictOf,
+ )
+from testtools.tests.matchers.helpers import TestMatchersInterface
+
+
+class TestMatchesAllDictInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesAllDict({'a': NotEquals(1), 'b': NotEquals(2)})
+ matches_matches = [3, 4]
+ matches_mismatches = [1, 2]
+
+ str_examples = [
+ ("MatchesAllDict({'a': NotEquals(1), 'b': NotEquals(2)})",
+ matches_matcher)]
+
+ describe_examples = [
+ ("""a: 1 == 1""", 1, matches_matcher),
+ ]
+
+
+class TestKeysEqual(TestCase, TestMatchersInterface):
+
+ matches_matcher = KeysEqual('foo', 'bar')
+ matches_matches = [
+ {'foo': 0, 'bar': 1},
+ ]
+ matches_mismatches = [
+ {},
+ {'foo': 0},
+ {'bar': 1},
+ {'foo': 0, 'bar': 1, 'baz': 2},
+ {'a': None, 'b': None, 'c': None},
+ ]
+
+ str_examples = [
+ ("KeysEqual('foo', 'bar')", KeysEqual('foo', 'bar')),
+ ]
+
+ describe_examples = [
+ ("['bar', 'foo'] does not match {'baz': 2, 'foo': 0, 'bar': 1}: "
+ "Keys not equal",
+ {'foo': 0, 'bar': 1, 'baz': 2}, KeysEqual('foo', 'bar')),
+ ]
+
+
+class TestSubDictOf(TestCase, TestMatchersInterface):
+
+ matches_matcher = _SubDictOf({'foo': 'bar', 'baz': 'qux'})
+
+ matches_matches = [
+ {'foo': 'bar', 'baz': 'qux'},
+ {'foo': 'bar'},
+ ]
+
+ matches_mismatches = [
+ {'foo': 'bar', 'baz': 'qux', 'cat': 'dog'},
+ {'foo': 'bar', 'cat': 'dog'},
+ ]
+
+ str_examples = []
+ describe_examples = []
+
+
+class TestMatchesDict(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesDict(
+ {'foo': Equals('bar'), 'baz': Not(Equals('qux'))})
+
+ matches_matches = [
+ {'foo': 'bar', 'baz': None},
+ {'foo': 'bar', 'baz': 'quux'},
+ ]
+ matches_mismatches = [
+ {},
+ {'foo': 'bar', 'baz': 'qux'},
+ {'foo': 'bop', 'baz': 'qux'},
+ {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'},
+ {'foo': 'bar', 'cat': 'dog'},
+ ]
+
+ str_examples = [
+ ("MatchesDict({'foo': %s, 'baz': %s})" % (
+ Equals('bar'), Not(Equals('qux'))),
+ matches_matcher),
+ ]
+
+ describe_examples = [
+ ("Missing: {\n"
+ " 'baz': Not(Equals('qux')),\n"
+ " 'foo': Equals('bar'),\n"
+ "}",
+ {}, matches_matcher),
+ ("Differences: {\n"
+ " 'baz': 'qux' matches Equals('qux'),\n"
+ "}",
+ {'foo': 'bar', 'baz': 'qux'}, matches_matcher),
+ ("Differences: {\n"
+ " 'baz': 'qux' matches Equals('qux'),\n"
+ " 'foo': 'bar' != 'bop',\n"
+ "}",
+ {'foo': 'bop', 'baz': 'qux'}, matches_matcher),
+ ("Extra: {\n"
+ " 'cat': 'dog',\n"
+ "}",
+ {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'}, matches_matcher),
+ ("Extra: {\n"
+ " 'cat': 'dog',\n"
+ "}\n"
+ "Missing: {\n"
+ " 'baz': Not(Equals('qux')),\n"
+ "}",
+ {'foo': 'bar', 'cat': 'dog'}, matches_matcher),
+ ]
+
+
+class TestContainsDict(TestCase, TestMatchersInterface):
+
+ matches_matcher = ContainsDict(
+ {'foo': Equals('bar'), 'baz': Not(Equals('qux'))})
+
+ matches_matches = [
+ {'foo': 'bar', 'baz': None},
+ {'foo': 'bar', 'baz': 'quux'},
+ {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'},
+ ]
+ matches_mismatches = [
+ {},
+ {'foo': 'bar', 'baz': 'qux'},
+ {'foo': 'bop', 'baz': 'qux'},
+ {'foo': 'bar', 'cat': 'dog'},
+ {'foo': 'bar'},
+ ]
+
+ str_examples = [
+ ("ContainsDict({'foo': %s, 'baz': %s})" % (
+ Equals('bar'), Not(Equals('qux'))),
+ matches_matcher),
+ ]
+
+ describe_examples = [
+ ("Missing: {\n"
+ " 'baz': Not(Equals('qux')),\n"
+ " 'foo': Equals('bar'),\n"
+ "}",
+ {}, matches_matcher),
+ ("Differences: {\n"
+ " 'baz': 'qux' matches Equals('qux'),\n"
+ "}",
+ {'foo': 'bar', 'baz': 'qux'}, matches_matcher),
+ ("Differences: {\n"
+ " 'baz': 'qux' matches Equals('qux'),\n"
+ " 'foo': 'bar' != 'bop',\n"
+ "}",
+ {'foo': 'bop', 'baz': 'qux'}, matches_matcher),
+ ("Missing: {\n"
+ " 'baz': Not(Equals('qux')),\n"
+ "}",
+ {'foo': 'bar', 'cat': 'dog'}, matches_matcher),
+ ]
+
+
+class TestContainedByDict(TestCase, TestMatchersInterface):
+
+ matches_matcher = ContainedByDict(
+ {'foo': Equals('bar'), 'baz': Not(Equals('qux'))})
+
+ matches_matches = [
+ {},
+ {'foo': 'bar'},
+ {'foo': 'bar', 'baz': 'quux'},
+ {'baz': 'quux'},
+ ]
+ matches_mismatches = [
+ {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'},
+ {'foo': 'bar', 'baz': 'qux'},
+ {'foo': 'bop', 'baz': 'qux'},
+ {'foo': 'bar', 'cat': 'dog'},
+ ]
+
+ str_examples = [
+ ("ContainedByDict({'foo': %s, 'baz': %s})" % (
+ Equals('bar'), Not(Equals('qux'))),
+ matches_matcher),
+ ]
+
+ describe_examples = [
+ ("Differences: {\n"
+ " 'baz': 'qux' matches Equals('qux'),\n"
+ "}",
+ {'foo': 'bar', 'baz': 'qux'}, matches_matcher),
+ ("Differences: {\n"
+ " 'baz': 'qux' matches Equals('qux'),\n"
+ " 'foo': 'bar' != 'bop',\n"
+ "}",
+ {'foo': 'bop', 'baz': 'qux'}, matches_matcher),
+ ("Extra: {\n"
+ " 'cat': 'dog',\n"
+ "}",
+ {'foo': 'bar', 'cat': 'dog'}, matches_matcher),
+ ]
+
+
+def test_suite():
+ from unittest import TestLoader
+ return TestLoader().loadTestsFromName(__name__)
=== added file 'testtools/tests/matchers/test_doctest.py'
--- testtools/tests/matchers/test_doctest.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/test_doctest.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,82 @@
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
+
+import doctest
+
+from testtools import TestCase
+from testtools.compat import (
+ str_is_unicode,
+ _b,
+ _u,
+ )
+from testtools.matchers._doctest import DocTestMatches
+from testtools.tests.helpers import FullStackRunTest
+from testtools.tests.matchers.helpers import TestMatchersInterface
+
+
+
+class TestDocTestMatchesInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = DocTestMatches("Ran 1 test in ...s", doctest.ELLIPSIS)
+ matches_matches = ["Ran 1 test in 0.000s", "Ran 1 test in 1.234s"]
+ matches_mismatches = ["Ran 1 tests in 0.000s", "Ran 2 test in 0.000s"]
+
+ str_examples = [("DocTestMatches('Ran 1 test in ...s\\n')",
+ DocTestMatches("Ran 1 test in ...s")),
+ ("DocTestMatches('foo\\n', flags=8)", DocTestMatches("foo", flags=8)),
+ ]
+
+ describe_examples = [('Expected:\n Ran 1 tests in ...s\nGot:\n'
+ ' Ran 1 test in 0.123s\n', "Ran 1 test in 0.123s",
+ DocTestMatches("Ran 1 tests in ...s", doctest.ELLIPSIS))]
+
+
+class TestDocTestMatchesInterfaceUnicode(TestCase, TestMatchersInterface):
+
+ matches_matcher = DocTestMatches(_u("\xa7..."), doctest.ELLIPSIS)
+ matches_matches = [_u("\xa7"), _u("\xa7 more\n")]
+ matches_mismatches = ["\\xa7", _u("more \xa7"), _u("\n\xa7")]
+
+ str_examples = [("DocTestMatches(%r)" % (_u("\xa7\n"),),
+ DocTestMatches(_u("\xa7"))),
+ ]
+
+ describe_examples = [(
+ _u("Expected:\n \xa7\nGot:\n a\n"),
+ "a",
+ DocTestMatches(_u("\xa7"), doctest.ELLIPSIS))]
+
+
+class TestDocTestMatchesSpecific(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test___init__simple(self):
+ matcher = DocTestMatches("foo")
+ self.assertEqual("foo\n", matcher.want)
+
+ def test___init__flags(self):
+ matcher = DocTestMatches("bar\n", doctest.ELLIPSIS)
+ self.assertEqual("bar\n", matcher.want)
+ self.assertEqual(doctest.ELLIPSIS, matcher.flags)
+
+ def test_describe_non_ascii_bytes(self):
+ """Even with bytestrings, the mismatch should be coercible to unicode
+
+ DocTestMatches is intended for text, but the Python 2 str type also
+ permits arbitrary binary inputs. This is a slightly bogus thing to do,
+ and under Python 3 using bytes objects will reasonably raise an error.
+ """
+ header = _b("\x89PNG\r\n\x1a\n...")
+ if str_is_unicode:
+ self.assertRaises(TypeError,
+ DocTestMatches, header, doctest.ELLIPSIS)
+ return
+ matcher = DocTestMatches(header, doctest.ELLIPSIS)
+ mismatch = matcher.match(_b("GIF89a\1\0\1\0\0\0\0;"))
+ # Must be treatable as unicode text, the exact output matters less
+ self.assertTrue(unicode(mismatch.describe()))
+
+
+def test_suite():
+ from unittest import TestLoader
+ return TestLoader().loadTestsFromName(__name__)
=== added file 'testtools/tests/matchers/test_exception.py'
--- testtools/tests/matchers/test_exception.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/test_exception.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,192 @@
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
+
+import sys
+
+from testtools import TestCase
+from testtools.matchers import (
+ AfterPreprocessing,
+ Equals,
+ )
+from testtools.matchers._exception import (
+ MatchesException,
+ Raises,
+ raises,
+ )
+from testtools.tests.helpers import FullStackRunTest
+from testtools.tests.matchers.helpers import TestMatchersInterface
+
+
+def make_error(type, *args, **kwargs):
+ try:
+ raise type(*args, **kwargs)
+ except type:
+ return sys.exc_info()
+
+
+class TestMatchesExceptionInstanceInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesException(ValueError("foo"))
+ error_foo = make_error(ValueError, 'foo')
+ error_bar = make_error(ValueError, 'bar')
+ error_base_foo = make_error(Exception, 'foo')
+ matches_matches = [error_foo]
+ matches_mismatches = [error_bar, error_base_foo]
+
+ str_examples = [
+ ("MatchesException(Exception('foo',))",
+ MatchesException(Exception('foo')))
+ ]
+ describe_examples = [
+ ("%r is not a %r" % (Exception, ValueError),
+ error_base_foo,
+ MatchesException(ValueError("foo"))),
+ ("ValueError('bar',) has different arguments to ValueError('foo',).",
+ error_bar,
+ MatchesException(ValueError("foo"))),
+ ]
+
+
+class TestMatchesExceptionTypeInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesException(ValueError)
+ error_foo = make_error(ValueError, 'foo')
+ error_sub = make_error(UnicodeError, 'bar')
+ error_base_foo = make_error(Exception, 'foo')
+ matches_matches = [error_foo, error_sub]
+ matches_mismatches = [error_base_foo]
+
+ str_examples = [
+ ("MatchesException(%r)" % Exception,
+ MatchesException(Exception))
+ ]
+ describe_examples = [
+ ("%r is not a %r" % (Exception, ValueError),
+ error_base_foo,
+ MatchesException(ValueError)),
+ ]
+
+
+class TestMatchesExceptionTypeReInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesException(ValueError, 'fo.')
+ error_foo = make_error(ValueError, 'foo')
+ error_sub = make_error(UnicodeError, 'foo')
+ error_bar = make_error(ValueError, 'bar')
+ matches_matches = [error_foo, error_sub]
+ matches_mismatches = [error_bar]
+
+ str_examples = [
+ ("MatchesException(%r)" % Exception,
+ MatchesException(Exception, 'fo.'))
+ ]
+ describe_examples = [
+ ("'bar' does not match /fo./",
+ error_bar, MatchesException(ValueError, "fo.")),
+ ]
+
+
+class TestMatchesExceptionTypeMatcherInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesException(
+ ValueError, AfterPreprocessing(str, Equals('foo')))
+ error_foo = make_error(ValueError, 'foo')
+ error_sub = make_error(UnicodeError, 'foo')
+ error_bar = make_error(ValueError, 'bar')
+ matches_matches = [error_foo, error_sub]
+ matches_mismatches = [error_bar]
+
+ str_examples = [
+ ("MatchesException(%r)" % Exception,
+ MatchesException(Exception, Equals('foo')))
+ ]
+ describe_examples = [
+ ("5 != %r" % (error_bar[1],),
+ error_bar, MatchesException(ValueError, Equals(5))),
+ ]
+
+
+class TestRaisesInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = Raises()
+ def boom():
+ raise Exception('foo')
+ matches_matches = [boom]
+ matches_mismatches = [lambda:None]
+
+ # Tricky to get function objects to render constantly, and the interfaces
+ # helper uses assertEqual rather than (for instance) DocTestMatches.
+ str_examples = []
+
+ describe_examples = []
+
+
+class TestRaisesExceptionMatcherInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = Raises(
+ exception_matcher=MatchesException(Exception('foo')))
+ def boom_bar():
+ raise Exception('bar')
+ def boom_foo():
+ raise Exception('foo')
+ matches_matches = [boom_foo]
+ matches_mismatches = [lambda:None, boom_bar]
+
+ # Tricky to get function objects to render constantly, and the interfaces
+ # helper uses assertEqual rather than (for instance) DocTestMatches.
+ str_examples = []
+
+ describe_examples = []
+
+
+class TestRaisesBaseTypes(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def raiser(self):
+ raise KeyboardInterrupt('foo')
+
+ def test_KeyboardInterrupt_matched(self):
+ # When KeyboardInterrupt is matched, it is swallowed.
+ matcher = Raises(MatchesException(KeyboardInterrupt))
+ self.assertThat(self.raiser, matcher)
+
+ def test_KeyboardInterrupt_propogates(self):
+ # The default 'it raised' propogates KeyboardInterrupt.
+ match_keyb = Raises(MatchesException(KeyboardInterrupt))
+ def raise_keyb_from_match():
+ matcher = Raises()
+ matcher.match(self.raiser)
+ self.assertThat(raise_keyb_from_match, match_keyb)
+
+ def test_KeyboardInterrupt_match_Exception_propogates(self):
+ # If the raised exception isn't matched, and it is not a subclass of
+ # Exception, it is propogated.
+ match_keyb = Raises(MatchesException(KeyboardInterrupt))
+ def raise_keyb_from_match():
+ if sys.version_info > (2, 5):
+ matcher = Raises(MatchesException(Exception))
+ else:
+ # On Python 2.4 KeyboardInterrupt is a StandardError subclass
+ # but should propogate from less generic exception matchers
+ matcher = Raises(MatchesException(EnvironmentError))
+ matcher.match(self.raiser)
+ self.assertThat(raise_keyb_from_match, match_keyb)
+
+
+class TestRaisesConvenience(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test_exc_type(self):
+ self.assertThat(lambda: 1/0, raises(ZeroDivisionError))
+
+ def test_exc_value(self):
+ e = RuntimeError("You lose!")
+ def raiser():
+ raise e
+ self.assertThat(raiser, raises(e))
+
+
+def test_suite():
+ from unittest import TestLoader
+ return TestLoader().loadTestsFromName(__name__)
=== added file 'testtools/tests/matchers/test_filesystem.py'
--- testtools/tests/matchers/test_filesystem.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/test_filesystem.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,243 @@
+# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
+
+import os
+import shutil
+import tarfile
+import tempfile
+
+from testtools import TestCase
+from testtools.matchers import (
+ Contains,
+ DocTestMatches,
+ Equals,
+ )
+from testtools.matchers._filesystem import (
+ DirContains,
+ DirExists,
+ FileContains,
+ FileExists,
+ HasPermissions,
+ PathExists,
+ SamePath,
+ TarballContains,
+ )
+
+
+class PathHelpers(object):
+
+ def mkdtemp(self):
+ directory = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, directory)
+ return directory
+
+ def create_file(self, filename, contents=''):
+ fp = open(filename, 'w')
+ try:
+ fp.write(contents)
+ finally:
+ fp.close()
+
+ def touch(self, filename):
+ return self.create_file(filename)
+
+
+class TestPathExists(TestCase, PathHelpers):
+
+ def test_exists(self):
+ tempdir = self.mkdtemp()
+ self.assertThat(tempdir, PathExists())
+
+ def test_not_exists(self):
+ doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
+ mismatch = PathExists().match(doesntexist)
+ self.assertThat(
+ "%s does not exist." % doesntexist, Equals(mismatch.describe()))
+
+
+class TestDirExists(TestCase, PathHelpers):
+
+ def test_exists(self):
+ tempdir = self.mkdtemp()
+ self.assertThat(tempdir, DirExists())
+
+ def test_not_exists(self):
+ doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
+ mismatch = DirExists().match(doesntexist)
+ self.assertThat(
+ PathExists().match(doesntexist).describe(),
+ Equals(mismatch.describe()))
+
+ def test_not_a_directory(self):
+ filename = os.path.join(self.mkdtemp(), 'foo')
+ self.touch(filename)
+ mismatch = DirExists().match(filename)
+ self.assertThat(
+ "%s is not a directory." % filename, Equals(mismatch.describe()))
+
+
+class TestFileExists(TestCase, PathHelpers):
+
+ def test_exists(self):
+ tempdir = self.mkdtemp()
+ filename = os.path.join(tempdir, 'filename')
+ self.touch(filename)
+ self.assertThat(filename, FileExists())
+
+ def test_not_exists(self):
+ doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
+ mismatch = FileExists().match(doesntexist)
+ self.assertThat(
+ PathExists().match(doesntexist).describe(),
+ Equals(mismatch.describe()))
+
+ def test_not_a_file(self):
+ tempdir = self.mkdtemp()
+ mismatch = FileExists().match(tempdir)
+ self.assertThat(
+ "%s is not a file." % tempdir, Equals(mismatch.describe()))
+
+
+class TestDirContains(TestCase, PathHelpers):
+
+ def test_empty(self):
+ tempdir = self.mkdtemp()
+ self.assertThat(tempdir, DirContains([]))
+
+ def test_not_exists(self):
+ doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
+ mismatch = DirContains([]).match(doesntexist)
+ self.assertThat(
+ PathExists().match(doesntexist).describe(),
+ Equals(mismatch.describe()))
+
+ def test_contains_files(self):
+ tempdir = self.mkdtemp()
+ self.touch(os.path.join(tempdir, 'foo'))
+ self.touch(os.path.join(tempdir, 'bar'))
+ self.assertThat(tempdir, DirContains(['bar', 'foo']))
+
+ def test_matcher(self):
+ tempdir = self.mkdtemp()
+ self.touch(os.path.join(tempdir, 'foo'))
+ self.touch(os.path.join(tempdir, 'bar'))
+ self.assertThat(tempdir, DirContains(matcher=Contains('bar')))
+
+ def test_neither_specified(self):
+ self.assertRaises(AssertionError, DirContains)
+
+ def test_both_specified(self):
+ self.assertRaises(
+ AssertionError, DirContains, filenames=[], matcher=Contains('a'))
+
+ def test_does_not_contain_files(self):
+ tempdir = self.mkdtemp()
+ self.touch(os.path.join(tempdir, 'foo'))
+ mismatch = DirContains(['bar', 'foo']).match(tempdir)
+ self.assertThat(
+ Equals(['bar', 'foo']).match(['foo']).describe(),
+ Equals(mismatch.describe()))
+
+
+class TestFileContains(TestCase, PathHelpers):
+
+ def test_not_exists(self):
+ doesntexist = os.path.join(self.mkdtemp(), 'doesntexist')
+ mismatch = FileContains('').match(doesntexist)
+ self.assertThat(
+ PathExists().match(doesntexist).describe(),
+ Equals(mismatch.describe()))
+
+ def test_contains(self):
+ tempdir = self.mkdtemp()
+ filename = os.path.join(tempdir, 'foo')
+ self.create_file(filename, 'Hello World!')
+ self.assertThat(filename, FileContains('Hello World!'))
+
+ def test_matcher(self):
+ tempdir = self.mkdtemp()
+ filename = os.path.join(tempdir, 'foo')
+ self.create_file(filename, 'Hello World!')
+ self.assertThat(
+ filename, FileContains(matcher=DocTestMatches('Hello World!')))
+
+ def test_neither_specified(self):
+ self.assertRaises(AssertionError, FileContains)
+
+ def test_both_specified(self):
+ self.assertRaises(
+ AssertionError, FileContains, contents=[], matcher=Contains('a'))
+
+ def test_does_not_contain(self):
+ tempdir = self.mkdtemp()
+ filename = os.path.join(tempdir, 'foo')
+ self.create_file(filename, 'Goodbye Cruel World!')
+ mismatch = FileContains('Hello World!').match(filename)
+ self.assertThat(
+ Equals('Hello World!').match('Goodbye Cruel World!').describe(),
+ Equals(mismatch.describe()))
+class TestTarballContains(TestCase, PathHelpers):
+
+ def test_match(self):
+ tempdir = self.mkdtemp()
+ in_temp_dir = lambda x: os.path.join(tempdir, x)
+ self.touch(in_temp_dir('a'))
+ self.touch(in_temp_dir('b'))
+ tarball = tarfile.open(in_temp_dir('foo.tar.gz'), 'w')
+ tarball.add(in_temp_dir('a'), 'a')
+ tarball.add(in_temp_dir('b'), 'b')
+ tarball.close()
+ self.assertThat(
+ in_temp_dir('foo.tar.gz'), TarballContains(['b', 'a']))
+
+ def test_mismatch(self):
+ tempdir = self.mkdtemp()
+ in_temp_dir = lambda x: os.path.join(tempdir, x)
+ self.touch(in_temp_dir('a'))
+ self.touch(in_temp_dir('b'))
+ tarball = tarfile.open(in_temp_dir('foo.tar.gz'), 'w')
+ tarball.add(in_temp_dir('a'), 'a')
+ tarball.add(in_temp_dir('b'), 'b')
+ tarball.close()
+ mismatch = TarballContains(['d', 'c']).match(in_temp_dir('foo.tar.gz'))
+ self.assertEqual(
+ mismatch.describe(),
+ Equals(['c', 'd']).match(['a', 'b']).describe())
+
+
+class TestSamePath(TestCase, PathHelpers):
+
+ def test_same_string(self):
+ self.assertThat('foo', SamePath('foo'))
+
+ def test_relative_and_absolute(self):
+ path = 'foo'
+ abspath = os.path.abspath(path)
+ self.assertThat(path, SamePath(abspath))
+ self.assertThat(abspath, SamePath(path))
+
+ def test_real_path(self):
+ tempdir = self.mkdtemp()
+ source = os.path.join(tempdir, 'source')
+ self.touch(source)
+ target = os.path.join(tempdir, 'target')
+ try:
+ os.symlink(source, target)
+ except (AttributeError, NotImplementedError):
+ self.skip("No symlink support")
+ self.assertThat(source, SamePath(target))
+ self.assertThat(target, SamePath(source))
+
+
+class TestHasPermissions(TestCase, PathHelpers):
+
+ def test_match(self):
+ tempdir = self.mkdtemp()
+ filename = os.path.join(tempdir, 'filename')
+ self.touch(filename)
+ permissions = oct(os.stat(filename).st_mode)[-4:]
+ self.assertThat(filename, HasPermissions(permissions))
+
+
+def test_suite():
+ from unittest import TestLoader
+ return TestLoader().loadTestsFromName(__name__)
=== added file 'testtools/tests/matchers/test_higherorder.py'
--- testtools/tests/matchers/test_higherorder.py 1970-01-01 00:00:00 +0000
+++ testtools/tests/matchers/test_higherorder.py 2012-09-08 17:30:27 +0000
@@ -0,0 +1,194 @@
+# Copyright (c) 2008-2011 testtools developers. See LICENSE for details.
+
+from testtools import TestCase
+from testtools.matchers import (
+ DocTestMatches,
+ Equals,
+ LessThan,
+ MatchesStructure,
+ Mismatch,
+ NotEquals,
+ )
+from testtools.matchers._higherorder import (
+ AfterPreprocessing,
+ AllMatch,
+ Annotate,
+ AnnotatedMismatch,
+ MatchesAny,
+ MatchesAll,
+ MatchesPredicate,
+ Not,
+ )
+from testtools.tests.helpers import FullStackRunTest
+from testtools.tests.matchers.helpers import TestMatchersInterface
+
+
+class TestAllMatch(TestCase, TestMatchersInterface):
+
+ matches_matcher = AllMatch(LessThan(10))
+ matches_matches = [
+ [9, 9, 9],
+ (9, 9),
+ iter([9, 9, 9, 9, 9]),
+ ]
+ matches_mismatches = [
+ [11, 9, 9],
+ iter([9, 12, 9, 11]),
+ ]
+
+ str_examples = [
+ ("AllMatch(LessThan(12))", AllMatch(LessThan(12))),
+ ]
+
+ describe_examples = [
+ ('Differences: [\n'
+ '10 is not > 11\n'
+ '10 is not > 10\n'
+ ']',
+ [11, 9, 10],
+ AllMatch(LessThan(10))),
+ ]
+
+
+class TestAfterPreprocessing(TestCase, TestMatchersInterface):
+
+ def parity(x):
+ return x % 2
+
+ matches_matcher = AfterPreprocessing(parity, Equals(1))
+ matches_matches = [3, 5]
+ matches_mismatches = [2]
+
+ str_examples = [
+ ("AfterPreprocessing(<function parity>, Equals(1))",
+ AfterPreprocessing(parity, Equals(1))),
+ ]
+
+ describe_examples = [
+ ("1 != 0: after <function parity> on 2", 2,
+ AfterPreprocessing(parity, Equals(1))),
+ ("1 != 0", 2,
+ AfterPreprocessing(parity, Equals(1), annotate=False)),
+ ]
+
+class TestMatchersAnyInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesAny(DocTestMatches("1"), DocTestMatches("2"))
+ matches_matches = ["1", "2"]
+ matches_mismatches = ["3"]
+
+ str_examples = [(
+ "MatchesAny(DocTestMatches('1\\n'), DocTestMatches('2\\n'))",
+ MatchesAny(DocTestMatches("1"), DocTestMatches("2"))),
+ ]
+
+ describe_examples = [("""Differences: [
+Expected:
+ 1
+Got:
+ 3
+
+Expected:
+ 2
+Got:
+ 3
+
+]""",
+ "3", MatchesAny(DocTestMatches("1"), DocTestMatches("2")))]
+
+
+class TestMatchesAllInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesAll(NotEquals(1), NotEquals(2))
+ matches_matches = [3, 4]
+ matches_mismatches = [1, 2]
+
+ str_examples = [
+ ("MatchesAll(NotEquals(1), NotEquals(2))",
+ MatchesAll(NotEquals(1), NotEquals(2)))]
+
+ describe_examples = [
+ ("""Differences: [
+1 == 1
+]""",
+ 1, MatchesAll(NotEquals(1), NotEquals(2))),
+ ("1 == 1", 1,
+ MatchesAll(NotEquals(2), NotEquals(1), Equals(3), first_only=True)),
+ ]
+
+
+class TestAnnotate(TestCase, TestMatchersInterface):
+
+ matches_matcher = Annotate("foo", Equals(1))
+ matches_matches = [1]
+ matches_mismatches = [2]
+
+ str_examples = [
+ ("Annotate('foo', Equals(1))", Annotate("foo", Equals(1)))]
+
+ describe_examples = [("1 != 2: foo", 2, Annotate('foo', Equals(1)))]
+
+ def test_if_message_no_message(self):
+ # Annotate.if_message returns the given matcher if there is no
+ # message.
+ matcher = Equals(1)
+ not_annotated = Annotate.if_message('', matcher)
+ self.assertIs(matcher, not_annotated)
+
+ def test_if_message_given_message(self):
+ # Annotate.if_message returns an annotated version of the matcher if a
+ # message is provided.
+ matcher = Equals(1)
+ expected = Annotate('foo', matcher)
+ annotated = Annotate.if_message('foo', matcher)
+ self.assertThat(
+ annotated,
+ MatchesStructure.fromExample(expected, 'annotation', 'matcher'))
+
+
+class TestAnnotatedMismatch(TestCase):
+
+ run_tests_with = FullStackRunTest
+
+ def test_forwards_details(self):
+ x = Mismatch('description', {'foo': 'bar'})
+ annotated = AnnotatedMismatch("annotation", x)
+ self.assertEqual(x.get_details(), annotated.get_details())
+
+
+class TestNotInterface(TestCase, TestMatchersInterface):
+
+ matches_matcher = Not(Equals(1))
+ matches_matches = [2]
+ matches_mismatches = [1]
+
+ str_examples = [
+ ("Not(Equals(1))", Not(Equals(1))),
+ ("Not(Equals('1'))", Not(Equals('1')))]
+
+ describe_examples = [('1 matches Equals(1)', 1, Not(Equals(1)))]
+
+
+def is_even(x):
+ return x % 2 == 0
+
+
+class TestMatchesPredicate(TestCase, TestMatchersInterface):
+
+ matches_matcher = MatchesPredicate(is_even, "%s is not even")
+ matches_matches = [2, 4, 6, 8]
+ matches_mismatches = [3, 5, 7, 9]
+
+ str_examples = [
+ ("MatchesPredicate(%r, %r)" % (is_even, "%s is not even"),
+ MatchesPredicate(is_even, "%s is not even")),
+ ]
+
+ describe_examples = [
+ ('7 is not even', 7, MatchesPredicate(is_even, "%s is not even")),
+ ]
+
+
+def test_suite():
+ from unittest import TestLoader
+ return TestLoader().loadTestsFromName(__name__)
Follow ups