17db96d56Sopenharmony_ci""" Test Iterator Length Transparency
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciSome functions or methods which accept general iterable arguments have
47db96d56Sopenharmony_cioptional, more efficient code paths if they know how many items to expect.
57db96d56Sopenharmony_ciFor instance, map(func, iterable), will pre-allocate the exact amount of
67db96d56Sopenharmony_cispace required whenever the iterable can report its length.
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_ciThe desired invariant is:  len(it)==len(list(it)).
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_ciA complication is that an iterable and iterator can be the same object. To
117db96d56Sopenharmony_cimaintain the invariant, an iterator needs to dynamically update its length.
127db96d56Sopenharmony_ciFor instance, an iterable such as range(10) always reports its length as ten,
137db96d56Sopenharmony_cibut it=iter(range(10)) starts at ten, and then goes to nine after next(it).
147db96d56Sopenharmony_ciHaving this capability means that map() can ignore the distinction between
157db96d56Sopenharmony_cimap(func, iterable) and map(func, iter(iterable)).
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ciWhen the iterable is immutable, the implementation can straight-forwardly
187db96d56Sopenharmony_cireport the original length minus the cumulative number of calls to next().
197db96d56Sopenharmony_ciThis is the case for tuples, range objects, and itertools.repeat().
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ciSome containers become temporarily immutable during iteration.  This includes
227db96d56Sopenharmony_cidicts, sets, and collections.deque.  Their implementation is equally simple
237db96d56Sopenharmony_cithough they need to permanently set their length to zero whenever there is
247db96d56Sopenharmony_cian attempt to iterate after a length mutation.
257db96d56Sopenharmony_ci
267db96d56Sopenharmony_ciThe situation slightly more involved whenever an object allows length mutation
277db96d56Sopenharmony_ciduring iteration.  Lists and sequence iterators are dynamically updatable.
287db96d56Sopenharmony_ciSo, if a list is extended during iteration, the iterator will continue through
297db96d56Sopenharmony_cithe new items.  If it shrinks to a point before the most recent iteration,
307db96d56Sopenharmony_cithen no further items are available and the length is reported at zero.
317db96d56Sopenharmony_ci
327db96d56Sopenharmony_ciReversed objects can also be wrapped around mutable objects; however, any
337db96d56Sopenharmony_ciappends after the current position are ignored.  Any other approach leads
347db96d56Sopenharmony_cito confusion and possibly returning the same item more than once.
357db96d56Sopenharmony_ci
367db96d56Sopenharmony_ciThe iterators not listed above, such as enumerate and the other itertools,
377db96d56Sopenharmony_ciare not length transparent because they have no way to distinguish between
387db96d56Sopenharmony_ciiterables that report static length and iterators whose length changes with
397db96d56Sopenharmony_cieach call (i.e. the difference between enumerate('abc') and
407db96d56Sopenharmony_cienumerate(iter('abc')).
417db96d56Sopenharmony_ci
427db96d56Sopenharmony_ci"""
437db96d56Sopenharmony_ci
447db96d56Sopenharmony_ciimport unittest
457db96d56Sopenharmony_cifrom itertools import repeat
467db96d56Sopenharmony_cifrom collections import deque
477db96d56Sopenharmony_cifrom operator import length_hint
487db96d56Sopenharmony_ci
497db96d56Sopenharmony_cin = 10
507db96d56Sopenharmony_ci
517db96d56Sopenharmony_ci
527db96d56Sopenharmony_ciclass TestInvariantWithoutMutations:
537db96d56Sopenharmony_ci
547db96d56Sopenharmony_ci    def test_invariant(self):
557db96d56Sopenharmony_ci        it = self.it
567db96d56Sopenharmony_ci        for i in reversed(range(1, n+1)):
577db96d56Sopenharmony_ci            self.assertEqual(length_hint(it), i)
587db96d56Sopenharmony_ci            next(it)
597db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), 0)
607db96d56Sopenharmony_ci        self.assertRaises(StopIteration, next, it)
617db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), 0)
627db96d56Sopenharmony_ci
637db96d56Sopenharmony_ciclass TestTemporarilyImmutable(TestInvariantWithoutMutations):
647db96d56Sopenharmony_ci
657db96d56Sopenharmony_ci    def test_immutable_during_iteration(self):
667db96d56Sopenharmony_ci        # objects such as deques, sets, and dictionaries enforce
677db96d56Sopenharmony_ci        # length immutability  during iteration
687db96d56Sopenharmony_ci
697db96d56Sopenharmony_ci        it = self.it
707db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), n)
717db96d56Sopenharmony_ci        next(it)
727db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), n-1)
737db96d56Sopenharmony_ci        self.mutate()
747db96d56Sopenharmony_ci        self.assertRaises(RuntimeError, next, it)
757db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), 0)
767db96d56Sopenharmony_ci
777db96d56Sopenharmony_ci## ------- Concrete Type Tests -------
787db96d56Sopenharmony_ci
797db96d56Sopenharmony_ciclass TestRepeat(TestInvariantWithoutMutations, unittest.TestCase):
807db96d56Sopenharmony_ci
817db96d56Sopenharmony_ci    def setUp(self):
827db96d56Sopenharmony_ci        self.it = repeat(None, n)
837db96d56Sopenharmony_ci
847db96d56Sopenharmony_ciclass TestXrange(TestInvariantWithoutMutations, unittest.TestCase):
857db96d56Sopenharmony_ci
867db96d56Sopenharmony_ci    def setUp(self):
877db96d56Sopenharmony_ci        self.it = iter(range(n))
887db96d56Sopenharmony_ci
897db96d56Sopenharmony_ciclass TestXrangeCustomReversed(TestInvariantWithoutMutations, unittest.TestCase):
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_ci    def setUp(self):
927db96d56Sopenharmony_ci        self.it = reversed(range(n))
937db96d56Sopenharmony_ci
947db96d56Sopenharmony_ciclass TestTuple(TestInvariantWithoutMutations, unittest.TestCase):
957db96d56Sopenharmony_ci
967db96d56Sopenharmony_ci    def setUp(self):
977db96d56Sopenharmony_ci        self.it = iter(tuple(range(n)))
987db96d56Sopenharmony_ci
997db96d56Sopenharmony_ci## ------- Types that should not be mutated during iteration -------
1007db96d56Sopenharmony_ci
1017db96d56Sopenharmony_ciclass TestDeque(TestTemporarilyImmutable, unittest.TestCase):
1027db96d56Sopenharmony_ci
1037db96d56Sopenharmony_ci    def setUp(self):
1047db96d56Sopenharmony_ci        d = deque(range(n))
1057db96d56Sopenharmony_ci        self.it = iter(d)
1067db96d56Sopenharmony_ci        self.mutate = d.pop
1077db96d56Sopenharmony_ci
1087db96d56Sopenharmony_ciclass TestDequeReversed(TestTemporarilyImmutable, unittest.TestCase):
1097db96d56Sopenharmony_ci
1107db96d56Sopenharmony_ci    def setUp(self):
1117db96d56Sopenharmony_ci        d = deque(range(n))
1127db96d56Sopenharmony_ci        self.it = reversed(d)
1137db96d56Sopenharmony_ci        self.mutate = d.pop
1147db96d56Sopenharmony_ci
1157db96d56Sopenharmony_ciclass TestDictKeys(TestTemporarilyImmutable, unittest.TestCase):
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ci    def setUp(self):
1187db96d56Sopenharmony_ci        d = dict.fromkeys(range(n))
1197db96d56Sopenharmony_ci        self.it = iter(d)
1207db96d56Sopenharmony_ci        self.mutate = d.popitem
1217db96d56Sopenharmony_ci
1227db96d56Sopenharmony_ciclass TestDictItems(TestTemporarilyImmutable, unittest.TestCase):
1237db96d56Sopenharmony_ci
1247db96d56Sopenharmony_ci    def setUp(self):
1257db96d56Sopenharmony_ci        d = dict.fromkeys(range(n))
1267db96d56Sopenharmony_ci        self.it = iter(d.items())
1277db96d56Sopenharmony_ci        self.mutate = d.popitem
1287db96d56Sopenharmony_ci
1297db96d56Sopenharmony_ciclass TestDictValues(TestTemporarilyImmutable, unittest.TestCase):
1307db96d56Sopenharmony_ci
1317db96d56Sopenharmony_ci    def setUp(self):
1327db96d56Sopenharmony_ci        d = dict.fromkeys(range(n))
1337db96d56Sopenharmony_ci        self.it = iter(d.values())
1347db96d56Sopenharmony_ci        self.mutate = d.popitem
1357db96d56Sopenharmony_ci
1367db96d56Sopenharmony_ciclass TestSet(TestTemporarilyImmutable, unittest.TestCase):
1377db96d56Sopenharmony_ci
1387db96d56Sopenharmony_ci    def setUp(self):
1397db96d56Sopenharmony_ci        d = set(range(n))
1407db96d56Sopenharmony_ci        self.it = iter(d)
1417db96d56Sopenharmony_ci        self.mutate = d.pop
1427db96d56Sopenharmony_ci
1437db96d56Sopenharmony_ci## ------- Types that can mutate during iteration -------
1447db96d56Sopenharmony_ci
1457db96d56Sopenharmony_ciclass TestList(TestInvariantWithoutMutations, unittest.TestCase):
1467db96d56Sopenharmony_ci
1477db96d56Sopenharmony_ci    def setUp(self):
1487db96d56Sopenharmony_ci        self.it = iter(range(n))
1497db96d56Sopenharmony_ci
1507db96d56Sopenharmony_ci    def test_mutation(self):
1517db96d56Sopenharmony_ci        d = list(range(n))
1527db96d56Sopenharmony_ci        it = iter(d)
1537db96d56Sopenharmony_ci        next(it)
1547db96d56Sopenharmony_ci        next(it)
1557db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), n - 2)
1567db96d56Sopenharmony_ci        d.append(n)
1577db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), n - 1)  # grow with append
1587db96d56Sopenharmony_ci        d[1:] = []
1597db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), 0)
1607db96d56Sopenharmony_ci        self.assertEqual(list(it), [])
1617db96d56Sopenharmony_ci        d.extend(range(20))
1627db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), 0)
1637db96d56Sopenharmony_ci
1647db96d56Sopenharmony_ci
1657db96d56Sopenharmony_ciclass TestListReversed(TestInvariantWithoutMutations, unittest.TestCase):
1667db96d56Sopenharmony_ci
1677db96d56Sopenharmony_ci    def setUp(self):
1687db96d56Sopenharmony_ci        self.it = reversed(range(n))
1697db96d56Sopenharmony_ci
1707db96d56Sopenharmony_ci    def test_mutation(self):
1717db96d56Sopenharmony_ci        d = list(range(n))
1727db96d56Sopenharmony_ci        it = reversed(d)
1737db96d56Sopenharmony_ci        next(it)
1747db96d56Sopenharmony_ci        next(it)
1757db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), n - 2)
1767db96d56Sopenharmony_ci        d.append(n)
1777db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), n - 2)  # ignore append
1787db96d56Sopenharmony_ci        d[1:] = []
1797db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), 0)
1807db96d56Sopenharmony_ci        self.assertEqual(list(it), [])  # confirm invariant
1817db96d56Sopenharmony_ci        d.extend(range(20))
1827db96d56Sopenharmony_ci        self.assertEqual(length_hint(it), 0)
1837db96d56Sopenharmony_ci
1847db96d56Sopenharmony_ci## -- Check to make sure exceptions are not suppressed by __length_hint__()
1857db96d56Sopenharmony_ci
1867db96d56Sopenharmony_ci
1877db96d56Sopenharmony_ciclass BadLen(object):
1887db96d56Sopenharmony_ci    def __iter__(self):
1897db96d56Sopenharmony_ci        return iter(range(10))
1907db96d56Sopenharmony_ci
1917db96d56Sopenharmony_ci    def __len__(self):
1927db96d56Sopenharmony_ci        raise RuntimeError('hello')
1937db96d56Sopenharmony_ci
1947db96d56Sopenharmony_ci
1957db96d56Sopenharmony_ciclass BadLengthHint(object):
1967db96d56Sopenharmony_ci    def __iter__(self):
1977db96d56Sopenharmony_ci        return iter(range(10))
1987db96d56Sopenharmony_ci
1997db96d56Sopenharmony_ci    def __length_hint__(self):
2007db96d56Sopenharmony_ci        raise RuntimeError('hello')
2017db96d56Sopenharmony_ci
2027db96d56Sopenharmony_ci
2037db96d56Sopenharmony_ciclass NoneLengthHint(object):
2047db96d56Sopenharmony_ci    def __iter__(self):
2057db96d56Sopenharmony_ci        return iter(range(10))
2067db96d56Sopenharmony_ci
2077db96d56Sopenharmony_ci    def __length_hint__(self):
2087db96d56Sopenharmony_ci        return NotImplemented
2097db96d56Sopenharmony_ci
2107db96d56Sopenharmony_ci
2117db96d56Sopenharmony_ciclass TestLengthHintExceptions(unittest.TestCase):
2127db96d56Sopenharmony_ci
2137db96d56Sopenharmony_ci    def test_issue1242657(self):
2147db96d56Sopenharmony_ci        self.assertRaises(RuntimeError, list, BadLen())
2157db96d56Sopenharmony_ci        self.assertRaises(RuntimeError, list, BadLengthHint())
2167db96d56Sopenharmony_ci        self.assertRaises(RuntimeError, [].extend, BadLen())
2177db96d56Sopenharmony_ci        self.assertRaises(RuntimeError, [].extend, BadLengthHint())
2187db96d56Sopenharmony_ci        b = bytearray(range(10))
2197db96d56Sopenharmony_ci        self.assertRaises(RuntimeError, b.extend, BadLen())
2207db96d56Sopenharmony_ci        self.assertRaises(RuntimeError, b.extend, BadLengthHint())
2217db96d56Sopenharmony_ci
2227db96d56Sopenharmony_ci    def test_invalid_hint(self):
2237db96d56Sopenharmony_ci        # Make sure an invalid result doesn't muck-up the works
2247db96d56Sopenharmony_ci        self.assertEqual(list(NoneLengthHint()), list(range(10)))
2257db96d56Sopenharmony_ci
2267db96d56Sopenharmony_ci
2277db96d56Sopenharmony_ciif __name__ == "__main__":
2287db96d56Sopenharmony_ci    unittest.main()
229