--- a/Lib/test/test_slice.py Fri Nov 02 14:49:02 2012 +0100 +++ b/Lib/test/test_slice.py Sat Nov 03 19:16:32 2012 +0000 @@ -4,8 +4,56 @@ from test import support from pickle import loads, dumps +import operator import sys + +def slice_indices(slice, length): + """ + Python reference implementation of the slice.indices method. + + """ + # Compute step and length as integers. + step = 1 if slice.step is None else operator.index(slice.step) + length = operator.index(length) + + # Check error conditions. + if step == 0: + raise ValueError("slice step cannot be zero") + if length < 0: + raise ValueError("length should be nonnegative") + + # Get lower and upper bounds for start and stop. + lower = -1 if step < 0 else 0 + upper = length - 1 if step < 0 else length + + # Compute start. + if slice.start is None: + start = upper if step < 0 else lower + else: + start = operator.index(slice.start) + start = max(start + length, lower) if start < 0 else min(start, upper) + + # Compute stop. + if slice.stop is None: + stop = lower if step < 0 else upper + else: + stop = operator.index(slice.stop) + stop = max(stop + length, lower) if stop < 0 else min(stop, upper) + + return start, stop, step + + +# Class providing an __index__ method. Used in testing. + +class MyIndexable(object): + def __init__(self, value): + self.value = value + + def __index__(self): + return self.value + + class SliceTest(unittest.TestCase): def test_constructor(self): @@ -75,6 +123,27 @@ s = slice(obj) self.assertTrue(s.stop is obj) + def check_indices(self, slice, length): + try: + expected = slice.indices(length) + except ValueError: + expected = "valueerror" + try: + actual = slice_indices(slice, length) + except ValueError: + actual = "valueerror" + + self.assertEqual(actual, expected) + + if slice.step == 0: + return + + # Check the indices we're getting. + self.assertEqual( + range(*slice_indices(slice, length)), + range(length)[slice], + ) + def test_indices(self): self.assertEqual(slice(None ).indices(10), (0, 10, 1)) self.assertEqual(slice(None, None, 2).indices(10), (0, 10, 2)) @@ -108,7 +177,42 @@ self.assertEqual(list(range(10))[::sys.maxsize - 1], [0]) - self.assertRaises(OverflowError, slice(None).indices, 1<<100) + # Check a variety of start, stop, step and length values, including + # values exceeding sys.maxsize (see issue #14794). + vals = [None, -2**100, -2**30, -53, -7, -1, 0, 1, 7, 53, 2**30, 2**100] + lengths = [0, 1, 7, 53, 2**30, 2**100] + for start in vals: + for stop in vals: + for step in vals: + s = slice(start, stop, step) + for length in lengths: + self.check_indices(s, length) + + # Negative length should raise ValueError + with self.assertRaises(ValueError): + slice(None).indices(-1) + + # Zero step should raise ValueError + with self.assertRaises(ValueError): + slice(0, 10, 0).indices(5) + + # Using a start, stop or step or length that can't be interpreted as an + # integer should give a TypeError ... + with self.assertRaises(TypeError): + slice(0.0, 10, 1).indices(5) + with self.assertRaises(TypeError): + slice(0, 10.0, 1).indices(5) + with self.assertRaises(TypeError): + slice(0, 10, 1.0).indices(5) + with self.assertRaises(TypeError): + slice(0, 10, 1).indices(5.0) + + # ... but it should be fine to use a custom class that provides index. + self.assertEqual(slice(0, 10, 1).indices(5), (0, 5, 1)) + self.assertEqual(slice(MyIndexable(0), 10, 1).indices(5), (0, 5, 1)) + self.assertEqual(slice(0, MyIndexable(10), 1).indices(5), (0, 5, 1)) + self.assertEqual(slice(0, 10, MyIndexable(1)).indices(5), (0, 5, 1)) + self.assertEqual(slice(0, 10, 1).indices(MyIndexable(5)), (0, 5, 1)) def test_setslice_without_getslice(self): tmp = []