test_tuple.py 10.6 KB
Newer Older
1
from test import support, seq_tests
2
import unittest
3

4
import gc
5
import pickle
6

7 8 9
class TupleTest(seq_tests.CommonTest):
    type2test = tuple

10 11 12 13 14
    def test_getitem_error(self):
        msg = "tuple indices must be integers or slices"
        with self.assertRaisesRegex(TypeError, msg):
            ()['a']

15
    def test_constructors(self):
16
        super().test_constructors()
17 18
        # calling built-in types without argument must return empty
        self.assertEqual(tuple(), ())
Christian Heimes's avatar
Christian Heimes committed
19 20
        t0_3 = (0, 1, 2, 3)
        t0_3_bis = tuple(t0_3)
21
        self.assertTrue(t0_3 is t0_3_bis)
Christian Heimes's avatar
Christian Heimes committed
22 23 24 25
        self.assertEqual(tuple([]), ())
        self.assertEqual(tuple([0, 1, 2, 3]), (0, 1, 2, 3))
        self.assertEqual(tuple(''), ())
        self.assertEqual(tuple('spam'), ('s', 'p', 'a', 'm'))
26 27 28 29
        self.assertEqual(tuple(x for x in range(10) if x % 2),
                         (1, 3, 5, 7, 9))

    def test_keyword_args(self):
30 31
        with self.assertRaisesRegex(TypeError, 'keyword argument'):
            tuple(sequence=())
32 33

    def test_truth(self):
34
        super().test_truth()
35 36
        self.assertTrue(not ())
        self.assertTrue((42, ))
37 38

    def test_len(self):
39
        super().test_len()
40 41 42 43 44
        self.assertEqual(len(()), 0)
        self.assertEqual(len((0,)), 1)
        self.assertEqual(len((0, 1, 2)), 3)

    def test_iadd(self):
45
        super().test_iadd()
46 47 48
        u = (0, 1)
        u2 = u
        u += (2, 3)
49
        self.assertTrue(u is not u2)
50 51

    def test_imul(self):
52
        super().test_imul()
53 54 55
        u = (0, 1)
        u2 = u
        u *= 3
56
        self.assertTrue(u is not u2)
57 58 59 60 61 62

    def test_tupleresizebug(self):
        # Check that a specific bug in _PyTuple_Resize() is squashed.
        def f():
            for i in range(1000):
                yield i
63
        self.assertEqual(list(tuple(f())), list(range(1000)))
64

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    # Various tests for hashing of tuples to check that we get few collisions.
    #
    # Earlier versions of the tuple hash algorithm had collisions
    # reported at:
    # - https://bugs.python.org/issue942952
    # - https://bugs.python.org/issue34751
    #
    # Notes:
    # - The hash of tuples is deterministic: if the test passes once on a given
    #   system, it will always pass. So the probabilities mentioned in the
    #   test_hash functions below should be interpreted assuming that the
    #   hashes are random.
    # - Due to the structure in the testsuite inputs, collisions are not
    #   independent. For example, if hash((a,b)) == hash((c,d)), then also
    #   hash((a,b,x)) == hash((c,d,x)). But the quoted probabilities assume
    #   independence anyway.
    # - We limit the hash to 32 bits in the tests to have a good test on
    #   64-bit systems too. Furthermore, this is also a sanity check that the
    #   lower 32 bits of a 64-bit hash are sufficiently random too.
    def test_hash1(self):
        # Check for hash collisions between small integers in range(50) and
        # certain tuples and nested tuples of such integers.
87
        N=50
88
        base = list(range(N))
89 90
        xp = [(i, j) for i in base for j in base]
        inps = base + [(i, j) for i in base for j in xp] + \
91
                     [(i, j) for i in xp for j in base] + xp + list(zip(base))
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
        self.assertEqual(len(inps), 252600)
        hashes = set(hash(x) % 2**32 for x in inps)
        collisions = len(inps) - len(hashes)

        # For a pure random 32-bit hash and N = 252,600 test items, the
        # expected number of collisions equals
        #
        # 2**(-32) * N(N-1)/2 = 7.4
        #
        # We allow up to 15 collisions, which suffices to make the test
        # pass with 99.5% confidence.
        self.assertLessEqual(collisions, 15)

    def test_hash2(self):
        # Check for hash collisions between small integers (positive and
        # negative), tuples and nested tuples of such integers.

        # All numbers in the interval [-n, ..., n] except -1 because
        # hash(-1) == hash(-2).
        n = 5
        A = [x for x in range(-n, n+1) if x != -1]

        B = A + [(a,) for a in A]

        L2 = [(a,b) for a in A for b in A]
        L3 = L2 + [(a,b,c) for a in A for b in A for c in A]
        L4 = L3 + [(a,b,c,d) for a in A for b in A for c in A for d in A]

        # T = list of testcases. These consist of all (possibly nested
        # at most 2 levels deep) tuples containing at most 4 items from
        # the set A.
        T = A
        T += [(a,) for a in B + L4]
        T += [(a,b) for a in L3 for b in B]
        T += [(a,b) for a in L2 for b in L2]
        T += [(a,b) for a in B for b in L3]
        T += [(a,b,c) for a in B for b in B for c in L2]
        T += [(a,b,c) for a in B for b in L2 for c in B]
        T += [(a,b,c) for a in L2 for b in B for c in B]
        T += [(a,b,c,d) for a in B for b in B for c in B for d in B]
        self.assertEqual(len(T), 345130)
        hashes = set(hash(x) % 2**32 for x in T)
        collisions = len(T) - len(hashes)

        # For a pure random 32-bit hash and N = 345,130 test items, the
        # expected number of collisions equals
        #
        # 2**(-32) * N(N-1)/2 = 13.9
        #
        # We allow up to 20 collisions, which suffices to make the test
        # pass with 95.5% confidence.
        self.assertLessEqual(collisions, 20)

    def test_hash3(self):
        # Check for hash collisions between tuples containing 0.0 and 0.5.
        # The hashes of 0.0 and 0.5 itself differ only in one high bit.
        # So this implicitly tests propagation of high bits to low bits.
        from itertools import product
        T = list(product([0.0, 0.5], repeat=18))
        self.assertEqual(len(T), 262144)
        hashes = set(hash(x) % 2**32 for x in T)
        collisions = len(T) - len(hashes)

        # For a pure random 32-bit hash and N = 262,144 test items, the
        # expected number of collisions equals
        #
        # 2**(-32) * N(N-1)/2 = 8.0
        #
        # We allow up to 15 collisions, which suffices to make the test
        # pass with 99.1% confidence.
        self.assertLessEqual(collisions, 15)
163

164 165 166 167 168 169 170 171 172 173 174
    def test_repr(self):
        l0 = tuple()
        l2 = (0, 1, 2)
        a0 = self.type2test(l0)
        a2 = self.type2test(l2)

        self.assertEqual(str(a0), repr(l0))
        self.assertEqual(str(a2), repr(l2))
        self.assertEqual(repr(a0), "()")
        self.assertEqual(repr(a2), "(0, 1, 2)")

175 176 177 178 179 180 181 182 183 184 185 186
    def _not_tracked(self, t):
        # Nested tuples can take several collections to untrack
        gc.collect()
        gc.collect()
        self.assertFalse(gc.is_tracked(t), t)

    def _tracked(self, t):
        self.assertTrue(gc.is_tracked(t), t)
        gc.collect()
        gc.collect()
        self.assertTrue(gc.is_tracked(t), t)

187
    @support.cpython_only
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
    def test_track_literals(self):
        # Test GC-optimization of tuple literals
        x, y, z = 1.5, "a", []

        self._not_tracked(())
        self._not_tracked((1,))
        self._not_tracked((1, 2))
        self._not_tracked((1, 2, "a"))
        self._not_tracked((1, 2, (None, True, False, ()), int))
        self._not_tracked((object(),))
        self._not_tracked(((1, x), y, (2, 3)))

        # Tuples with mutable elements are always tracked, even if those
        # elements are not tracked right now.
        self._tracked(([],))
        self._tracked(([1],))
        self._tracked(({},))
        self._tracked((set(),))
        self._tracked((x, y, z))

    def check_track_dynamic(self, tp, always_track):
        x, y, z = 1.5, "a", []

        check = self._tracked if always_track else self._not_tracked
        check(tp())
        check(tp([]))
        check(tp(set()))
        check(tp([1, x, y]))
        check(tp(obj for obj in [1, x, y]))
        check(tp(set([1, x, y])))
        check(tp(tuple([obj]) for obj in [1, x, y]))
        check(tuple(tp([obj]) for obj in [1, x, y]))

        self._tracked(tp([z]))
        self._tracked(tp([[x, y]]))
        self._tracked(tp([{x: y}]))
        self._tracked(tp(obj for obj in [x, y, z]))
        self._tracked(tp(tuple([obj]) for obj in [x, y, z]))
        self._tracked(tuple(tp([obj]) for obj in [x, y, z]))

228
    @support.cpython_only
229 230 231 232
    def test_track_dynamic(self):
        # Test GC-optimization of dynamically constructed tuples.
        self.check_track_dynamic(tuple, False)

233
    @support.cpython_only
234 235 236 237 238 239
    def test_track_subtypes(self):
        # Tuple subtypes must always be tracked
        class MyTuple(tuple):
            pass
        self.check_track_dynamic(MyTuple, True)

240
    @support.cpython_only
241 242 243
    def test_bug7466(self):
        # Trying to untrack an unfinished tuple could crash Python
        self._not_tracked(tuple(gc.collect() for i in range(101)))
244

245 246 247 248 249 250 251 252 253 254
    def test_repr_large(self):
        # Check the repr of large list objects
        def check(n):
            l = (0,) * n
            s = repr(l)
            self.assertEqual(s,
                '(' + ', '.join(['0'] * n) + ')')
        check(10)       # check our checking code
        check(1000000)

255 256 257 258
    def test_iterator_pickle(self):
        # Userlist iterators don't support pickling yet since
        # they are based on generators.
        data = self.type2test([4, 5, 6, 7])
259 260 261 262 263 264 265 266 267 268 269
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            itorg = iter(data)
            d = pickle.dumps(itorg, proto)
            it = pickle.loads(d)
            self.assertEqual(type(itorg), type(it))
            self.assertEqual(self.type2test(it), self.type2test(data))

            it = pickle.loads(d)
            next(it)
            d = pickle.dumps(it, proto)
            self.assertEqual(self.type2test(it), self.type2test(data)[1:])
270 271 272

    def test_reversed_pickle(self):
        data = self.type2test([4, 5, 6, 7])
273 274 275 276 277 278 279 280 281 282 283
        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
            itorg = reversed(data)
            d = pickle.dumps(itorg, proto)
            it = pickle.loads(d)
            self.assertEqual(type(itorg), type(it))
            self.assertEqual(self.type2test(it), self.type2test(reversed(data)))

            it = pickle.loads(d)
            next(it)
            d = pickle.dumps(it, proto)
            self.assertEqual(self.type2test(it), self.type2test(reversed(data))[1:])
284

285 286 287 288 289 290 291 292
    def test_no_comdat_folding(self):
        # Issue 8847: In the PGO build, the MSVC linker's COMDAT folding
        # optimization causes failures in code that relies on distinct
        # function addresses.
        class T(tuple): pass
        with self.assertRaises(TypeError):
            [3,] + T((1,2))

293 294 295 296 297 298 299 300
    def test_lexicographic_ordering(self):
        # Issue 21100
        a = self.type2test([1, 2])
        b = self.type2test([1, 2, 0])
        c = self.type2test([1, 3])
        self.assertLess(a, b)
        self.assertLess(b, c)

301 302
if __name__ == "__main__":
    unittest.main()