Skip to content

Commit

Permalink
Add is_sequence_with matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
ErwinJunge committed Jul 17, 2019
1 parent a1c9970 commit 81c0fb2
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 4 deletions.
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ For instance, ``has_attrs(name="bob")`` is equivalent to ``has_attrs(name=equal_
assert_that(result, is_sequence("a", "b"))
# Matches ["a", "b"] but not ["b", "a"]
* ``is_sequence_with(*args)``: matches an iterable if it has the same elements in the same order, accepts extra items.
For instance:

.. code:: python
assert_that(result, is_sequence_with("a", "b"))
# Matches ["a", "b"], ["c", "a", "b"] and ["a", "b", "c"], but not ["b", "a"]
* ``includes(*args)``: matches an iterable if it includes all of the elements.
For instance:

Expand Down
3 changes: 2 additions & 1 deletion precisely/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from .comparison_matchers import contains_string, greater_than, greater_than_or_equal_to, less_than, less_than_or_equal_to, starts_with, close_to
from .core_matchers import equal_to, anything, all_of, any_of, not_
from .object_matchers import has_attr, has_attrs, is_instance
from .iterable_matchers import all_elements, contains_exactly, includes, is_sequence
from .iterable_matchers import all_elements, contains_exactly, includes, is_sequence, is_sequence_with
from .feature_matchers import has_feature
from .function_matchers import raises
from .mapping_matchers import is_mapping
Expand Down Expand Up @@ -33,6 +33,7 @@
"includes",
"is_same_sequence",
"is_sequence",
"is_sequence_with",
"has_feature",
"is_mapping",
"raises",
Expand Down
25 changes: 22 additions & 3 deletions precisely/iterable_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .base import Matcher
from .results import matched, unmatched, indented_list, indexed_indented_list, Result
from .coercion import to_matcher
from .utils import window


def contains_exactly(*matchers):
Expand Down Expand Up @@ -115,17 +116,35 @@ def match_remaining(self):
)))


def is_sequence(*matchers):
return IsSequenceMatcher([to_matcher(matcher) for matcher in matchers])
def is_sequence(*matchers, **kwargs):
allow_extra = kwargs.pop('allow_extra', False) # workaround for python 2
return IsSequenceMatcher([to_matcher(matcher) for matcher in matchers], allow_extra)


def is_sequence_with(*matchers):
return is_sequence(*matchers, allow_extra=True)


class IsSequenceMatcher(Matcher):
_missing = object()

def __init__(self, matchers):
def __init__(self, matchers, allow_extra):
self._matchers = matchers
self._allow_extra = allow_extra

def match(self, actual):
if self._allow_extra:
for subsequence in window(_to_list_or_mismatch(actual), len(self._matchers)):
response = self._match(subsequence)
if response.is_match:
break
if not response.is_match:
response = self._match(actual)
else:
response = self._match(actual)
return response

def _match(self, actual):
values = _to_list_or_mismatch(actual)

if isinstance(values, Result):
Expand Down
13 changes: 13 additions & 0 deletions precisely/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from itertools import islice


def window(seq, n):
"Returns a sliding window (of width n) over data from the iterable"
" s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ... "
it = iter(seq)
result = tuple(islice(it, n))
if len(result) <= n:
yield result
for elem in it:
result = result[1:] + (elem,)
yield result
111 changes: 111 additions & 0 deletions tests/is_sequence_with_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from nose.tools import istest, assert_equal

from precisely import is_sequence_with, equal_to
from precisely.results import matched, unmatched


@istest
def matches_when_all_submatchers_match_one_item_with_no_items_leftover():
matcher = is_sequence_with(equal_to("apple"), equal_to("banana"))

assert_equal(matched(), matcher.match(["apple", "banana"]))


@istest
def mismatches_when_actual_is_not_iterable():
matcher = is_sequence_with(equal_to("apple"))

assert_equal(
unmatched("was not iterable\nwas 0"),
matcher.match(0)
)


@istest
def mismatches_when_items_are_in_wrong_order():
matcher = is_sequence_with(equal_to("apple"), equal_to("banana"))

assert_equal(
unmatched("element at index 0 mismatched:\n * was 'banana'"),
matcher.match(["banana", "apple"])
)


@istest
def mismatches_when_item_is_missing():
matcher = is_sequence_with(equal_to("apple"), equal_to("banana"), equal_to("coconut"))

assert_equal(
unmatched("element at index 2 was missing"),
matcher.match(["apple", "banana"])
)


@istest
def mismatches_when_item_is_expected_but_iterable_is_empty():
matcher = is_sequence_with(equal_to("apple"))

assert_equal(
unmatched("iterable was empty"),
matcher.match([])
)


@istest
def when_empty_iterable_is_expected_then_empty_iterable_matches():
matcher = is_sequence_with()

assert_equal(
matched(),
matcher.match([])
)


@istest
def matches_when_contains_extra_item_after():
matcher = is_sequence_with(equal_to("apple"), equal_to("pear"))

assert_equal(
matched(),
matcher.match(["apple", "pear", "coconut"])
)


@istest
def matches_when_contains_extra_item_before():
matcher = is_sequence_with(equal_to("apple"), equal_to("pear"))

assert_equal(
matched(),
matcher.match(["coconut", "apple", "pear"])
)


@istest
def when_there_are_zero_submatchers_then_description_is_of_empty_iterable():
matcher = is_sequence_with()

assert_equal(
"empty iterable",
matcher.describe()
)


@istest
def description_contains_descriptions_of_submatchers():
matcher = is_sequence_with(equal_to("apple"), equal_to("banana"))

assert_equal(
"iterable containing in order:\n 0: 'apple'\n 1: 'banana'",
matcher.describe()
)


@istest
def elements_are_coerced_to_matchers():
matcher = is_sequence_with("apple", "banana")

assert_equal(
"iterable containing in order:\n 0: 'apple'\n 1: 'banana'",
matcher.describe()
)

0 comments on commit 81c0fb2

Please sign in to comment.