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
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Changelog
=========

* :release:`0.12.3 <2023-12-20>`
* :bug:`74` Support uniqueness assertions for iterable containers that do not
support slicing
* :bug:`128` Properly handle assertion failures during setUp/tearDown
* :support:`145` Support Python 3.12 (and drop 3.8)
* :release:`0.12.2 <2023-12-20>`
Expand Down
31 changes: 13 additions & 18 deletions marbles/mixins/marbles/mixins/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,15 @@ class UniqueMixins(abc.ABC):
elements.
'''

@staticmethod
def _unique(container):
seen = []
for elem in container:
if elem in seen:
return False
seen.append(elem)
return True

@abc.abstractmethod
def fail(self, msg):
pass # pragma: no cover
Expand Down Expand Up @@ -391,14 +400,8 @@ def assertUnique(self, container, msg=None):

standardMsg = 'Elements in %s are not unique' % (container,)

# We iterate over each element in the container instead of
# comparing len(container) == len(set(container)) to allow
# for containers that contain unhashable types
for idx, elem in enumerate(container):
# If elem appears at an earlier or later index position
# the elements are not unique
if elem in container[:idx] or elem in container[idx+1:]:
self.fail(self._formatMessage(msg, standardMsg))
if not self._unique(container):
self.fail(self._formatMessage(msg, standardMsg))

def assertNotUnique(self, container, msg=None):
'''Fail if elements in ``container`` are unique.
Expand All @@ -420,16 +423,8 @@ def assertNotUnique(self, container, msg=None):

standardMsg = 'Elements in %s are unique' % (container,)

# We iterate over each element in the container instead of
# comparing len(container) == len(set(container)) to allow
# for containers that contain unhashable types
for idx, elem in enumerate(container):
# If elem appears at an earlier or later index position
# the elements are not unique
if elem in container[:idx] or elem in container[idx+1:]:
return # succeed fast

self.fail(self._formatMessage(msg, standardMsg))
if self._unique(container):
self.fail(self._formatMessage(msg, standardMsg))


class FileMixins(abc.ABC):
Expand Down
16 changes: 16 additions & 0 deletions marbles/mixins/tests/test_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import operator
import os
import unittest
from collections import deque
from unittest import mock
from datetime import date, datetime, timedelta, timezone

Expand Down Expand Up @@ -268,6 +269,21 @@ def test_assert_unique(self):
self.kls.assertNotUnique([set([1, 2, 3]), set([2, 3, 4])])
self.assertEqual(e.exception.args[0], msg)

# containers that are iterable but do not support slicing
self.kls.assertUnique(deque([1, 2, 3]))

msg = 'Elements in %s are not unique' % (deque([1, 2, 1]),)
with self.assertRaises(AssertionError) as e:
self.kls.assertUnique(deque([1, 2, 1]))
self.assertEqual(e.exception.args[0], msg)

self.kls.assertNotUnique(deque([1, 2, 1]))

msg = 'Elements in %s are unique' % (deque([1, 2, 3]),)
with self.assertRaises(AssertionError) as e:
self.kls.assertNotUnique(deque([1, 2, 3]))
self.assertEqual(e.exception.args[0], msg)


class TestFileMixins(unittest.TestCase):

Expand Down