diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /contrib/python/PyHamcrest | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'contrib/python/PyHamcrest')
62 files changed, 3075 insertions, 0 deletions
diff --git a/contrib/python/PyHamcrest/LICENSE.txt b/contrib/python/PyHamcrest/LICENSE.txt new file mode 100644 index 0000000000..0bea089e5c --- /dev/null +++ b/contrib/python/PyHamcrest/LICENSE.txt @@ -0,0 +1,27 @@ +BSD License + +Copyright 2011 hamcrest.org +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. + +Neither the name of Hamcrest nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/contrib/python/PyHamcrest/README.rst b/contrib/python/PyHamcrest/README.rst new file mode 100644 index 0000000000..8ef46bbb91 --- /dev/null +++ b/contrib/python/PyHamcrest/README.rst @@ -0,0 +1,320 @@ +PyHamcrest +========== + +| |docs| |travis| |coveralls| |landscape| |scrutinizer| |codeclimate| +| |version| |downloads| |wheel| |supported-versions| |supported-implementations| + +.. |docs| image:: https://readthedocs.org/projects/pyhamcrest/badge/?style=flat + :target: https://pyhamcrest.readthedocs.org/ + :alt: Documentation Status + +.. |travis| image:: http://img.shields.io/travis/hamcrest/PyHamcrest/master.png?style=flat + :alt: Travis-CI Build Status + :target: https://travis-ci.org/hamcrest/PyHamcrest + +.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/hamcrest/PyHamcrest?branch=master + :alt: AppVeyor Build Status + :target: https://ci.appveyor.com/project/hamcrest/PyHamcrest + +.. |coveralls| image:: http://img.shields.io/coveralls/hamcrest/PyHamcrest/master.png?style=flat + :alt: Coverage Status + :target: https://coveralls.io/r/hamcrest/PyHamcrest + +.. |landscape| image:: https://landscape.io/github/hamcrest/PyHamcrest/master/landscape.svg?style=flat + :target: https://landscape.io/github/hamcrest/PyHamcrest/master + :alt: Code Quality Status + +.. |codeclimate| image:: https://codeclimate.com/github/hamcrest/PyHamcrest/badges/gpa.svg + :target: https://codeclimate.com/github/hamcrest/PyHamcrest + :alt: Code Climate + +.. |version| image:: http://img.shields.io/pypi/v/PyHamcrest.png?style=flat + :alt: PyPI Package latest release + :target: https://pypi.python.org/pypi/PyHamcrest + +.. |downloads| image:: http://img.shields.io/pypi/dm/PyHamcrest.png?style=flat + :alt: PyPI Package monthly downloads + :target: https://pypi.python.org/pypi/PyHamcrest + +.. |wheel| image:: https://pypip.in/wheel/PyHamcrest/badge.png?style=flat + :alt: PyPI Wheel + :target: https://pypi.python.org/pypi/PyHamcrest + +.. |supported-versions| image:: https://pypip.in/py_versions/PyHamcrest/badge.png?style=flat + :alt: Supported versions + :target: https://pypi.python.org/pypi/PyHamcrest + +.. |supported-implementations| image:: https://pypip.in/implementation/PyHamcrest/badge.png?style=flat + :alt: Supported imlementations + :target: https://pypi.python.org/pypi/PyHamcrest + +.. |scrutinizer| image:: https://img.shields.io/scrutinizer/g/hamcrest/PyHamcrest/master.png?style=flat + :alt: Scrtinizer Status + :target: https://scrutinizer-ci.com/g/hamcrest/PyHamcrest/ + + +Introduction +============ + +PyHamcrest is a framework for writing matcher objects, allowing you to +declaratively define "match" rules. There are a number of situations where +matchers are invaluable, such as UI validation, or data filtering, but it is in +the area of writing flexible tests that matchers are most commonly used. This +tutorial shows you how to use PyHamcrest for unit testing. + +When writing tests it is sometimes difficult to get the balance right between +overspecifying the test (and making it brittle to changes), and not specifying +enough (making the test less valuable since it continues to pass even when the +thing being tested is broken). Having a tool that allows you to pick out +precisely the aspect under test and describe the values it should have, to a +controlled level of precision, helps greatly in writing tests that are "just +right." Such tests fail when the behavior of the aspect under test deviates +from the expected behavior, yet continue to pass when minor, unrelated changes +to the behaviour are made. + +Installation +============ + +Hamcrest can be installed using the usual Python packaging tools. It depends on +distribute, but as long as you have a network connection when you install, the +installation process will take care of that for you. + +My first PyHamcrest test +======================== + +We'll start by writing a very simple PyUnit test, but instead of using PyUnit's +``assertEqual`` method, we'll use PyHamcrest's ``assert_that`` construct and +the standard set of matchers: + +.. code:: python + + from hamcrest import * + import unittest + + class BiscuitTest(unittest.TestCase): + def testEquals(self): + theBiscuit = Biscuit('Ginger') + myBiscuit = Biscuit('Ginger') + assert_that(theBiscuit, equal_to(myBiscuit)) + + if __name__ == '__main__': + unittest.main() + +The ``assert_that`` function is a stylized sentence for making a test +assertion. In this example, the subject of the assertion is the object +``theBiscuit``, which is the first method parameter. The second method +parameter is a matcher for ``Biscuit`` objects, here a matcher that checks one +object is equal to another using the Python ``==`` operator. The test passes +since the ``Biscuit`` class defines an ``__eq__`` method. + +If you have more than one assertion in your test you can include an identifier +for the tested value in the assertion: + +.. code:: python + + assert_that(theBiscuit.getChocolateChipCount(), equal_to(10), 'chocolate chips') + assert_that(theBiscuit.getHazelnutCount(), equal_to(3), 'hazelnuts') + +As a convenience, assert_that can also be used to verify a boolean condition: + +.. code:: python + + assert_that(theBiscuit.isCooked(), 'cooked') + +This is equivalent to the ``assert_`` method of unittest.TestCase, but because +it's a standalone function, it offers greater flexibility in test writing. + + +Predefined matchers +=================== + +PyHamcrest comes with a library of useful matchers: + +* Object + + * ``equal_to`` - match equal object + * ``has_length`` - match ``len()`` + * ``has_property`` - match value of property with given name + * ``has_properties`` - match an object that has all of the given properties. + * ``has_string`` - match ``str()`` + * ``instance_of`` - match object type + * ``none``, ``not_none`` - match ``None``, or not ``None`` + * ``same_instance`` - match same object + * ``calling, raises`` - wrap a method call and assert that it raises an exception + +* Number + + * ``close_to`` - match number close to a given value + * ``greater_than``, ``greater_than_or_equal_to``, ``less_than``, + ``less_than_or_equal_to`` - match numeric ordering + +* Text + + * ``contains_string`` - match part of a string + * ``ends_with`` - match the end of a string + * ``equal_to_ignoring_case`` - match the complete string but ignore case + * ``equal_to_ignoring_whitespace`` - match the complete string but ignore extra whitespace + * ``matches_regexp`` - match a regular expression in a string + * ``starts_with`` - match the beginning of a string + * ``string_contains_in_order`` - match parts of a string, in relative order + +* Logical + + * ``all_of`` - ``and`` together all matchers + * ``any_of`` - ``or`` together all matchers + * ``anything`` - match anything, useful in composite matchers when you don't care about a particular value + * ``is_not`` - negate the matcher + +* Sequence + + * ``contains`` - exactly match the entire sequence + * ``contains_inanyorder`` - match the entire sequence, but in any order + * ``has_item`` - match if given item appears in the sequence + * ``has_items`` - match if all given items appear in the sequence, in any order + * ``is_in`` - match if item appears in the given sequence + * ``only_contains`` - match if sequence's items appear in given list + * ``empty`` - match if the sequence is empty + +* Dictionary + + * ``has_entries`` - match dictionary with list of key-value pairs + * ``has_entry`` - match dictionary containing a key-value pair + * ``has_key`` - match dictionary with a key + * ``has_value`` - match dictionary with a value + +* Decorator + + * ``calling`` - wrap a callable in a deffered object, for subsequent matching on calling behaviour + * ``raises`` - Ensure that a deferred callable raises as expected + * ``described_as`` - give the matcher a custom failure description + * ``is_`` - decorator to improve readability - see `Syntactic sugar` below + +The arguments for many of these matchers accept not just a matching value, but +another matcher, so matchers can be composed for greater flexibility. For +example, ``only_contains(less_than(5))`` will match any sequence where every +item is less than 5. + + +Syntactic sugar +=============== + +PyHamcrest strives to make your tests as readable as possible. For example, the +``is_`` matcher is a wrapper that doesn't add any extra behavior to the +underlying matcher. The following assertions are all equivalent: + +.. code:: python + + assert_that(theBiscuit, equal_to(myBiscuit)) + assert_that(theBiscuit, is_(equal_to(myBiscuit))) + assert_that(theBiscuit, is_(myBiscuit)) + +The last form is allowed since ``is_(value)`` wraps most non-matcher arguments +with ``equal_to``. But if the argument is a type, it is wrapped with +``instance_of``, so the following are also equivalent: + +.. code:: python + + assert_that(theBiscuit, instance_of(Biscuit)) + assert_that(theBiscuit, is_(instance_of(Biscuit))) + assert_that(theBiscuit, is_(Biscuit)) + +*Note that PyHamcrest's ``is_`` matcher is unrelated to Python's ``is`` +operator. The matcher for object identity is ``same_instance``.* + + +Writing custom matchers +======================= + +PyHamcrest comes bundled with lots of useful matchers, but you'll probably find +that you need to create your own from time to time to fit your testing needs. +This commonly occurs when you find a fragment of code that tests the same set +of properties over and over again (and in different tests), and you want to +bundle the fragment into a single assertion. By writing your own matcher you'll +eliminate code duplication and make your tests more readable! + +Let's write our own matcher for testing if a calendar date falls on a Saturday. +This is the test we want to write: + +.. code:: python + + def testDateIsOnASaturday(self): + d = datetime.date(2008, 04, 26) + assert_that(d, is_(on_a_saturday())) + +And here's the implementation: + +.. code:: python + + from hamcrest.core.base_matcher import BaseMatcher + from hamcrest.core.helpers.hasmethod import hasmethod + + class IsGivenDayOfWeek(BaseMatcher): + + def __init__(self, day): + self.day = day # Monday is 0, Sunday is 6 + + def _matches(self, item): + if not hasmethod(item, 'weekday'): + return False + return item.weekday() == self.day + + def describe_to(self, description): + day_as_string = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', + 'Friday', 'Saturday', 'Sunday'] + description.append_text('calendar date falling on ') \ + .append_text(day_as_string[self.day]) + + def on_a_saturday(): + return IsGivenDayOfWeek(5) + +For our Matcher implementation we implement the ``_matches`` method - which +calls the ``weekday`` method after confirming that the argument (which may not +be a date) has such a method - and the ``describe_to`` method - which is used +to produce a failure message when a test fails. Here's an example of how the +failure message looks: + +.. code:: python + + assert_that(datetime.date(2008, 04, 06), is_(on_a_saturday())) + +fails with the message:: + + AssertionError: + Expected: is calendar date falling on Saturday + got: <2008-04-06> + +Let's say this matcher is saved in a module named ``isgivendayofweek``. We +could use it in our test by importing the factory function ``on_a_saturday``: + +.. code:: python + + from hamcrest import * + import unittest + from isgivendayofweek import on_a_saturday + + class DateTest(unittest.TestCase): + def testDateIsOnASaturday(self): + d = datetime.date(2008, 04, 26) + assert_that(d, is_(on_a_saturday())) + + if __name__ == '__main__': + unittest.main() + +Even though the ``on_a_saturday`` function creates a new matcher each time it +is called, you should not assume this is the only usage pattern for your +matcher. Therefore you should make sure your matcher is stateless, so a single +instance can be reused between matches. + + +More resources +============== + +* Documentation_ +* Package_ +* Sources_ +* Hamcrest_ + +.. _Documentation: http://readthedocs.org/docs/pyhamcrest/en/V1.8.2/ +.. _Package: http://pypi.python.org/pypi/PyHamcrest +.. _Sources: https://github.com/hamcrest/PyHamcrest +.. _Hamcrest: http://hamcrest.org diff --git a/contrib/python/PyHamcrest/src/hamcrest/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/__init__.py new file mode 100644 index 0000000000..3a751d44f6 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/__init__.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import +from hamcrest.core import * +from hamcrest.library import * + +__version__ = "1.9.0" +__author__ = "Chris Rose" +__copyright__ = "Copyright 2015 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/core/__init__.py new file mode 100644 index 0000000000..779fa72345 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/__init__.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import +from hamcrest.core.assert_that import assert_that +from hamcrest.core.core import * + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/assert_that.py b/contrib/python/PyHamcrest/src/hamcrest/core/assert_that.py new file mode 100644 index 0000000000..b38bc243ca --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/assert_that.py @@ -0,0 +1,64 @@ +from __future__ import absolute_import +from hamcrest.core.matcher import Matcher +from hamcrest.core.string_description import StringDescription + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" +# unittest integration; hide these frames from tracebacks +__unittest = True +# py.test integration; hide these frames from tracebacks +__tracebackhide__ = True + +def assert_that(arg1, arg2=None, arg3=''): + """Asserts that actual value satisfies matcher. (Can also assert plain + boolean condition.) + + :param actual: The object to evaluate as the actual value. + :param matcher: The matcher to satisfy as the expected condition. + :param reason: Optional explanation to include in failure description. + + ``assert_that`` passes the actual value to the matcher for evaluation. If + the matcher is not satisfied, an exception is thrown describing the + mismatch. + + ``assert_that`` is designed to integrate well with PyUnit and other unit + testing frameworks. The exception raised for an unmet assertion is an + :py:exc:`AssertionError`, which PyUnit reports as a test failure. + + With a different set of parameters, ``assert_that`` can also verify a + boolean condition: + + .. function:: assert_that(assertion[, reason]) + + :param assertion: Boolean condition to verify. + :param reason: Optional explanation to include in failure description. + + This is equivalent to the :py:meth:`~unittest.TestCase.assertTrue` method + of :py:class:`unittest.TestCase`, but offers greater flexibility in test + writing by being a standalone function. + + """ + if isinstance(arg2, Matcher): + _assert_match(actual=arg1, matcher=arg2, reason=arg3) + else: + _assert_bool(assertion=arg1, reason=arg2) + + +def _assert_match(actual, matcher, reason): + if not matcher.matches(actual): + description = StringDescription() + description.append_text(reason) \ + .append_text('\nExpected: ') \ + .append_description_of(matcher) \ + .append_text('\n but: ') + matcher.describe_mismatch(actual, description) + description.append_text('\n') + raise AssertionError(description) + + +def _assert_bool(assertion, reason=None): + if not assertion: + if not reason: + reason = 'Assertion failed' + raise AssertionError(reason) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/base_description.py b/contrib/python/PyHamcrest/src/hamcrest/core/base_description.py new file mode 100644 index 0000000000..8c7c51364d --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/base_description.py @@ -0,0 +1,92 @@ +from __future__ import absolute_import +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +import warnings +import six + +from hamcrest.core.description import Description +from hamcrest.core.selfdescribingvalue import SelfDescribingValue +from hamcrest.core.helpers.hasmethod import hasmethod + +class BaseDescription(Description): + """Base class for all :py:class:`~hamcrest.core.description.Description` + implementations. + + """ + + def append_text(self, text): + self.append(text) + return self + + def append_description_of(self, value): + if hasmethod(value, 'describe_to'): + value.describe_to(self) + elif six.PY3 and isinstance(value, six.text_type): + self.append(repr(value)) + elif six.PY2 and isinstance(value, six.binary_type): + self.append_string_in_python_syntax(value) + elif isinstance(value, six.text_type): + self.append_string_in_python_syntax(value) + else: + description = str(value) + if description[:1] == '<' and description[-1:] == '>': + self.append(description) + else: + self.append('<') + self.append(description) + self.append('>') + return self + + def append_value(self, value): + warnings.warn('Call append_description_of instead of append_value', + DeprecationWarning) + if isinstance(value, str): + self.append_string_in_python_syntax(value) + else: + self.append('<') + self.append(str(value)) + self.append('>') + return self + + def append_value_list(self, start, separator, end, list): + warnings.warn('Call append_list instead of append_value_list', + DeprecationWarning) + return self.append_list(start, separator, end, + map(SelfDescribingValue, list)) + + def append_list(self, start, separator, end, list): + separate = False + + self.append(start) + for item in list: + if separate: + self.append(separator) + self.append_description_of(item) + separate = True + self.append(end) + return self + + def append(self, string): + """Append the string to the description.""" + raise NotImplementedError('append') + + def append_string_in_python_syntax(self, string): + self.append("'") + for ch in string: + self.append(character_in_python_syntax(ch)) + self.append("'") + + +def character_in_python_syntax(ch): + if ch == "'": + return "\'" + elif ch == '\n': + return '\\n' + elif ch == '\r': + return '\\r' + elif ch == '\t': + return '\\t' + else: + return ch diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/base_matcher.py b/contrib/python/PyHamcrest/src/hamcrest/core/base_matcher.py new file mode 100644 index 0000000000..951e2a7b95 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/base_matcher.py @@ -0,0 +1,34 @@ +from __future__ import absolute_import +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.matcher import Matcher +from hamcrest.core.string_description import tostring + + +class BaseMatcher(Matcher): + """Base class for all :py:class:`~hamcrest.core.matcher.Matcher` + implementations. + + Most implementations can just implement :py:obj:`_matches`, leaving the + handling of any mismatch description to the ``matches`` method. But if it + makes more sense to generate the mismatch description during the matching, + override :py:meth:`~hamcrest.core.matcher.Matcher.matches` instead. + + """ + + def __str__(self): + return tostring(self) + + def _matches(self, item): + raise NotImplementedError('_matches') + + def matches(self, item, mismatch_description=None): + match_result = self._matches(item) + if not match_result and mismatch_description: + self.describe_mismatch(item, mismatch_description) + return match_result + + def describe_mismatch(self, item, mismatch_description): + mismatch_description.append_text('was ').append_description_of(item) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/compat.py b/contrib/python/PyHamcrest/src/hamcrest/core/compat.py new file mode 100644 index 0000000000..2c6d1fa74c --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/compat.py @@ -0,0 +1,19 @@ +__author__ = "Per Fagrell" +__copyright__ = "Copyright 2013 hamcrest.org" +__license__ = "BSD, see License.txt" + +__all__ = ['is_callable'] + +import sys + +# callable was not part of py3k until 3.2, so we create this +# generic is_callable to use callable if possible, otherwise +# we use generic homebrew. +if sys.version_info[0] == 3 and sys.version_info[1] < 2: + def is_callable(function): + """Return whether the object is callable (i.e., some kind of function).""" + if function is None: + return False + return any("__call__" in klass.__dict__ for klass in type(function).__mro__) +else: + is_callable = callable diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/__init__.py new file mode 100644 index 0000000000..38e93e249a --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/__init__.py @@ -0,0 +1,18 @@ +from __future__ import absolute_import +"""Fundamental matchers of objects and values, and composite matchers.""" + +from hamcrest.core.core.allof import all_of +from hamcrest.core.core.anyof import any_of +from hamcrest.core.core.described_as import described_as +from hamcrest.core.core.is_ import is_ +from hamcrest.core.core.isanything import anything +from hamcrest.core.core.isequal import equal_to +from hamcrest.core.core.isinstanceof import instance_of +from hamcrest.core.core.isnone import none, not_none +from hamcrest.core.core.isnot import is_not, not_ +from hamcrest.core.core.issame import same_instance +from hamcrest.core.core.raises import calling, raises + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/allof.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/allof.py new file mode 100644 index 0000000000..35c5d0bfeb --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/allof.py @@ -0,0 +1,44 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class AllOf(BaseMatcher): + + def __init__(self, *matchers): + self.matchers = matchers + + def matches(self, item, mismatch_description=None): + for matcher in self.matchers: + if not matcher.matches(item): + if mismatch_description: + mismatch_description.append_description_of(matcher) \ + .append_text(' ') + matcher.describe_mismatch(item, mismatch_description) + return False + return True + + def describe_mismatch(self, item, mismatch_description): + self.matches(item, mismatch_description) + + def describe_to(self, description): + description.append_list('(', ' and ', ')', self.matchers) + + +def all_of(*items): + """Matches if all of the given matchers evaluate to ``True``. + + :param matcher1,...: A comma-separated list of matchers. + + The matchers are evaluated from left to right using short-circuit + evaluation, so evaluation stops as soon as a matcher returns ``False``. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + """ + return AllOf(*[wrap_matcher(item) for item in items]) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/anyof.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/anyof.py new file mode 100644 index 0000000000..7a2bfc6627 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/anyof.py @@ -0,0 +1,37 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class AnyOf(BaseMatcher): + + def __init__(self, *matchers): + self.matchers = matchers + + def _matches(self, item): + for matcher in self.matchers: + if matcher.matches(item): + return True + return False + + def describe_to(self, description): + description.append_list('(', ' or ', ')', self.matchers) + + +def any_of(*items): + """Matches if any of the given matchers evaluate to ``True``. + + :param matcher1,...: A comma-separated list of matchers. + + The matchers are evaluated from left to right using short-circuit + evaluation, so evaluation stops as soon as a matcher returns ``True``. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + """ + return AnyOf(*[wrap_matcher(item) for item in items]) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/described_as.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/described_as.py new file mode 100644 index 0000000000..93b4d6ac45 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/described_as.py @@ -0,0 +1,48 @@ +from hamcrest.core.base_matcher import BaseMatcher +import re + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +ARG_PATTERN = re.compile('%([0-9]+)') + + +class DescribedAs(BaseMatcher): + + def __init__(self, description_template, matcher, *values): + self.template = description_template + self.matcher = matcher + self.values = values + + def matches(self, item, mismatch_description=None): + return self.matcher.matches(item, mismatch_description) + + def describe_mismatch(self, item, mismatch_description): + self.matcher.describe_mismatch(item, mismatch_description) + + def describe_to(self, description): + text_start = 0 + for match in re.finditer(ARG_PATTERN, self.template): + description.append_text(self.template[text_start:match.start()]) + arg_index = int(match.group()[1:]) + description.append_description_of(self.values[arg_index]) + text_start = match.end() + + if text_start < len(self.template): + description.append_text(self.template[text_start:]) + + +def described_as(description, matcher, *values): + """Adds custom failure description to a given matcher. + + :param description: Overrides the matcher's description. + :param matcher: The matcher to satisfy. + :param value1,...: Optional comma-separated list of substitution values. + + The description may contain substitution placeholders %0, %1, etc. These + will be replaced by any values that follow the matcher. + + """ + return DescribedAs(description, matcher, *values) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/is_.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/is_.py new file mode 100644 index 0000000000..ba11a762ae --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/is_.py @@ -0,0 +1,76 @@ +from __future__ import absolute_import +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.wrap_matcher import wrap_matcher, is_matchable_type +from .isinstanceof import instance_of + + +class Is(BaseMatcher): + + def __init__(self, matcher): + self.matcher = matcher + + def matches(self, item, mismatch_description=None): + return self.matcher.matches(item, mismatch_description) + + def describe_mismatch(self, item, mismatch_description): + return self.matcher.describe_mismatch(item, mismatch_description) + + def describe_to(self, description): + description.append_description_of(self.matcher) + + +def wrap_value_or_type(x): + if is_matchable_type(x): + return instance_of(x) + else: + return wrap_matcher(x) + + +def is_(x): + """Decorates another matcher, or provides shortcuts to the frequently used + ``is(equal_to(x))`` and ``is(instance_of(x))``. + + :param x: The matcher to satisfy, or a type for + :py:func:`~hamcrest.core.core.isinstanceof.instance_of` matching, or an + expected value for :py:func:`~hamcrest.core.core.isequal.equal_to` + matching. + + This matcher compares the evaluated object to the given matcher. + + .. note:: + + PyHamcrest's ``is_`` matcher is unrelated to Python's ``is`` operator. + The matcher for object identity is + :py:func:`~hamcrest.core.core.issame.same_instance`. + + If the ``x`` argument is a matcher, its behavior is retained, but the test + may be more expressive. For example:: + + assert_that(value, less_than(5)) + assert_that(value, is_(less_than(5))) + + If the ``x`` argument is a type, it is wrapped in an + :py:func:`~hamcrest.core.core.isinstanceof.instance_of` matcher. This makes + the following statements equivalent:: + + assert_that(cheese, instance_of(Cheddar)) + assert_that(cheese, is_(instance_of(Cheddar))) + assert_that(cheese, is_(Cheddar)) + + Otherwise, if the ``x`` argument is not a matcher, it is wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher. This makes the + following statements equivalent:: + + assert_that(cheese, equal_to(smelly)) + assert_that(cheese, is_(equal_to(smelly))) + assert_that(cheese, is_(smelly)) + + Choose the style that makes your expression most readable. This will vary + depending on context. + + """ + return Is(wrap_value_or_type(x)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/isanything.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/isanything.py new file mode 100644 index 0000000000..f916811bc9 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/isanything.py @@ -0,0 +1,31 @@ +from hamcrest.core.base_matcher import BaseMatcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsAnything(BaseMatcher): + + def __init__(self, description): + self.description = description + if not description: + self.description = 'ANYTHING' + + def _matches(self, item): + return True + + def describe_to(self, description): + description.append_text(self.description) + + +def anything(description=None): + """Matches anything. + + :param description: Optional string used to describe this matcher. + + This matcher always evaluates to ``True``. Specify this in composite + matchers when the value of a particular element is unimportant. + + """ + return IsAnything(description) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/isequal.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/isequal.py new file mode 100644 index 0000000000..119fd58a48 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/isequal.py @@ -0,0 +1,32 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.matcher import Matcher + + +class IsEqual(BaseMatcher): + + def __init__(self, equals): + self.object = equals + + def _matches(self, item): + return item == self.object + + def describe_to(self, description): + nested_matcher = isinstance(self.object, Matcher) + if nested_matcher: + description.append_text('<') + description.append_description_of(self.object) + if nested_matcher: + description.append_text('>') + + +def equal_to(obj): + """Matches if object is equal to a given object. + + :param obj: The object to compare against as the expected value. + + This matcher compares the evaluated object to ``obj`` for equality.""" + return IsEqual(obj) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/isinstanceof.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/isinstanceof.py new file mode 100644 index 0000000000..f8eb4a2fd1 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/isinstanceof.py @@ -0,0 +1,43 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.wrap_matcher import is_matchable_type + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +import types + +class IsInstanceOf(BaseMatcher): + + def __init__(self, expected_type): + if not is_matchable_type(expected_type): + raise TypeError('IsInstanceOf requires type or a tuple of classes and types') + self.expected_type = expected_type + + def _matches(self, item): + return isinstance(item, self.expected_type) + + def describe_to(self, description): + try: + type_description = self.expected_type.__name__ + except AttributeError: + type_description = "one of %s" % ",".join(str(e) for e in self.expected_type) + description.append_text('an instance of ') \ + .append_text(type_description) + + +def instance_of(atype): + """Matches if object is an instance of, or inherits from, a given type. + + :param atype: The type to compare against as the expected type or a tuple + of types. + + This matcher checks whether the evaluated object is an instance of + ``atype`` or an instance of any class that inherits from ``atype``. + + Example:: + + instance_of(str) + + """ + return IsInstanceOf(atype) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/isnone.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/isnone.py new file mode 100644 index 0000000000..511fd5ae46 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/isnone.py @@ -0,0 +1,26 @@ +from __future__ import absolute_import +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher +from .isnot import is_not + + +class IsNone(BaseMatcher): + + def _matches(self, item): + return item is None + + def describe_to(self, description): + description.append_text('None') + + +def none(): + """Matches if object is ``None``.""" + return IsNone() + + +def not_none(): + """Matches if object is not ``None``.""" + return is_not(none()) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/isnot.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/isnot.py new file mode 100644 index 0000000000..7567e6f325 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/isnot.py @@ -0,0 +1,57 @@ +from __future__ import absolute_import +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher, Matcher +from hamcrest.core.helpers.wrap_matcher import wrap_matcher, is_matchable_type +from .isequal import equal_to +from .isinstanceof import instance_of + + +class IsNot(BaseMatcher): + + def __init__(self, matcher): + self.matcher = matcher + + def _matches(self, item): + return not self.matcher.matches(item) + + def describe_to(self, description): + description.append_text('not ').append_description_of(self.matcher) + + +def wrap_value_or_type(x): + if is_matchable_type(x): + return instance_of(x) + else: + return wrap_matcher(x) + + +def is_not(match): + """Inverts the given matcher to its logical negation. + + :param match: The matcher to negate. + + This matcher compares the evaluated object to the negation of the given + matcher. If the ``match`` argument is not a matcher, it is implicitly + wrapped in an :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to + check for equality, and thus matches for inequality. + + Examples:: + + assert_that(cheese, is_not(equal_to(smelly))) + assert_that(cheese, is_not(smelly)) + + """ + return IsNot(wrap_value_or_type(match)) + +def not_(match): + """Alias of :py:func:`is_not` for better readability of negations. + + Examples:: + + assert_that(alist, not_(has_item(item))) + + """ + return is_not(match) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/issame.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/issame.py new file mode 100644 index 0000000000..b1f85427d7 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/issame.py @@ -0,0 +1,39 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher + + +class IsSame(BaseMatcher): + + def __init__(self, object): + self.object = object + + def _matches(self, item): + return item is self.object + + def describe_to(self, description): + description.append_text('same instance as ') \ + .append_text(hex(id(self.object))) \ + .append_text(' ') \ + .append_description_of(self.object) + + def describe_mismatch(self, item, mismatch_description): + mismatch_description.append_text('was ') + if item is not None: + mismatch_description.append_text(hex(id(item))) \ + .append_text(' ') + mismatch_description.append_description_of(item) + + +def same_instance(obj): + """Matches if evaluated object is the same instance as a given object. + + :param obj: The object to compare against as the expected value. + + This matcher invokes the ``is`` identity operator to determine if the + evaluated object is the the same object as ``obj``. + + """ + return IsSame(obj) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/core/raises.py b/contrib/python/PyHamcrest/src/hamcrest/core/core/raises.py new file mode 100644 index 0000000000..878e2af87a --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/core/raises.py @@ -0,0 +1,121 @@ +from weakref import ref +import re +import sys +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.wrap_matcher import wrap_matcher +from hamcrest.core.compat import is_callable + +__author__ = "Per Fagrell" +__copyright__ = "Copyright 2013 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class Raises(BaseMatcher): + def __init__(self, expected, pattern=None, matcher=None): + self.pattern = pattern + self.expected = expected + self.actual = None + self.function = None + self.matcher = matcher + self.actual_return_value = None + + def _matches(self, function): + if not is_callable(function): + return False + + self.function = ref(function) + return self._call_function(function) + + def _call_function(self, function): + self.actual = None + try: + self.actual_return_value = function() + except Exception: + self.actual = sys.exc_info()[1] + + if isinstance(self.actual, self.expected): + if self.pattern is not None: + return ( + re.search(self.pattern, str(self.actual)) is not None + and (self.matcher is None or self.matcher.matches(self.actual)) + ) + return self.matcher is None or self.matcher.matches(self.actual) + return False + + def describe_to(self, description): + description.append_text('Expected a callable raising %s' % self.expected) + if self.matcher is not None: + description.append_text("\n and ") + description.append_description_of(self.matcher) + + def describe_mismatch(self, item, description): + if not is_callable(item): + description.append_text('%s is not callable' % item) + return + + function = None if self.function is None else self.function() + if function is None or function is not item: + self.function = ref(item) + if not self._call_function(item): + return + + if self.actual is None: + description.append_text('No exception raised and actual return value = ') + description.append_value(self.actual_return_value) + elif isinstance(self.actual, self.expected): + if self.pattern is not None: + description.append_text('Correct assertion type raised, but the expected pattern ("%s") not found.' % self.pattern) + description.append_text('\n message was: "%s"' % str(self.actual)) + if self.matcher is not None: + description.append_text("\nAdditional exception matcher: ") + self.matcher.describe_mismatch(self.actual, description) + else: + description.append_text('%s was raised instead' % type(self.actual)) + + +def raises(exception, pattern=None, matcher=None): + """Matches if the called function raised the expected exception. + + :param exception: The class of the expected exception + :param pattern: Optional regular expression to match exception message. + + Expects the actual to be wrapped by using :py:func:`~hamcrest.core.core.raises.calling`, + or a callable taking no arguments. + Optional argument pattern should be a string containing a regular expression. If provided, + the string representation of the actual exception - e.g. `str(actual)` - must match pattern. + + Examples:: + + assert_that(calling(int).with_args('q'), raises(TypeError)) + assert_that(calling(parse, broken_input), raises(ValueError)) + """ + return Raises(exception, pattern, matcher) + + +class DeferredCallable(object): + def __init__(self, func): + self.func = func + self.args = tuple() + self.kwargs = {} + + def __call__(self): + return self.func(*self.args, **self.kwargs) + + def with_args(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + return self + + +def calling(func): + """Wrapper for function call that delays the actual execution so that + :py:func:`~hamcrest.core.core.raises.raises` matcher can catch any thrown exception. + + :param func: The function or method to be called + + The arguments can be provided with a call to the `with_args` function on the returned + object:: + + calling(my_method).with_args(arguments, and_='keywords') + """ + return DeferredCallable(func) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/description.py b/contrib/python/PyHamcrest/src/hamcrest/core/description.py new file mode 100644 index 0000000000..6201b7f70f --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/description.py @@ -0,0 +1,58 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class Description(object): + """A description of a :py:class:`~hamcrest.core.matcher.Matcher`. + + A :py:class:`~hamcrest.core.matcher.Matcher` will describe itself to a + description which can later be used for reporting. + + """ + + def append_text(self, text): + """Appends some plain text to the description. + + :returns: ``self``, for chaining + + """ + raise NotImplementedError('append_text') + + def append_description_of(self, value): + """Appends description of given value to this description. + + If the value implements + :py:meth:`~hamcrest.core.selfdescribing.SelfDescribing.describe_to`, + then it will be used. + + :returns: ``self``, for chaining + + """ + raise NotImplementedError('append_description_of') + + def append_value(self, value): + """Appends an arbitary value to the description. + + **Deprecated:** Call + :py:meth:`~hamcrest.core.description.Description.append_description_of` + instead. + + :returns: ``self``, for chaining + + """ + raise NotImplementedError('append_value') + + def append_list(self, start, separator, end, list): + """Appends a list of objects to the description. + + :param start: String that will begin the list description. + :param separator: String that will separate each object in the + description. + :param end: String that will end the list description. + :param list: List of objects to be described. + + :returns: ``self``, for chaining + + """ + raise NotImplementedError('append_list') diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/helpers/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/core/helpers/__init__.py new file mode 100644 index 0000000000..61cb82d43b --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/helpers/__init__.py @@ -0,0 +1,5 @@ +"""Utilities for writing Matchers.""" + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/helpers/hasmethod.py b/contrib/python/PyHamcrest/src/hamcrest/core/helpers/hasmethod.py new file mode 100644 index 0000000000..a1f3bfb154 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/helpers/hasmethod.py @@ -0,0 +1,12 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +def hasmethod(obj, methodname): + """Does ``obj`` have a method named ``methodname``?""" + + if not hasattr(obj, methodname): + return False + method = getattr(obj, methodname) + return callable(method) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/helpers/wrap_matcher.py b/contrib/python/PyHamcrest/src/hamcrest/core/helpers/wrap_matcher.py new file mode 100644 index 0000000000..a5b506fb39 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/helpers/wrap_matcher.py @@ -0,0 +1,36 @@ +import six + +from hamcrest.core.base_matcher import Matcher +from hamcrest.core.core.isequal import equal_to + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +import types + +def wrap_matcher(x): + """Wraps argument in a matcher, if necessary. + + :returns: the argument as-is if it is already a matcher, otherwise wrapped + in an :py:func:`~hamcrest.core.core.isequal.equal_to` matcher. + + """ + if isinstance(x, Matcher): + return x + else: + return equal_to(x) + +def is_matchable_type(expected_type): + if isinstance(expected_type, type): + return True + + if isinstance(expected_type, six.class_types): + return True + + if isinstance(expected_type, tuple) and \ + expected_type and \ + all(map(is_matchable_type, expected_type)): + return True + + return False diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/matcher.py b/contrib/python/PyHamcrest/src/hamcrest/core/matcher.py new file mode 100644 index 0000000000..81ee27c6d9 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/matcher.py @@ -0,0 +1,52 @@ +from __future__ import absolute_import +from .selfdescribing import SelfDescribing + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class Matcher(SelfDescribing): + """A matcher over acceptable values. + + A matcher is able to describe itself to give feedback when it fails. + + Matcher implementations should *not* directly implement this protocol. + Instead, *extend* the :py:class:`~hamcrest.core.base_matcher.BaseMatcher` + class, which will ensure that the + :py:class:`~hamcrest.core.matcher.Matcher` API can grow to support new + features and remain compatible with all + :py:class:`~hamcrest.core.matcher.Matcher` implementations. + + """ + + def matches(self, item, mismatch_description=None): + """Evaluates the matcher for argument item. + + If a mismatch is detected and argument ``mismatch_description`` is + provided, it will generate a description of why the matcher has not + accepted the item. + + :param item: The object against which the matcher is evaluated. + :returns: ``True`` if ``item`` matches, otherwise ``False``. + + """ + raise NotImplementedError('matches') + + def describe_mismatch(self, item, mismatch_description): + """Generates a description of why the matcher has not accepted the + item. + + The description will be part of a larger description of why a matching + failed, so it should be concise. + + This method assumes that ``matches(item)`` is ``False``, but will not + check this. + + :param item: The item that the + :py:class:`~hamcrest.core.matcher.Matcher` has rejected. + :param mismatch_description: The description to be built or appended + to. + + """ + raise NotImplementedError('describe_mismatch') diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/selfdescribing.py b/contrib/python/PyHamcrest/src/hamcrest/core/selfdescribing.py new file mode 100644 index 0000000000..e77b0e0fdb --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/selfdescribing.py @@ -0,0 +1,18 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class SelfDescribing(object): + """The ability of an object to describe itself.""" + + def describe_to(self, description): + """Generates a description of the object. + + The description may be part of a description of a larger object of + which this is just a component, so it should be worded appropriately. + + :param description: The description to be built or appended to. + + """ + raise NotImplementedError('describe_to') diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/selfdescribingvalue.py b/contrib/python/PyHamcrest/src/hamcrest/core/selfdescribingvalue.py new file mode 100644 index 0000000000..dfa4e3a20e --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/selfdescribingvalue.py @@ -0,0 +1,27 @@ +from hamcrest.core.selfdescribing import SelfDescribing + +import warnings + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class SelfDescribingValue(SelfDescribing): + """Wrap any value in a ``SelfDescribingValue`` to satisfy the + :py:class:`~hamcrest.core.selfdescribing.SelfDescribing` interface. + + **Deprecated:** No need for this class now that + :py:meth:`~hamcrest.core.description.Description.append_description_of` + handles any type of value. + + """ + + def __init__(self, value): + warnings.warn('SelfDescribingValue no longer needed', + DeprecationWarning) + self.value = value + + def describe_to(self, description): + """Generates a description of the value.""" + description.append_value(self.value) diff --git a/contrib/python/PyHamcrest/src/hamcrest/core/string_description.py b/contrib/python/PyHamcrest/src/hamcrest/core/string_description.py new file mode 100644 index 0000000000..7626bf91e8 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/core/string_description.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import + +import codecs +import six + +from .base_description import BaseDescription + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +def tostring(selfdescribing): + """Returns the description of a + :py:class:`~hamcrest.core.selfdescribing.SelfDescribing` object as a + string. + + :param selfdescribing: The object to be described. + :returns: The description of the object. + """ + return str(StringDescription().append_description_of(selfdescribing)) + + +class StringDescription(BaseDescription): + """A :py:class:`~hamcrest.core.description.Description` that is stored as a + string. + """ + + def __init__(self): + self.__out_list = [] + + def __str__(self): + """Returns the description.""" + return ''.join(self.__out_list) + + def append(self, string): + self.__out_list.append(six.text_type(string)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/library/__init__.py new file mode 100644 index 0000000000..a5a7963521 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/__init__.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import +"""Library of Matcher implementations.""" + +from hamcrest.core import * +from hamcrest.library.collection import * +from hamcrest.library.integration import * +from hamcrest.library.number import * +from hamcrest.library.object import * +from hamcrest.library.text import * + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +__all__ = [ + 'has_entry', + 'has_entries', + 'has_key', + 'has_value', + 'is_in', + 'empty', + 'has_item', + 'has_items', + 'contains_inanyorder', + 'contains', + 'only_contains', + 'match_equality', + 'matches_regexp', + 'close_to', + 'greater_than', + 'greater_than_or_equal_to', + 'less_than', + 'less_than_or_equal_to', + 'has_length', + 'has_property', + 'has_properties', + 'has_string', + 'equal_to_ignoring_case', + 'equal_to_ignoring_whitespace', + 'contains_string', + 'ends_with', + 'starts_with', + 'string_contains_in_order', +] diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/__init__.py new file mode 100644 index 0000000000..2f89987788 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/__init__.py @@ -0,0 +1,16 @@ +"""Matchers of collections.""" +from __future__ import absolute_import +from .isdict_containing import has_entry +from .isdict_containingentries import has_entries +from .isdict_containingkey import has_key +from .isdict_containingvalue import has_value +from .isin import is_in +from .issequence_containing import has_item, has_items +from .issequence_containinginanyorder import contains_inanyorder +from .issequence_containinginorder import contains +from .issequence_onlycontaining import only_contains +from .is_empty import empty + +__author__ = "Chris Rose" +__copyright__ = "Copyright 2013 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/is_empty.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/is_empty.py new file mode 100644 index 0000000000..bc99b63373 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/is_empty.py @@ -0,0 +1,35 @@ +from hamcrest.core.base_matcher import BaseMatcher + +__author__ = "Chris Rose" +__copyright__ = "Copyright 2012 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsEmpty(BaseMatcher): + + def matches(self, item, mismatch_description=None): + try: + if len(item) == 0: + return True + + if mismatch_description: + mismatch_description \ + .append_text('has %d item(s)' % len(item)) + + except TypeError: + if mismatch_description: + mismatch_description \ + .append_text('does not support length') + + return False + + def describe_to(self, description): + description.append_text('an empty collection') + + +def empty(): + """ + This matcher matches any collection-like object that responds to the + __len__ method, and has a length of 0. + """ + return IsEmpty() diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containing.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containing.py new file mode 100644 index 0000000000..95281973fb --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containing.py @@ -0,0 +1,54 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsDictContaining(BaseMatcher): + + def __init__(self, key_matcher, value_matcher): + self.key_matcher = key_matcher + self.value_matcher = value_matcher + + def _matches(self, dictionary): + if hasmethod(dictionary, 'items'): + for key, value in dictionary.items(): + if self.key_matcher.matches(key) and self.value_matcher.matches(value): + return True + return False + + def describe_to(self, description): + description.append_text('a dictionary containing [') \ + .append_description_of(self.key_matcher) \ + .append_text(': ') \ + .append_description_of(self.value_matcher) \ + .append_text(']') + + +def has_entry(key_match, value_match): + """Matches if dictionary contains key-value entry satisfying a given pair + of matchers. + + :param key_match: The matcher to satisfy for the key, or an expected value + for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + :param value_match: The matcher to satisfy for the value, or an expected + value for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + This matcher iterates the evaluated dictionary, searching for any key-value + entry that satisfies ``key_match`` and ``value_match``. If a matching entry + is found, ``has_entry`` is satisfied. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + Examples:: + + has_entry(equal_to('foo'), equal_to(1)) + has_entry('foo', 1) + + """ + return IsDictContaining(wrap_matcher(key_match), wrap_matcher(value_match)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingentries.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingentries.py new file mode 100644 index 0000000000..eb83ade52d --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingentries.py @@ -0,0 +1,133 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsDictContainingEntries(BaseMatcher): + + def __init__(self, value_matchers): + self.value_matchers = sorted(value_matchers.items()) + + def _not_a_dictionary(self, dictionary, mismatch_description): + if mismatch_description: + mismatch_description.append_description_of(dictionary) \ + .append_text(' is not a mapping object') + return False + + def matches(self, dictionary, mismatch_description=None): + for key, value_matcher in self.value_matchers: + + try: + if not key in dictionary: + if mismatch_description: + mismatch_description.append_text('no ') \ + .append_description_of(key) \ + .append_text(' key in ') \ + .append_description_of(dictionary) + return False + except TypeError: + return self._not_a_dictionary(dictionary, mismatch_description) + + try: + actual_value = dictionary[key] + except TypeError: + return self._not_a_dictionary(dictionary, mismatch_description) + + if not value_matcher.matches(actual_value): + if mismatch_description: + mismatch_description.append_text('value for ') \ + .append_description_of(key) \ + .append_text(' ') + value_matcher.describe_mismatch(actual_value, mismatch_description) + return False + + return True + + def describe_mismatch(self, item, mismatch_description): + self.matches(item, mismatch_description) + + def describe_keyvalue(self, index, value, description): + """Describes key-value pair at given index.""" + description.append_description_of(index) \ + .append_text(': ') \ + .append_description_of(value) + + def describe_to(self, description): + description.append_text('a dictionary containing {') + first = True + for key, value in self.value_matchers: + if not first: + description.append_text(', ') + self.describe_keyvalue(key, value, description) + first = False + description.append_text('}') + + +def has_entries(*keys_valuematchers, **kv_args): + """Matches if dictionary contains entries satisfying a dictionary of keys + and corresponding value matchers. + + :param matcher_dict: A dictionary mapping keys to associated value matchers, + or to expected values for + :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + Note that the keys must be actual keys, not matchers. Any value argument + that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + Examples:: + + has_entries({'foo':equal_to(1), 'bar':equal_to(2)}) + has_entries({'foo':1, 'bar':2}) + + ``has_entries`` also accepts a list of keyword arguments: + + .. function:: has_entries(keyword1=value_matcher1[, keyword2=value_matcher2[, ...]]) + + :param keyword1: A keyword to look up. + :param valueMatcher1: The matcher to satisfy for the value, or an expected + value for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + Examples:: + + has_entries(foo=equal_to(1), bar=equal_to(2)) + has_entries(foo=1, bar=2) + + Finally, ``has_entries`` also accepts a list of alternating keys and their + value matchers: + + .. function:: has_entries(key1, value_matcher1[, ...]) + + :param key1: A key (not a matcher) to look up. + :param valueMatcher1: The matcher to satisfy for the value, or an expected + value for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + Examples:: + + has_entries('foo', equal_to(1), 'bar', equal_to(2)) + has_entries('foo', 1, 'bar', 2) + + """ + if len(keys_valuematchers) == 1: + try: + base_dict = keys_valuematchers[0].copy() + for key in base_dict: + base_dict[key] = wrap_matcher(base_dict[key]) + except AttributeError: + raise ValueError('single-argument calls to has_entries must pass a dict as the argument') + else: + if len(keys_valuematchers) % 2: + raise ValueError('has_entries requires key-value pairs') + base_dict = {} + for index in range(int(len(keys_valuematchers) / 2)): + base_dict[keys_valuematchers[2 * index]] = wrap_matcher(keys_valuematchers[2 * index + 1]) + + for key, value in kv_args.items(): + base_dict[key] = wrap_matcher(value) + + return IsDictContainingEntries(base_dict) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingkey.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingkey.py new file mode 100644 index 0000000000..ccb51e6396 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingkey.py @@ -0,0 +1,48 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsDictContainingKey(BaseMatcher): + + def __init__(self, key_matcher): + self.key_matcher = key_matcher + + def _matches(self, dictionary): + if hasmethod(dictionary, 'keys'): + for key in dictionary.keys(): + if self.key_matcher.matches(key): + return True + return False + + def describe_to(self, description): + description.append_text('a dictionary containing key ') \ + .append_description_of(self.key_matcher) + + +def has_key(key_match): + """Matches if dictionary contains an entry whose key satisfies a given + matcher. + + :param key_match: The matcher to satisfy for the key, or an expected value + for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + This matcher iterates the evaluated dictionary, searching for any key-value + entry whose key satisfies the given matcher. If a matching entry is found, + ``has_key`` is satisfied. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + Examples:: + + has_key(equal_to('foo')) + has_key('foo') + + """ + return IsDictContainingKey(wrap_matcher(key_match)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingvalue.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingvalue.py new file mode 100644 index 0000000000..e5288841b2 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isdict_containingvalue.py @@ -0,0 +1,48 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsDictContainingValue(BaseMatcher): + + def __init__(self, value_matcher): + self.value_matcher = value_matcher + + def _matches(self, dictionary): + if hasmethod(dictionary, 'values'): + for value in dictionary.values(): + if self.value_matcher.matches(value): + return True + return False + + def describe_to(self, description): + description.append_text('a dictionary containing value ') \ + .append_description_of(self.value_matcher) + + +def has_value(value): + """Matches if dictionary contains an entry whose value satisfies a given + matcher. + + :param value_match: The matcher to satisfy for the value, or an expected + value for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + This matcher iterates the evaluated dictionary, searching for any key-value + entry whose value satisfies the given matcher. If a matching entry is + found, ``has_value`` is satisfied. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + Examples:: + + has_value(equal_to('bar')) + has_value('bar') + + """ + return IsDictContainingValue(wrap_matcher(value)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/isin.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isin.py new file mode 100644 index 0000000000..87962a2474 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/isin.py @@ -0,0 +1,30 @@ +from hamcrest.core.base_matcher import BaseMatcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsIn(BaseMatcher): + + def __init__(self, sequence): + self.sequence = sequence + + def _matches(self, item): + return item in self.sequence + + def describe_to(self, description): + description.append_text('one of ') \ + .append_list('(', ', ', ')', self.sequence) + + +def is_in(sequence): + """Matches if evaluated object is present in a given sequence. + + :param sequence: The sequence to search. + + This matcher invokes the ``in`` membership operator to determine if the + evaluated object is a member of the sequence. + + """ + return IsIn(sequence) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containing.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containing.py new file mode 100644 index 0000000000..21939b3f3f --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containing.py @@ -0,0 +1,89 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.core.allof import all_of +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + + +class IsSequenceContaining(BaseMatcher): + + def __init__(self, element_matcher): + self.element_matcher = element_matcher + + def _matches(self, sequence): + try: + for item in sequence: + if self.element_matcher.matches(item): + return True + except TypeError: # not a sequence + return False + + def describe_to(self, description): + description.append_text('a sequence containing ') \ + .append_description_of(self.element_matcher) + + +# It'd be great to make use of all_of, but we can't be sure we won't +# be seeing a one-time sequence here (like a generator); see issue #20 +# Instead, we wrap it inside a class that will convert the sequence into +# a concrete list and then hand it off to the all_of matcher. +class IsSequenceContainingEvery(BaseMatcher): + + def __init__(self, *element_matchers): + delegates = [has_item(e) for e in element_matchers] + self.matcher = all_of(*delegates) + + def _matches(self, sequence): + try: + return self.matcher.matches(list(sequence)) + except TypeError: + return False + + def describe_mismatch(self, item, mismatch_description): + self.matcher.describe_mismatch(item, mismatch_description) + + def describe_to(self, description): + self.matcher.describe_to(description) + + + +def has_item(match): + """Matches if any element of sequence satisfies a given matcher. + + :param match: The matcher to satisfy, or an expected value for + :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + This matcher iterates the evaluated sequence, searching for any element + that satisfies a given matcher. If a matching element is found, + ``has_item`` is satisfied. + + If the ``match`` argument is not a matcher, it is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + """ + return IsSequenceContaining(wrap_matcher(match)) + + +def has_items(*items): + """Matches if all of the given matchers are satisfied by any elements of + the sequence. + + :param match1,...: A comma-separated list of matchers. + + This matcher iterates the given matchers, searching for any elements in the + evaluated sequence that satisfy them. If each matcher is satisfied, then + ``has_items`` is satisfied. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + """ + matchers = [] + for item in items: + matchers.append(wrap_matcher(item)) + return IsSequenceContainingEvery(*matchers) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containinginanyorder.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containinginanyorder.py new file mode 100644 index 0000000000..78e2b006fc --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containinginanyorder.py @@ -0,0 +1,97 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class MatchInAnyOrder(object): + def __init__(self, matchers, mismatch_description): + self.matchers = matchers[:] + self.mismatch_description = mismatch_description + + def matches(self, item): + return self.isnotsurplus(item) and self.ismatched(item) + + def isfinished(self, sequence): + if not self.matchers: + return True + if self.mismatch_description: + self.mismatch_description.append_text('no item matches: ') \ + .append_list('', ', ', '', self.matchers) \ + .append_text(' in ') \ + .append_list('[', ', ', ']', sequence) + return False + + def isnotsurplus(self, item): + if not self.matchers: + if self.mismatch_description: + self.mismatch_description.append_text('not matched: ') \ + .append_description_of(item) + return False + return True + + def ismatched(self, item): + for index, matcher in enumerate(self.matchers): + if matcher.matches(item): + del self.matchers[index] + return True + + if self.mismatch_description: + self.mismatch_description.append_text('not matched: ') \ + .append_description_of(item) + return False + + +class IsSequenceContainingInAnyOrder(BaseMatcher): + + def __init__(self, matchers): + self.matchers = matchers + + def matches(self, sequence, mismatch_description=None): + try: + sequence = list(sequence) + matchsequence = MatchInAnyOrder(self.matchers, mismatch_description) + for item in sequence: + if not matchsequence.matches(item): + return False + return matchsequence.isfinished(sequence) + except TypeError: + if mismatch_description: + super(IsSequenceContainingInAnyOrder, self) \ + .describe_mismatch(sequence, mismatch_description) + return False + + def describe_mismatch(self, item, mismatch_description): + self.matches(item, mismatch_description) + + def describe_to(self, description): + description.append_text('a sequence over ') \ + .append_list('[', ', ', ']', self.matchers) \ + .append_text(' in any order') + + +def contains_inanyorder(*items): + """Matches if sequences's elements, in any order, satisfy a given list of + matchers. + + :param match1,...: A comma-separated list of matchers. + + This matcher iterates the evaluated sequence, seeing if each element + satisfies any of the given matchers. The matchers are tried from left to + right, and when a satisfied matcher is found, it is no longer a candidate + for the remaining elements. If a one-to-one correspondence is established + between elements and matchers, ``contains_inanyorder`` is satisfied. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + """ + + matchers = [] + for item in items: + matchers.append(wrap_matcher(item)) + return IsSequenceContainingInAnyOrder(matchers) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containinginorder.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containinginorder.py new file mode 100644 index 0000000000..3fd91a6c92 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_containinginorder.py @@ -0,0 +1,88 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + + +class MatchingInOrder(object): + def __init__(self, matchers, mismatch_description): + self.matchers = matchers + self.mismatch_description = mismatch_description + self.next_match_index = 0 + + def matches(self, item): + return self.isnotsurplus(item) and self.ismatched(item) + + def isfinished(self): + if self.next_match_index < len(self.matchers): + if self.mismatch_description: + self.mismatch_description.append_text('No item matched: ') \ + .append_description_of(self.matchers[self.next_match_index]) + return False + return True + + def ismatched(self, item): + matcher = self.matchers[self.next_match_index] + if not matcher.matches(item): + if self.mismatch_description: + self.mismatch_description.append_text('item ' + str(self.next_match_index) + ': ') + matcher.describe_mismatch(item, self.mismatch_description) + return False + self.next_match_index += 1 + return True + + def isnotsurplus(self, item): + if len(self.matchers) <= self.next_match_index: + if self.mismatch_description: + self.mismatch_description.append_text('Not matched: ') \ + .append_description_of(item) + return False + return True + + +class IsSequenceContainingInOrder(BaseMatcher): + + def __init__(self, matchers): + self.matchers = matchers + + def matches(self, sequence, mismatch_description=None): + try: + matchsequence = MatchingInOrder(self.matchers, mismatch_description) + for item in sequence: + if not matchsequence.matches(item): + return False + return matchsequence.isfinished() + except TypeError: + if mismatch_description: + super(IsSequenceContainingInOrder, self) \ + .describe_mismatch(sequence, mismatch_description) + return False + + def describe_mismatch(self, item, mismatch_description): + self.matches(item, mismatch_description) + + def describe_to(self, description): + description.append_text('a sequence containing ') \ + .append_list('[', ', ', ']', self.matchers) + + +def contains(*items): + """Matches if sequence's elements satisfy a given list of matchers, in order. + + :param match1,...: A comma-separated list of matchers. + + This matcher iterates the evaluated sequence and a given list of matchers, + seeing if each element satisfies its corresponding matcher. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + """ + matchers = [] + for item in items: + matchers.append(wrap_matcher(item)) + return IsSequenceContainingInOrder(matchers) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_onlycontaining.py b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_onlycontaining.py new file mode 100644 index 0000000000..bd52c10419 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/collection/issequence_onlycontaining.py @@ -0,0 +1,55 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.core.anyof import any_of +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsSequenceOnlyContaining(BaseMatcher): + + def __init__(self, matcher): + self.matcher = matcher + + def _matches(self, sequence): + try: + sequence = list(sequence) + if len(sequence) == 0: + return False + for item in sequence: + if not self.matcher.matches(item): + return False + return True + except TypeError: + return False + + def describe_to(self, description): + description.append_text('a sequence containing items matching ') \ + .append_description_of(self.matcher) + + +def only_contains(*items): + """Matches if each element of sequence satisfies any of the given matchers. + + :param match1,...: A comma-separated list of matchers. + + This matcher iterates the evaluated sequence, confirming whether each + element satisfies any of the given matchers. + + Example:: + + only_contains(less_than(4)) + + will match ``[3,1,2]``. + + Any argument that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + """ + matchers = [] + for item in items: + matchers.append(wrap_matcher(item)) + return IsSequenceOnlyContaining(any_of(*matchers)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/integration/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/library/integration/__init__.py new file mode 100644 index 0000000000..cc1e132163 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/integration/__init__.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import +"""Utilities for integrating Hamcrest with other libraries.""" + +from .match_equality import match_equality + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/integration/match_equality.py b/contrib/python/PyHamcrest/src/hamcrest/library/integration/match_equality.py new file mode 100644 index 0000000000..52da054760 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/integration/match_equality.py @@ -0,0 +1,42 @@ +from hamcrest.core.string_description import tostring +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Chris Rose" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" +__unittest = True + + +class EqualityWrapper(object): + + def __init__(self, matcher): + self.matcher = matcher + + def __eq__(self, object): + return self.matcher.matches(object) + + def __str__(self): + return repr(self) + + def __repr__(self): + return tostring(self.matcher) + + +def match_equality(matcher): + """Wraps a matcher to define equality in terms of satisfying the matcher. + + ``match_equality`` allows Hamcrest matchers to be used in libraries that + are not Hamcrest-aware. They might use the equality operator:: + + assert match_equality(matcher) == object + + Or they might provide a method that uses equality for its test:: + + library.method_that_tests_eq(match_equality(matcher)) + + One concrete example is integrating with the ``assert_called_with`` methods + in Michael Foord's `mock <http://www.voidspace.org.uk/python/mock/>`_ + library. + + """ + return EqualityWrapper(wrap_matcher(matcher)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/number/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/library/number/__init__.py new file mode 100644 index 0000000000..5087faf846 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/number/__init__.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import +"""Matchers that perform numeric comparisons.""" + +from .iscloseto import close_to +from .ordering_comparison import greater_than, greater_than_or_equal_to, less_than, less_than_or_equal_to + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/number/iscloseto.py b/contrib/python/PyHamcrest/src/hamcrest/library/number/iscloseto.py new file mode 100644 index 0000000000..e401615e35 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/number/iscloseto.py @@ -0,0 +1,74 @@ +import six +from hamcrest.core.base_matcher import BaseMatcher +from math import fabs + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +def isnumeric(value): + """Confirm that 'value' can be treated numerically; duck-test accordingly + """ + if isinstance(value, (float, complex) + six.integer_types): + return True + + try: + _ = (fabs(value) + 0 - 0) * 1 + return True + except ArithmeticError: + return True + except: + return False + return False + + +class IsCloseTo(BaseMatcher): + + def __init__(self, value, delta): + if not isnumeric(value): + raise TypeError('IsCloseTo value must be numeric') + if not isnumeric(delta): + raise TypeError('IsCloseTo delta must be numeric') + + self.value = value + self.delta = delta + + def _matches(self, item): + if not isnumeric(item): + return False + return fabs(item - self.value) <= self.delta + + def describe_mismatch(self, item, mismatch_description): + if not isnumeric(item): + super(IsCloseTo, self).describe_mismatch(item, mismatch_description) + else: + actual_delta = fabs(item - self.value) + mismatch_description.append_description_of(item) \ + .append_text(' differed by ') \ + .append_description_of(actual_delta) + + def describe_to(self, description): + description.append_text('a numeric value within ') \ + .append_description_of(self.delta) \ + .append_text(' of ') \ + .append_description_of(self.value) + + +def close_to(value, delta): + """Matches if object is a number close to a given value, within a given + delta. + + :param value: The value to compare against as the expected value. + :param delta: The maximum delta between the values for which the numbers + are considered close. + + This matcher compares the evaluated object against ``value`` to see if the + difference is within a positive ``delta``. + + Example:: + + close_to(3.0, 0.25) + + """ + return IsCloseTo(value, delta) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/number/ordering_comparison.py b/contrib/python/PyHamcrest/src/hamcrest/library/number/ordering_comparison.py new file mode 100644 index 0000000000..c3c75f425d --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/number/ordering_comparison.py @@ -0,0 +1,59 @@ +from hamcrest.core.base_matcher import BaseMatcher +import operator + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class OrderingComparison(BaseMatcher): + + def __init__(self, value, comparison_function, comparison_description): + self.value = value + self.comparison_function = comparison_function + self.comparison_description = comparison_description + + def _matches(self, item): + return self.comparison_function(item, self.value) + + def describe_to(self, description): + description.append_text('a value ') \ + .append_text(self.comparison_description) \ + .append_text(' ') \ + .append_description_of(self.value) + + +def greater_than(value): + """Matches if object is greater than a given value. + + :param value: The value to compare against. + + """ + return OrderingComparison(value, operator.gt, 'greater than') + + +def greater_than_or_equal_to(value): + """Matches if object is greater than or equal to a given value. + + :param value: The value to compare against. + + """ + return OrderingComparison(value, operator.ge, 'greater than or equal to') + + +def less_than(value): + """Matches if object is less than a given value. + + :param value: The value to compare against. + + """ + return OrderingComparison(value, operator.lt, 'less than') + + +def less_than_or_equal_to(value): + """Matches if object is less than or equal to a given value. + + :param value: The value to compare against. + + """ + return OrderingComparison(value, operator.le, 'less than or equal to') diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/object/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/library/object/__init__.py new file mode 100644 index 0000000000..5ca4556661 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/object/__init__.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import +"""Matchers that inspect objects and classes.""" + +from .haslength import has_length +from .hasproperty import has_property, has_properties +from .hasstring import has_string + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/object/haslength.py b/contrib/python/PyHamcrest/src/hamcrest/library/object/haslength.py new file mode 100644 index 0000000000..3ef0ab5b81 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/object/haslength.py @@ -0,0 +1,50 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class HasLength(BaseMatcher): + + def __init__(self, len_matcher): + self.len_matcher = len_matcher + + def _matches(self, item): + if not hasmethod(item, '__len__'): + return False + return self.len_matcher.matches(len(item)) + + def describe_mismatch(self, item, mismatch_description): + super(HasLength, self).describe_mismatch(item, mismatch_description) + if hasmethod(item, '__len__'): + mismatch_description.append_text(' with length of ') \ + .append_description_of(len(item)) + + def describe_to(self, description): + description.append_text('an object with length of ') \ + .append_description_of(self.len_matcher) + + +def has_length(match): + """Matches if ``len(item)`` satisfies a given matcher. + + :param match: The matcher to satisfy, or an expected value for + :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + This matcher invokes the :py:func:`len` function on the evaluated object to + get its length, passing the result to a given matcher for evaluation. + + If the ``match`` argument is not a matcher, it is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + :equality. + + Examples:: + + has_length(greater_than(6)) + has_length(5) + + """ + return HasLength(wrap_matcher(match)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/object/hasproperty.py b/contrib/python/PyHamcrest/src/hamcrest/library/object/hasproperty.py new file mode 100644 index 0000000000..d2536d69f4 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/object/hasproperty.py @@ -0,0 +1,154 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core import anything +from hamcrest.core.core.allof import all_of +from hamcrest.core.string_description import StringDescription +from hamcrest.core.helpers.hasmethod import hasmethod +from hamcrest.core.helpers.wrap_matcher import wrap_matcher as wrap_shortcut + +__author__ = "Chris Rose" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class IsObjectWithProperty(BaseMatcher): + + def __init__(self, property_name, value_matcher): + self.property_name = property_name + self.value_matcher = value_matcher + + def _matches(self, o): + if o is None: + return False + + if not hasattr(o, self.property_name): + return False + + value = getattr(o, self.property_name) + return self.value_matcher.matches(value) + + def describe_to(self, description): + description.append_text("an object with a property '") \ + .append_text(self.property_name) \ + .append_text("' matching ") \ + .append_description_of(self.value_matcher) + + def describe_mismatch(self, item, mismatch_description): + if item is None: + mismatch_description.append_text('was None') + return + + if not hasattr(item, self.property_name): + mismatch_description.append_value(item) \ + .append_text(' did not have the ') \ + .append_value(self.property_name) \ + .append_text(' property') + return + + mismatch_description.append_text('property ').append_value(self.property_name).append_text(' ') + value = getattr(item, self.property_name) + self.value_matcher.describe_mismatch(value, mismatch_description) + + def __str__(self): + d = StringDescription() + self.describe_to(d) + return str(d) + + +def has_property(name, match=None): + """Matches if object has a property with a given name whose value satisfies + a given matcher. + + :param name: The name of the property. + :param match: Optional matcher to satisfy. + + This matcher determines if the evaluated object has a property with a given + name. If no such property is found, ``has_property`` is not satisfied. + + If the property is found, its value is passed to a given matcher for + evaluation. If the ``match`` argument is not a matcher, it is implicitly + wrapped in an :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to + check for equality. + + If the ``match`` argument is not provided, the + :py:func:`~hamcrest.core.core.isanything.anything` matcher is used so that + ``has_property`` is satisfied if a matching property is found. + + Examples:: + + has_property('name', starts_with('J')) + has_property('name', 'Jon') + has_property('name') + + """ + + if match is None: + match = anything() + + return IsObjectWithProperty(name, wrap_shortcut(match)) + + +def has_properties(*keys_valuematchers, **kv_args): + """Matches if an object has properties satisfying all of a dictionary + of string property names and corresponding value matchers. + + :param matcher_dict: A dictionary mapping keys to associated value matchers, + or to expected values for + :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + Note that the keys must be actual keys, not matchers. Any value argument + that is not a matcher is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + Examples:: + + has_properties({'foo':equal_to(1), 'bar':equal_to(2)}) + has_properties({'foo':1, 'bar':2}) + + ``has_properties`` also accepts a list of keyword arguments: + + .. function:: has_properties(keyword1=value_matcher1[, keyword2=value_matcher2[, ...]]) + + :param keyword1: A keyword to look up. + :param valueMatcher1: The matcher to satisfy for the value, or an expected + value for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + Examples:: + + has_properties(foo=equal_to(1), bar=equal_to(2)) + has_properties(foo=1, bar=2) + + Finally, ``has_properties`` also accepts a list of alternating keys and their + value matchers: + + .. function:: has_properties(key1, value_matcher1[, ...]) + + :param key1: A key (not a matcher) to look up. + :param valueMatcher1: The matcher to satisfy for the value, or an expected + value for :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + Examples:: + + has_properties('foo', equal_to(1), 'bar', equal_to(2)) + has_properties('foo', 1, 'bar', 2) + + """ + if len(keys_valuematchers) == 1: + try: + base_dict = keys_valuematchers[0].copy() + for key in base_dict: + base_dict[key] = wrap_shortcut(base_dict[key]) + except AttributeError: + raise ValueError('single-argument calls to has_properties must pass a dict as the argument') + else: + if len(keys_valuematchers) % 2: + raise ValueError('has_properties requires key-value pairs') + base_dict = {} + for index in range(int(len(keys_valuematchers) / 2)): + base_dict[keys_valuematchers[2 * index]] = wrap_shortcut(keys_valuematchers[2 * index + 1]) + + for key, value in kv_args.items(): + base_dict[key] = wrap_shortcut(value) + + return all_of(*[has_property(property_name, property_value_matcher) for \ + property_name, property_value_matcher in base_dict.items()]) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/object/hasstring.py b/contrib/python/PyHamcrest/src/hamcrest/library/object/hasstring.py new file mode 100644 index 0000000000..8b50547e21 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/object/hasstring.py @@ -0,0 +1,40 @@ +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.wrap_matcher import wrap_matcher + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class HasString(BaseMatcher): + + def __init__(self, str_matcher): + self.str_matcher = str_matcher + + def _matches(self, item): + return self.str_matcher.matches(str(item)) + + def describe_to(self, description): + description.append_text('an object with str ') \ + .append_description_of(self.str_matcher) + + +def has_string(match): + """Matches if ``str(item)`` satisfies a given matcher. + + :param match: The matcher to satisfy, or an expected value for + :py:func:`~hamcrest.core.core.isequal.equal_to` matching. + + This matcher invokes the :py:func:`str` function on the evaluated object to + get its length, passing the result to a given matcher for evaluation. If + the ``match`` argument is not a matcher, it is implicitly wrapped in an + :py:func:`~hamcrest.core.core.isequal.equal_to` matcher to check for + equality. + + Examples:: + + has_string(starts_with('foo')) + has_string('bar') + + """ + return HasString(wrap_matcher(match)) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/__init__.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/__init__.py new file mode 100644 index 0000000000..39d0e8b382 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/__init__.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import +"""Matchers that perform text comparisons.""" + +from .isequal_ignoring_case import equal_to_ignoring_case +from .isequal_ignoring_whitespace import equal_to_ignoring_whitespace +from .stringcontains import contains_string +from .stringendswith import ends_with +from .stringstartswith import starts_with +from .stringmatches import matches_regexp +from .stringcontainsinorder import string_contains_in_order + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/isequal_ignoring_case.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/isequal_ignoring_case.py new file mode 100644 index 0000000000..d1ee2d17fc --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/isequal_ignoring_case.py @@ -0,0 +1,43 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher + +import six + +class IsEqualIgnoringCase(BaseMatcher): + + def __init__(self, string): + if not isinstance(string, six.string_types): + raise TypeError('IsEqualIgnoringCase requires string') + self.original_string = string + self.lowered_string = string.lower() + + def _matches(self, item): + if not isinstance(item, six.string_types): + return False + return self.lowered_string == item.lower() + + def describe_to(self, description): + description.append_description_of(self.original_string) \ + .append_text(' ignoring case') + + +def equal_to_ignoring_case(string): + """Matches if object is a string equal to a given string, ignoring case + differences. + + :param string: The string to compare against as the expected value. + + This matcher first checks whether the evaluated object is a string. If so, + it compares it with ``string``, ignoring differences of case. + + Example:: + + equal_to_ignoring_case("hello world") + + will match "heLLo WorlD". + + """ + return IsEqualIgnoringCase(string) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/isequal_ignoring_whitespace.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/isequal_ignoring_whitespace.py new file mode 100644 index 0000000000..86fa997601 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/isequal_ignoring_whitespace.py @@ -0,0 +1,57 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher + +import six + +def stripspace(string): + result = '' + last_was_space = True + for character in string: + if character.isspace(): + if not last_was_space: + result += ' ' + last_was_space = True + else: + result += character + last_was_space = False + return result.strip() + + +class IsEqualIgnoringWhiteSpace(BaseMatcher): + + def __init__(self, string): + if not isinstance(string, six.string_types): + raise TypeError('IsEqualIgnoringWhiteSpace requires string') + self.original_string = string + self.stripped_string = stripspace(string) + + def _matches(self, item): + if not isinstance(item, six.string_types): + return False + return self.stripped_string == stripspace(item) + + def describe_to(self, description): + description.append_description_of(self.original_string) \ + .append_text(' ignoring whitespace') + + +def equal_to_ignoring_whitespace(string): + """Matches if object is a string equal to a given string, ignoring + differences in whitespace. + + :param string: The string to compare against as the expected value. + + This matcher first checks whether the evaluated object is a string. If so, + it compares it with ``string``, ignoring differences in runs of whitespace. + + Example:: + + equal_to_ignoring_whitespace("hello world") + + will match ``"hello world"``. + + """ + return IsEqualIgnoringWhiteSpace(string) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/stringcontains.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringcontains.py new file mode 100644 index 0000000000..58ffd283c6 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringcontains.py @@ -0,0 +1,38 @@ +from hamcrest.library.text.substringmatcher import SubstringMatcher +from hamcrest.core.helpers.hasmethod import hasmethod + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class StringContains(SubstringMatcher): + + def __init__(self, substring): + super(StringContains, self).__init__(substring) + + def _matches(self, item): + if not hasmethod(item, 'find'): + return False + return item.find(self.substring) >= 0 + + def relationship(self): + return 'containing' + + +def contains_string(substring): + """Matches if object is a string containing a given string. + + :param string: The string to search for. + + This matcher first checks whether the evaluated object is a string. If so, + it checks whether it contains ``string``. + + Example:: + + contains_string("def") + + will match "abcdefg". + + """ + return StringContains(substring) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/stringcontainsinorder.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringcontainsinorder.py new file mode 100644 index 0000000000..516b1043ab --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringcontainsinorder.py @@ -0,0 +1,52 @@ +__author__ = "Romilly Cocking"
+__copyright__ = "Copyright 2011 hamcrest.org"
+__license__ = "BSD, see License.txt"
+
+from hamcrest.core.base_matcher import BaseMatcher
+from hamcrest.core.helpers.hasmethod import hasmethod
+
+import six
+
+class StringContainsInOrder(BaseMatcher):
+
+ def __init__(self, *substrings):
+ for substring in substrings:
+ if not isinstance(substring, six.string_types):
+ raise TypeError(self.__class__.__name__
+ + ' requires string arguments')
+ self.substrings = substrings
+
+ def _matches(self, item):
+ if not hasmethod(item, 'find'):
+ return False
+ from_index = 0
+ for substring in self.substrings:
+ from_index = item.find(substring, from_index)
+ if from_index == -1:
+ return False
+ return True
+
+ def describe_to(self, description):
+ description.append_list('a string containing ', ', ', ' in order',
+ self.substrings)
+
+
+def string_contains_in_order(*substrings):
+ """Matches if object is a string containing a given list of substrings in
+ relative order.
+
+ :param string1,...: A comma-separated list of strings.
+
+ This matcher first checks whether the evaluated object is a string. If so,
+ it checks whether it contains a given list of strings, in relative order to
+ each other. The searches are performed starting from the beginning of the
+ evaluated string.
+
+ Example::
+
+ string_contains_in_order("bc", "fg", "jkl")
+
+ will match "abcdefghijklm".
+
+ """
+ return StringContainsInOrder(*substrings)
diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/stringendswith.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringendswith.py new file mode 100644 index 0000000000..43f9c3d302 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringendswith.py @@ -0,0 +1,39 @@ +from hamcrest.library.text.substringmatcher import SubstringMatcher +from hamcrest.core.helpers.hasmethod import hasmethod + +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + + +class StringEndsWith(SubstringMatcher): + + def __init__(self, substring): + super(StringEndsWith, self).__init__(substring) + + def _matches(self, item): + if not hasmethod(item, 'endswith'): + return False + return item.endswith(self.substring) + + def relationship(self): + return 'ending with' + + +def ends_with(string): + """Matches if object is a string ending with a given string. + + :param string: The string to search for. + + This matcher first checks whether the evaluated object is a string. If so, + it checks if ``string`` matches the ending characters of the evaluated + object. + + Example:: + + ends_with("bar") + + will match "foobar". + + """ + return StringEndsWith(string) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/stringmatches.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringmatches.py new file mode 100644 index 0000000000..2a16e29729 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringmatches.py @@ -0,0 +1,40 @@ +__author__ = "Chris Rose" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +import re + +import six + +from hamcrest.core.base_matcher import BaseMatcher +from hamcrest.core.helpers.hasmethod import hasmethod + +class StringMatchesPattern(BaseMatcher): + + def __init__(self, pattern): + self.pattern = pattern + + def describe_to(self, description): + description.append_text("a string matching '") \ + .append_text(self.pattern.pattern) \ + .append_text("'") + + def _matches(self, item): + return self.pattern.search(item) is not None + + +def matches_regexp(pattern): + """Matches if object is a string containing a match for a given regular + expression. + + :param pattern: The regular expression to search for. + + This matcher first checks whether the evaluated object is a string. If so, + it checks if the regular expression ``pattern`` matches anywhere within the + evaluated object. + + """ + if isinstance(pattern, six.string_types): + pattern = re.compile(pattern) + + return StringMatchesPattern(pattern) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/stringstartswith.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringstartswith.py new file mode 100644 index 0000000000..a0af49c9c2 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/stringstartswith.py @@ -0,0 +1,39 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.library.text.substringmatcher import SubstringMatcher +from hamcrest.core.helpers.hasmethod import hasmethod + + +class StringStartsWith(SubstringMatcher): + + def __init__(self, substring): + super(StringStartsWith, self).__init__(substring) + + def _matches(self, item): + if not hasmethod(item, 'startswith'): + return False + return item.startswith(self.substring) + + def relationship(self): + return 'starting with' + + +def starts_with(substring): + """Matches if object is a string starting with a given string. + + :param string: The string to search for. + + This matcher first checks whether the evaluated object is a string. If so, + it checks if ``string`` matches the beginning characters of the evaluated + object. + + Example:: + + starts_with("foo") + + will match "foobar". + + """ + return StringStartsWith(substring) diff --git a/contrib/python/PyHamcrest/src/hamcrest/library/text/substringmatcher.py b/contrib/python/PyHamcrest/src/hamcrest/library/text/substringmatcher.py new file mode 100644 index 0000000000..63ea359a51 --- /dev/null +++ b/contrib/python/PyHamcrest/src/hamcrest/library/text/substringmatcher.py @@ -0,0 +1,20 @@ +__author__ = "Jon Reid" +__copyright__ = "Copyright 2011 hamcrest.org" +__license__ = "BSD, see License.txt" + +from hamcrest.core.base_matcher import BaseMatcher + +import six + +class SubstringMatcher(BaseMatcher): + + def __init__(self, substring): + if not isinstance(substring, six.string_types): + raise TypeError(self.__class__.__name__ + ' requires string') + self.substring = substring + + def describe_to(self, description): + description.append_text('a string ') \ + .append_text(self.relationship()) \ + .append_text(' ') \ + .append_description_of(self.substring) diff --git a/contrib/python/PyHamcrest/tests/test_raises.py b/contrib/python/PyHamcrest/tests/test_raises.py new file mode 100644 index 0000000000..4c9ff4e040 --- /dev/null +++ b/contrib/python/PyHamcrest/tests/test_raises.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +__author__ = 'asatarin@yandex-team.ru' + + +class TestException(RuntimeError): + def __init__(self, *args, **kwargs): + super(TestException, self).__init__(*args, **kwargs) + self.prop = "property" + + +def raises_exception(): + raise TestException() + + +def returns_value(): + return 'my_return_value' + + +def test_raises(): + """ + >>> from hamcrest import assert_that + >>> from hamcrest import has_property + >>> from hamcrest import not_, raises + >>> raises(TestException).matches(raises_exception) + True + >>> raises(TestException, matcher=has_property("prop", "property")).matches(raises_exception) + True + >>> raises(TestException, matcher=has_property("prop", "fail")).matches(raises_exception) + False + >>> raises(TestException, matcher=not_(has_property("prop", "fail"))).matches(raises_exception) + True + >>> raises(TestException, matcher=not_(has_property("prop", "property"))).matches(raises_exception) + False + + >>> assert_that(returns_value, raises(TestException), 'message') + Traceback (most recent call last): + ... + AssertionError: message + Expected: Expected a callable raising <class '__tests__.test_raises.TestException'> + but: No exception raised and actual return value = 'my_return_value' + <BLANKLINE> + """ diff --git a/contrib/python/PyHamcrest/tests/test_string_description.py b/contrib/python/PyHamcrest/tests/test_string_description.py new file mode 100644 index 0000000000..40cbdd226e --- /dev/null +++ b/contrib/python/PyHamcrest/tests/test_string_description.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from hamcrest import assert_that, empty, equal_to + +__author__ = 'asatarin@yandex-team.ru' + + +def test_string_description_is_fast(): + list_of_very_long_strings = ["aa"*1000 for _ in range(10000)] + try: + assert_that(list_of_very_long_strings, empty()) + x = 0 + except AssertionError as e: + x = len(str(e)) + + assert_that(x, equal_to(20040048)) diff --git a/contrib/python/PyHamcrest/tests/ya.make b/contrib/python/PyHamcrest/tests/ya.make new file mode 100644 index 0000000000..6519301793 --- /dev/null +++ b/contrib/python/PyHamcrest/tests/ya.make @@ -0,0 +1,16 @@ +OWNER(g:python-contrib) + +PY23_TEST() + +NO_LINT() + +TEST_SRCS( + test_raises.py + test_string_description.py +) + +PEERDIR( + contrib/python/PyHamcrest +) + +END() diff --git a/contrib/python/PyHamcrest/ya.make b/contrib/python/PyHamcrest/ya.make new file mode 100644 index 0000000000..c24f0fbef8 --- /dev/null +++ b/contrib/python/PyHamcrest/ya.make @@ -0,0 +1,84 @@ +PY23_LIBRARY() + +LICENSE(BSD-3-Clause) + +OWNER(g:python-contrib) + +VERSION(1.9.0) + +PEERDIR ( + contrib/python/six +) + +SRCDIR( + contrib/python/PyHamcrest/src +) + +PY_SRCS( + TOP_LEVEL + + hamcrest/core/compat.py + hamcrest/core/assert_that.py + hamcrest/core/matcher.py + hamcrest/core/base_matcher.py + hamcrest/core/selfdescribingvalue.py + hamcrest/core/string_description.py + hamcrest/core/core/isnot.py + hamcrest/core/core/allof.py + hamcrest/core/core/issame.py + hamcrest/core/core/anyof.py + hamcrest/core/core/isanything.py + hamcrest/core/core/is_.py + hamcrest/core/core/described_as.py + hamcrest/core/core/raises.py + hamcrest/core/core/isequal.py + hamcrest/core/core/isnone.py + hamcrest/core/core/isinstanceof.py + hamcrest/core/core/__init__.py + hamcrest/core/description.py + hamcrest/core/selfdescribing.py + hamcrest/core/base_description.py + hamcrest/core/helpers/hasmethod.py + hamcrest/core/helpers/wrap_matcher.py + hamcrest/core/helpers/__init__.py + hamcrest/core/__init__.py + hamcrest/library/integration/match_equality.py + hamcrest/library/integration/__init__.py + hamcrest/library/number/ordering_comparison.py + hamcrest/library/number/iscloseto.py + hamcrest/library/number/__init__.py + hamcrest/library/text/substringmatcher.py + hamcrest/library/text/stringcontainsinorder.py + hamcrest/library/text/isequal_ignoring_case.py + hamcrest/library/text/stringstartswith.py + hamcrest/library/text/stringendswith.py + hamcrest/library/text/isequal_ignoring_whitespace.py + hamcrest/library/text/stringcontains.py + hamcrest/library/text/stringmatches.py + hamcrest/library/text/__init__.py + hamcrest/library/object/hasstring.py + hamcrest/library/object/hasproperty.py + hamcrest/library/object/haslength.py + hamcrest/library/object/__init__.py + hamcrest/library/collection/isdict_containingkey.py + hamcrest/library/collection/issequence_onlycontaining.py + hamcrest/library/collection/issequence_containing.py + hamcrest/library/collection/issequence_containinginorder.py + hamcrest/library/collection/isdict_containing.py + hamcrest/library/collection/issequence_containinginanyorder.py + hamcrest/library/collection/isin.py + hamcrest/library/collection/isdict_containingvalue.py + hamcrest/library/collection/is_empty.py + hamcrest/library/collection/isdict_containingentries.py + hamcrest/library/collection/__init__.py + hamcrest/library/__init__.py + hamcrest/__init__.py +) + +NO_LINT() + +END() + +RECURSE_FOR_TESTS( + tests +) |