Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
)