1# tests for slice objects; in particular the indices method.
2
3import itertools
4import operator
5import sys
6import unittest
7import weakref
8import copy
9
10from pickle import loads, dumps
11from test import support
12
13
14def evaluate_slice_index(arg):
15    """
16    Helper function to convert a slice argument to an integer, and raise
17    TypeError with a suitable message on failure.
18
19    """
20    if hasattr(arg, '__index__'):
21        return operator.index(arg)
22    else:
23        raise TypeError(
24            "slice indices must be integers or "
25            "None or have an __index__ method")
26
27def slice_indices(slice, length):
28    """
29    Reference implementation for the slice.indices method.
30
31    """
32    # Compute step and length as integers.
33    length = operator.index(length)
34    step = 1 if slice.step is None else evaluate_slice_index(slice.step)
35
36    # Raise ValueError for negative length or zero step.
37    if length < 0:
38        raise ValueError("length should not be negative")
39    if step == 0:
40        raise ValueError("slice step cannot be zero")
41
42    # Find lower and upper bounds for start and stop.
43    lower = -1 if step < 0 else 0
44    upper = length - 1 if step < 0 else length
45
46    # Compute start.
47    if slice.start is None:
48        start = upper if step < 0 else lower
49    else:
50        start = evaluate_slice_index(slice.start)
51        start = max(start + length, lower) if start < 0 else min(start, upper)
52
53    # Compute stop.
54    if slice.stop is None:
55        stop = lower if step < 0 else upper
56    else:
57        stop = evaluate_slice_index(slice.stop)
58        stop = max(stop + length, lower) if stop < 0 else min(stop, upper)
59
60    return start, stop, step
61
62
63# Class providing an __index__ method.  Used for testing slice.indices.
64
65class MyIndexable(object):
66    def __init__(self, value):
67        self.value = value
68
69    def __index__(self):
70        return self.value
71
72
73class SliceTest(unittest.TestCase):
74
75    def test_constructor(self):
76        self.assertRaises(TypeError, slice)
77        self.assertRaises(TypeError, slice, 1, 2, 3, 4)
78
79    def test_repr(self):
80        self.assertEqual(repr(slice(1, 2, 3)), "slice(1, 2, 3)")
81
82    def test_hash(self):
83        # Verify clearing of SF bug #800796
84        self.assertRaises(TypeError, hash, slice(5))
85        with self.assertRaises(TypeError):
86            slice(5).__hash__()
87
88    def test_cmp(self):
89        s1 = slice(1, 2, 3)
90        s2 = slice(1, 2, 3)
91        s3 = slice(1, 2, 4)
92        self.assertEqual(s1, s2)
93        self.assertNotEqual(s1, s3)
94        self.assertNotEqual(s1, None)
95        self.assertNotEqual(s1, (1, 2, 3))
96        self.assertNotEqual(s1, "")
97
98        class Exc(Exception):
99            pass
100
101        class BadCmp(object):
102            def __eq__(self, other):
103                raise Exc
104
105        s1 = slice(BadCmp())
106        s2 = slice(BadCmp())
107        self.assertEqual(s1, s1)
108        self.assertRaises(Exc, lambda: s1 == s2)
109
110        s1 = slice(1, BadCmp())
111        s2 = slice(1, BadCmp())
112        self.assertEqual(s1, s1)
113        self.assertRaises(Exc, lambda: s1 == s2)
114
115        s1 = slice(1, 2, BadCmp())
116        s2 = slice(1, 2, BadCmp())
117        self.assertEqual(s1, s1)
118        self.assertRaises(Exc, lambda: s1 == s2)
119
120    def test_members(self):
121        s = slice(1)
122        self.assertEqual(s.start, None)
123        self.assertEqual(s.stop, 1)
124        self.assertEqual(s.step, None)
125
126        s = slice(1, 2)
127        self.assertEqual(s.start, 1)
128        self.assertEqual(s.stop, 2)
129        self.assertEqual(s.step, None)
130
131        s = slice(1, 2, 3)
132        self.assertEqual(s.start, 1)
133        self.assertEqual(s.stop, 2)
134        self.assertEqual(s.step, 3)
135
136        class AnyClass:
137            pass
138
139        obj = AnyClass()
140        s = slice(obj)
141        self.assertTrue(s.stop is obj)
142
143    def check_indices(self, slice, length):
144        try:
145            actual = slice.indices(length)
146        except ValueError:
147            actual = "valueerror"
148        try:
149            expected = slice_indices(slice, length)
150        except ValueError:
151            expected = "valueerror"
152        self.assertEqual(actual, expected)
153
154        if length >= 0 and slice.step != 0:
155            actual = range(*slice.indices(length))
156            expected = range(length)[slice]
157            self.assertEqual(actual, expected)
158
159    def test_indices(self):
160        self.assertEqual(slice(None           ).indices(10), (0, 10,  1))
161        self.assertEqual(slice(None,  None,  2).indices(10), (0, 10,  2))
162        self.assertEqual(slice(1,     None,  2).indices(10), (1, 10,  2))
163        self.assertEqual(slice(None,  None, -1).indices(10), (9, -1, -1))
164        self.assertEqual(slice(None,  None, -2).indices(10), (9, -1, -2))
165        self.assertEqual(slice(3,     None, -2).indices(10), (3, -1, -2))
166        # issue 3004 tests
167        self.assertEqual(slice(None, -9).indices(10), (0, 1, 1))
168        self.assertEqual(slice(None, -10).indices(10), (0, 0, 1))
169        self.assertEqual(slice(None, -11).indices(10), (0, 0, 1))
170        self.assertEqual(slice(None, -10, -1).indices(10), (9, 0, -1))
171        self.assertEqual(slice(None, -11, -1).indices(10), (9, -1, -1))
172        self.assertEqual(slice(None, -12, -1).indices(10), (9, -1, -1))
173        self.assertEqual(slice(None, 9).indices(10), (0, 9, 1))
174        self.assertEqual(slice(None, 10).indices(10), (0, 10, 1))
175        self.assertEqual(slice(None, 11).indices(10), (0, 10, 1))
176        self.assertEqual(slice(None, 8, -1).indices(10), (9, 8, -1))
177        self.assertEqual(slice(None, 9, -1).indices(10), (9, 9, -1))
178        self.assertEqual(slice(None, 10, -1).indices(10), (9, 9, -1))
179
180        self.assertEqual(
181            slice(-100,  100     ).indices(10),
182            slice(None).indices(10)
183        )
184        self.assertEqual(
185            slice(100,  -100,  -1).indices(10),
186            slice(None, None, -1).indices(10)
187        )
188        self.assertEqual(slice(-100, 100, 2).indices(10), (0, 10,  2))
189
190        self.assertEqual(list(range(10))[::sys.maxsize - 1], [0])
191
192        # Check a variety of start, stop, step and length values, including
193        # values exceeding sys.maxsize (see issue #14794).
194        vals = [None, -2**100, -2**30, -53, -7, -1, 0, 1, 7, 53, 2**30, 2**100]
195        lengths = [0, 1, 7, 53, 2**30, 2**100]
196        for slice_args in itertools.product(vals, repeat=3):
197            s = slice(*slice_args)
198            for length in lengths:
199                self.check_indices(s, length)
200        self.check_indices(slice(0, 10, 1), -3)
201
202        # Negative length should raise ValueError
203        with self.assertRaises(ValueError):
204            slice(None).indices(-1)
205
206        # Zero step should raise ValueError
207        with self.assertRaises(ValueError):
208            slice(0, 10, 0).indices(5)
209
210        # Using a start, stop or step or length that can't be interpreted as an
211        # integer should give a TypeError ...
212        with self.assertRaises(TypeError):
213            slice(0.0, 10, 1).indices(5)
214        with self.assertRaises(TypeError):
215            slice(0, 10.0, 1).indices(5)
216        with self.assertRaises(TypeError):
217            slice(0, 10, 1.0).indices(5)
218        with self.assertRaises(TypeError):
219            slice(0, 10, 1).indices(5.0)
220
221        # ... but it should be fine to use a custom class that provides index.
222        self.assertEqual(slice(0, 10, 1).indices(5), (0, 5, 1))
223        self.assertEqual(slice(MyIndexable(0), 10, 1).indices(5), (0, 5, 1))
224        self.assertEqual(slice(0, MyIndexable(10), 1).indices(5), (0, 5, 1))
225        self.assertEqual(slice(0, 10, MyIndexable(1)).indices(5), (0, 5, 1))
226        self.assertEqual(slice(0, 10, 1).indices(MyIndexable(5)), (0, 5, 1))
227
228    def test_setslice_without_getslice(self):
229        tmp = []
230        class X(object):
231            def __setitem__(self, i, k):
232                tmp.append((i, k))
233
234        x = X()
235        x[1:2] = 42
236        self.assertEqual(tmp, [(slice(1, 2), 42)])
237
238    def test_pickle(self):
239        import pickle
240
241        s = slice(10, 20, 3)
242        for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
243            t = loads(dumps(s, protocol))
244            self.assertEqual(s, t)
245            self.assertEqual(s.indices(15), t.indices(15))
246            self.assertNotEqual(id(s), id(t))
247
248    def test_copy(self):
249        s = slice(1, 10)
250        c = copy.copy(s)
251        self.assertIs(s, c)
252
253        s = slice(1, 10, 2)
254        c = copy.copy(s)
255        self.assertIs(s, c)
256
257        # Corner case for mutable indices:
258        s = slice([1, 2], [3, 4], [5, 6])
259        c = copy.copy(s)
260        self.assertIs(s, c)
261        self.assertIs(s.start, c.start)
262        self.assertIs(s.stop, c.stop)
263        self.assertIs(s.step, c.step)
264
265    def test_deepcopy(self):
266        s = slice(1, 10)
267        c = copy.deepcopy(s)
268        self.assertEqual(s, c)
269
270        s = slice(1, 10, 2)
271        c = copy.deepcopy(s)
272        self.assertEqual(s, c)
273
274        # Corner case for mutable indices:
275        s = slice([1, 2], [3, 4], [5, 6])
276        c = copy.deepcopy(s)
277        self.assertIsNot(s, c)
278        self.assertEqual(s, c)
279        self.assertIsNot(s.start, c.start)
280        self.assertIsNot(s.stop, c.stop)
281        self.assertIsNot(s.step, c.step)
282
283    def test_cycle(self):
284        class myobj(): pass
285        o = myobj()
286        o.s = slice(o)
287        w = weakref.ref(o)
288        o = None
289        support.gc_collect()
290        self.assertIsNone(w())
291
292if __name__ == "__main__":
293    unittest.main()
294