Skip to content

Commit

Permalink
Merge 790d4aa into f2d8c6e
Browse files Browse the repository at this point in the history
  • Loading branch information
bpjatfb committed Mar 27, 2023
2 parents f2d8c6e + 790d4aa commit ba8c71d
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 1 deletion.
12 changes: 12 additions & 0 deletions docs/patching/argument_matchers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ Dictionaries
"``DictContainingKeys(keys_list)``", "A dictionary containing all keys from ``keys_list``"
"``DictSupersetOf(this_dict)``", "A dictionary containing all key / value pairs from ``this_dict``"

Collections
-----------
.. csv-table::
:header: "Matcher", "Description"

"``AnyContaining(element)``", "A container that contains ``element``"
"``AnyContainingAll(element_list)``", "A container that contains every element of ``element_list``"
"``AnyIterable()``", "Any iterable"
"``IterableWithElements(element_list)``", "An iterable containing all the elements in ``element_list`` in the same order"
"``AnyNotEmpty()``", "An object where ``len()`` does not evaluate to zero"
"``AnyEmpty()``", "An object where ``len()`` evaluates to zero"

Generic
-------

Expand Down
116 changes: 116 additions & 0 deletions tests/matchers_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from collections import deque

import testslide

from . import sample_module
Expand Down Expand Up @@ -297,6 +299,120 @@ def testDictContainingKeys(self):
testslide.matchers.DictContainingKeys({"a", "b", "c"})


class TestIterable(testslide.TestCase):
def testAnyContaining(self):
# list
self.assertEqual(testslide.matchers.AnyContaining(1), [1, 2, 3])
self.assertNotEqual(testslide.matchers.AnyContaining(1), [2, 3, 4])
# non-list collection
self.assertEqual(
testslide.matchers.AnyContaining(1), deque([1, 2, 3], maxlen=100)
)
self.assertEqual(
testslide.matchers.AnyContaining(1), deque([1, 2, 3], maxlen=100)
)
# string
with self.assertRaises(TypeError):
self.assertNotEqual(testslide.matchers.AnyContaining(1), "DERP")
self.assertEqual(testslide.matchers.AnyContaining("E"), "DERP")
self.assertNotEqual(testslide.matchers.AnyContaining("A"), "DERP")

def testAnyContainingAll(self):
# list
self.assertEqual(testslide.matchers.AnyContainingAll([1, 2]), [1, 2, 3])
self.assertNotEqual(
testslide.matchers.AnyContainingAll([1, 2, 3, 5]), [2, 3, 4]
)
# non-list
self.assertEqual(testslide.matchers.AnyContainingAll([1, 2]), {1, 2, 3})
self.assertNotEqual(
testslide.matchers.AnyContainingAll([1, 2, 3, 5]), {2, 3, 4}
)
self.assertEqual(testslide.matchers.AnyContainingAll({1, 2}), [1, 2, 3])
self.assertNotEqual(
testslide.matchers.AnyContainingAll({1, 2, 3, 5}), [2, 3, 4]
)
# non-iterables
with self.assertRaises(TypeError):
self.assertEqual(testslide.matchers.AnyContainingAll(10), [1, 2, 3])
with self.assertRaises(TypeError):
self.assertEqual(testslide.matchers.AnyContainingAll([1, 2, 3]), 10)

def testAnyIterable(self):
self.assertEqual(testslide.matchers.AnyIterable(), [1, 2, 3])
self.assertEqual(testslide.matchers.AnyIterable(), range(3))
self.assertEqual(testslide.matchers.AnyIterable(), {1, 2, 3})
self.assertNotEqual(testslide.matchers.AnyIterable(), 10)

def testIterableWithElements(self):
self.assertEqual(testslide.matchers.IterableWithElements([1, 2, 3]), [1, 2, 3])
# subset
self.assertNotEqual(testslide.matchers.IterableWithElements([1, 2]), [1, 2, 3])
# non-list
self.assertEqual(
testslide.matchers.IterableWithElements([1, 2, 3]),
deque([1, 2, 3], maxlen=100),
)
self.assertNotEqual(
testslide.matchers.IterableWithElements([2, 3, 4]),
deque([1, 2, 3], maxlen=100),
)
self.assertEqual(
testslide.matchers.IterableWithElements(range(1, 4)),
[1, 2, 3],
)

def testExhaustedIterators(self):
expected_list = [1, 2, 3]
for MatcherClass in (
testslide.matchers.AnyContainingAll,
testslide.matchers.IterableWithElements,
):
it = iter(expected_list)
matcher = MatcherClass(it)

# verify iterator is exhausted
with self.assertRaises(StopIteration):
next(it)

self.assertEqual(
matcher,
expected_list,
)
# Asserting against this matcher twice produces the same result
self.assertEqual(
matcher,
expected_list,
)

def testAnyEmpty(self):
# Sized
self.assertEqual(testslide.matchers.AnyEmpty(), [])
self.assertEqual(testslide.matchers.AnyEmpty(), {})
self.assertNotEqual(testslide.matchers.AnyEmpty(), [1, 2, 3])
# iterables without len()
with self.assertRaises(TypeError):
self.assertEqual(testslide.matchers.AnyEmpty(), iter([]))
with self.assertRaises(TypeError):
self.assertNotEqual(testslide.matchers.AnyEmpty(), iter([1, 2, 3]))
with self.assertRaises(TypeError):
self.assertEqual(testslide.matchers.AnyEmpty(), 10)

def testAnyNotEmpty(self):
# Sized
self.assertNotEqual(testslide.matchers.AnyNotEmpty(), [])
self.assertEqual(testslide.matchers.AnyNotEmpty(), [1, 2, 3])
self.assertEqual(testslide.matchers.AnyNotEmpty(), {1, 2, 3})
# iterables without len()
with self.assertRaises(TypeError):
self.assertNotEqual(testslide.matchers.AnyNotEmpty(), iter([]))
with self.assertRaises(TypeError):
self.assertEqual(testslide.matchers.AnyNotEmpty(), iter([1, 2, 3]))
# not iterable
with self.assertRaises(TypeError):
self.assertEqual(testslide.matchers.AnyNotEmpty(), 10)


class TestChaining(testslide.TestCase):
def testBitwiseAnd(self):
self.assertTrue(
Expand Down
82 changes: 81 additions & 1 deletion testslide/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@
# LICENSE file in the root directory of this source tree.
import re
from typing import Any as AnyType
from typing import Callable, Dict, List, NoReturn, Optional, TypeVar, Union
from typing import (
Callable,
Container,
Dict,
Iterable,
List,
NoReturn,
Optional,
Sized,
TypeVar,
Union,
)


class AlreadyChainedException(Exception):
Expand Down Expand Up @@ -474,6 +485,75 @@ def __eq__(self, other: Dict[AnyType, AnyType]) -> bool: # type: ignore
return False


# generic containers/iterables


class AnyContaining(Matcher):
def __init__(self, needle: AnyType) -> None:
self.needle = needle

def __eq__(self, other: Container[AnyType]) -> bool: # type: ignore
return self.needle in other

def __repr__(self) -> str:
return "<{} 0x{:02X}{}>".format(
type(self).__name__,
id(self),
f" needle={self.needle}" if self.needle is not None else "",
)


class AnyContainingAll(Matcher):
def __init__(self, subset: Iterable[AnyType]) -> None:
self.subset_repr = repr(subset) if subset is not None else ""
self.subset = list(subset)

def __eq__(self, other: Container[AnyType]) -> bool: # type: ignore
return all(x in other for x in self.subset)

def __repr__(self) -> str:
return "<{} 0x{:02X}{}>".format(
type(self).__name__,
id(self),
f" subset={self.subset_repr}",
)


class AnyIterable(Matcher):
def __eq__(self, other: AnyType):
try:
iter(other)
except TypeError:
return False
return True


class IterableWithElements(Matcher):
def __init__(self, elements: Iterable[AnyType]) -> None:
self.elements_repr = repr(elements) if elements is not None else ""
self.elements = list(elements)

def __eq__(self, other: Iterable[AnyType]) -> bool: # type: ignore
return self.elements == list(other)

def __repr__(self) -> str:
return "<{} 0x{:02X}{}>".format(
type(self).__name__,
id(self),
f" elements={self.elements_repr}",
)


class AnyNotEmpty(Matcher):
def __eq__(self, other: Sized) -> bool: # type: ignore
return bool(len(other))


class AnyEmpty(Matcher):
def __eq__(self, other: Sized) -> bool: # type: ignore
return not bool(len(other))


# generic


Expand Down

0 comments on commit ba8c71d

Please sign in to comment.