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