| import sys | |
| import unittest | |
| from test.support import import_helper | |
| _testlimitedcapi = import_helper.import_module('_testlimitedcapi') | |
| NULL = None | |
| SSIZE_MAX = sys.maxsize | |
| SSIZE_MIN = -sys.maxsize - 1 | |
| VALUES = [None, 0, 1, 3, 7, -1, -3, -7] | |
| STEPS = [None, 1, 3, 5, -1, -3, -5] | |
| LENGTHS = [0, 1, 3, 10] | |
| class Index: | |
| def __init__(self, value): | |
| self.value = value | |
| def __index__(self): | |
| return self.value | |
| class BadIndex: | |
| def __index__(self): | |
| raise RuntimeError('bad index') | |
| class PopIndex: | |
| # __index__() removes the last item of the list. | |
| def __init__(self, value, seq): | |
| self.value = value | |
| self.seq = seq | |
| def __index__(self): | |
| self.seq.pop() | |
| return self.value | |
| class SliceTest(unittest.TestCase): | |
| def test_check(self): | |
| # Test PySlice_Check() | |
| check = _testlimitedcapi.slice_check | |
| self.assertTrue(check(slice(1, 7, 2))) | |
| self.assertFalse(check(object())) | |
| # CRASHES check(NULL) | |
| def test_new(self): | |
| # Test PySlice_New() | |
| new = _testlimitedcapi.slice_new | |
| self.assertEqual(new(1, 7, 2), slice(1, 7, 2)) | |
| self.assertEqual(new(7, 1, -2), slice(7, 1, -2)) | |
| self.assertEqual(new('a', 'b', 'c'), slice('a', 'b', 'c')) | |
| self.assertEqual(new(NULL, NULL, NULL), slice(None, None, None)) | |
| def test_getindices(self): | |
| # Test PySlice_GetIndices() | |
| getindices = _testlimitedcapi.slice_getindices | |
| self.assertEqual(getindices(slice(1, 7, 2), 10), (1, 7, 2)) | |
| self.assertEqual(getindices(slice(None), 10), (0, 10, 1)) | |
| self.assertEqual(getindices(slice(None, None, -1), 10), (9, -1, -1)) | |
| self.assertEqual(getindices(slice(-3, -1), 10), (7, 9, 1)) | |
| self.assertEqual(getindices(slice(-1, -3, -1), 10), (9, 7, -1)) | |
| self.assertEqual(getindices(slice(None, None, -2), 0), (-1, -1, -2)) | |
| self.assertEqual(getindices(slice(-3, -5, 1), 0), (-3, -5, 1)) | |
| # It fails without setting an exception for out of bounds indices, | |
| # a zero step and non-integer indices. | |
| self.assertIsNone(getindices(slice(1, 11), 10)) | |
| self.assertIsNone(getindices(slice(10, 1), 10)) | |
| self.assertIsNone(getindices(slice(1, 7, 0), 10)) | |
| self.assertIsNone(getindices(slice(Index(1)), 10)) | |
| self.assertIsNone(getindices(slice('a'), 10)) | |
| self.assertIsNone(getindices(slice(1, 'a'), 10)) | |
| self.assertIsNone(getindices(slice(1, 7, 'a'), 10)) | |
| # Negative length is not supported, but does not fail. | |
| self.assertIsNone(getindices(slice(None), -3)) | |
| self.assertIsNone(getindices(slice(1, 7, 2), -3)) | |
| self.assertEqual(getindices(slice(-10, -5, 1), -3), (-13, -8, 1)) | |
| self.assertEqual(getindices(slice(-5, -10, -2), -3), (-8, -13, -2)) | |
| # CRASHES getindices(NULL, 10) | |
| # CRASHES getindices(object(), 10) | |
| def test_unpack(self): | |
| # Test PySlice_Unpack() | |
| unpack = _testlimitedcapi.slice_unpack | |
| self.assertEqual(unpack(slice(1, 7)), (1, 7, 1)) | |
| self.assertEqual(unpack(slice(1, 7, 2)), (1, 7, 2)) | |
| self.assertEqual(unpack(slice(7, 1, -2)), (7, 1, -2)) | |
| self.assertEqual(unpack(slice(None, 7, 2)), (0, 7, 2)) | |
| self.assertEqual(unpack(slice(None, 7, -2)), (SSIZE_MAX, 7, -2)) | |
| self.assertEqual(unpack(slice(1, None, 2)), (1, SSIZE_MAX, 2)) | |
| self.assertEqual(unpack(slice(1, None, -2)), (1, SSIZE_MIN, -2)) | |
| self.assertEqual(unpack(slice(None)), (0, SSIZE_MAX, 1)) | |
| self.assertEqual(unpack(slice(None, None, -1)), | |
| (SSIZE_MAX, SSIZE_MIN, -1)) | |
| # Negative indices are not adjusted. | |
| self.assertEqual(unpack(slice(-3, -1)), (-3, -1, 1)) | |
| self.assertEqual(unpack(slice(Index(1), Index(7), Index(2))), | |
| (1, 7, 2)) | |
| # Values which do not fit in Py_ssize_t are silently clipped. | |
| self.assertEqual(unpack(slice(1, 2**1000)), (1, SSIZE_MAX, 1)) | |
| self.assertEqual(unpack(slice(1, -2**1000)), (1, SSIZE_MIN, 1)) | |
| self.assertEqual(unpack(slice(2**1000, 7)), (SSIZE_MAX, 7, 1)) | |
| self.assertEqual(unpack(slice(-2**1000, 7)), (SSIZE_MIN, 7, 1)) | |
| self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SSIZE_MAX)) | |
| # The step is boosted to -PY_SSIZE_T_MAX, not PY_SSIZE_T_MIN, so | |
| # that negating it is safe. | |
| self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SSIZE_MAX)) | |
| self.assertEqual(unpack(slice(7, 1, SSIZE_MIN)), (7, 1, -SSIZE_MAX)) | |
| with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): | |
| unpack(slice(1, 1, 0)) | |
| with self.assertRaisesRegex(TypeError, | |
| 'slice indices must be integers'): | |
| unpack(slice('a', 7)) | |
| with self.assertRaisesRegex(TypeError, | |
| 'slice indices must be integers'): | |
| unpack(slice(1, 'a')) | |
| with self.assertRaisesRegex(TypeError, | |
| 'slice indices must be integers'): | |
| unpack(slice(1, 7, 'a')) | |
| with self.assertRaisesRegex(RuntimeError, 'bad index'): | |
| unpack(slice(BadIndex(), 7)) | |
| with self.assertRaisesRegex(RuntimeError, 'bad index'): | |
| unpack(slice(1, BadIndex())) | |
| with self.assertRaisesRegex(RuntimeError, 'bad index'): | |
| unpack(slice(1, 7, BadIndex())) | |
| # CRASHES unpack(NULL) | |
| # CRASHES unpack(object()) | |
| def test_adjustindices(self): | |
| # Test PySlice_AdjustIndices() | |
| adjust = _testlimitedcapi.slice_adjustindices | |
| self.assertEqual(adjust(10, 1, 7, 1), (6, 1, 7)) | |
| self.assertEqual(adjust(10, 1, 7, 2), (3, 1, 7)) | |
| self.assertEqual(adjust(10, 7, 1, -1), (6, 7, 1)) | |
| self.assertEqual(adjust(10, 7, 1, -2), (3, 7, 1)) | |
| # An empty slice keeps the adjusted indices. | |
| self.assertEqual(adjust(10, 7, 1, 1), (0, 7, 1)) | |
| self.assertEqual(adjust(10, 1, 7, -1), (0, 1, 7)) | |
| # Negative indices are added to the length. | |
| self.assertEqual(adjust(10, -9, -3, 1), (6, 1, 7)) | |
| self.assertEqual(adjust(10, -3, -9, -1), (6, 7, 1)) | |
| # Out of bounds indices are clipped. | |
| self.assertEqual(adjust(10, -100, 100, 1), (10, 0, 10)) | |
| self.assertEqual(adjust(10, 100, -100, -1), (10, 9, -1)) | |
| self.assertEqual(adjust(10, SSIZE_MIN, SSIZE_MAX, 1), (10, 0, 10)) | |
| self.assertEqual(adjust(10, SSIZE_MAX, SSIZE_MIN, -1), (10, 9, -1)) | |
| self.assertEqual(adjust(0, 1, 7, 1), (0, 0, 0)) | |
| self.assertEqual(adjust(0, 7, 1, -1), (0, -1, -1)) | |
| # The returned length is the length of the corresponding range. | |
| for length in LENGTHS: | |
| for start in VALUES[1:]: | |
| for stop in VALUES[1:]: | |
| for step in STEPS[1:]: | |
| with self.subTest(length=length, start=start, | |
| stop=stop, step=step): | |
| slicelength, start2, stop2 = adjust(length, start, | |
| stop, step) | |
| self.assertEqual(slicelength, | |
| len(range(start2, stop2, step))) | |
| # Negative length is not supported, but does not fail. | |
| self.assertEqual(adjust(-3, 1, 7, 1), (0, -3, -3)) | |
| self.assertEqual(adjust(-3, 7, 1, -1), (0, -4, -4)) | |
| self.assertEqual(adjust(-3, -10, -5, 1), (0, 0, 0)) | |
| # The step is asserted to be neither zero nor less than | |
| # -PY_SSIZE_T_MAX. | |
| # CRASHES adjust(10, 0, 10, 0) | |
| # CRASHES adjust(10, 0, 10, SSIZE_MIN) | |
| class GetIndicesExMacroTest(unittest.TestCase): | |
| # PySlice_GetIndicesEx() is a macro using PySlice_Unpack() and | |
| # PySlice_AdjustIndices(). It is also a deprecated function, exported | |
| # for the stable ABI. | |
| getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_macro) | |
| getindicesex_seq = staticmethod( | |
| _testlimitedcapi.slice_getindicesex_seq_macro) | |
| # The macro evaluates the length after calling PySlice_Unpack(), so the | |
| # size of the list after removing an item is used. | |
| resized = (6, 8, 1, 2) | |
| def test_getindicesex(self): | |
| # Test PySlice_GetIndicesEx() | |
| getindicesex = self.getindicesex | |
| self.assertEqual(getindicesex(slice(1, 7, 2), 10), (1, 7, 2, 3)) | |
| self.assertEqual(getindicesex(slice(7, 1, -2), 10), (7, 1, -2, 3)) | |
| self.assertEqual(getindicesex(slice(Index(1), Index(7), Index(2)), 10), | |
| (1, 7, 2, 3)) | |
| # The result agrees with slice.indices() and the slice length is | |
| # the length of the corresponding range. | |
| for length in LENGTHS: | |
| for start in VALUES: | |
| for stop in VALUES: | |
| for step in STEPS: | |
| s = slice(start, stop, step) | |
| with self.subTest(slice=s, length=length): | |
| indices = s.indices(length) | |
| self.assertEqual(getindicesex(s, length), | |
| indices + (len(range(*indices)),)) | |
| # Negative indices are added to the length. | |
| self.assertEqual(getindicesex(slice(-9, -3), 10), (1, 7, 1, 6)) | |
| self.assertEqual(getindicesex(slice(-3, -9, -1), 10), (7, 1, -1, 6)) | |
| # Out of bounds indices are clipped. | |
| self.assertEqual(getindicesex(slice(-100, 100), 10), (0, 10, 1, 10)) | |
| self.assertEqual(getindicesex(slice(100, -100, -1), 10), | |
| (9, -1, -1, 10)) | |
| self.assertEqual(getindicesex(slice(None), 0), (0, 0, 1, 0)) | |
| self.assertEqual(getindicesex(slice(1, 7, 2), 0), (0, 0, 2, 0)) | |
| self.assertEqual(getindicesex(slice(None, None, -1), 0), | |
| (-1, -1, -1, 0)) | |
| # Indices which do not fit in Py_ssize_t are clipped, not rejected. | |
| # Note that slice.indices() does not clip the step. | |
| self.assertEqual(getindicesex(slice(1, 2**1000), 10), (1, 10, 1, 9)) | |
| self.assertEqual(getindicesex(slice(2**1000, 7), 10), (10, 7, 1, 0)) | |
| self.assertEqual(getindicesex(slice(1, 7, 2**1000), 10), | |
| (1, 7, SSIZE_MAX, 1)) | |
| # -PY_SSIZE_T_MAX-1 is replaced with -PY_SSIZE_T_MAX. | |
| self.assertEqual(getindicesex(slice(7, 1, -2**1000), 10), | |
| (7, 1, -SSIZE_MAX, 1)) | |
| self.assertEqual(getindicesex(slice(7, 1, SSIZE_MIN), 10), | |
| (7, 1, -SSIZE_MAX, 1)) | |
| with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): | |
| getindicesex(slice(1, 7, 0), 10) | |
| with self.assertRaisesRegex(TypeError, | |
| 'slice indices must be integers'): | |
| getindicesex(slice('a', 7), 10) | |
| with self.assertRaisesRegex(TypeError, | |
| 'slice indices must be integers'): | |
| getindicesex(slice(1, 'a'), 10) | |
| with self.assertRaisesRegex(TypeError, | |
| 'slice indices must be integers'): | |
| getindicesex(slice(1, 7, 'a'), 10) | |
| with self.assertRaisesRegex(RuntimeError, 'bad index'): | |
| getindicesex(slice(BadIndex(), 7), 10) | |
| with self.assertRaisesRegex(RuntimeError, 'bad index'): | |
| getindicesex(slice(1, BadIndex()), 10) | |
| with self.assertRaisesRegex(RuntimeError, 'bad index'): | |
| getindicesex(slice(1, 7, BadIndex()), 10) | |
| # Negative length is not supported, but does not fail. | |
| self.assertEqual(getindicesex(slice(None), -3), (-3, -3, 1, 0)) | |
| self.assertEqual(getindicesex(slice(1, 7, 2), -3), (-3, -3, 2, 0)) | |
| self.assertEqual(getindicesex(slice(7, 1, -2), -3), (-4, -4, -2, 0)) | |
| # CRASHES getindicesex(NULL, 10) | |
| # CRASHES getindicesex(object(), 10) | |
| def test_getindicesex_seq(self): | |
| # The length is the size of a sequence. | |
| getindicesex_seq = self.getindicesex_seq | |
| seq = list(range(10)) | |
| self.assertEqual(getindicesex_seq(slice(-3, -1), seq), (7, 9, 1, 2)) | |
| self.assertEqual(getindicesex_seq(slice(-3, -1), []), (0, 0, 1, 0)) | |
| # gh-72054: __index__() can resize the sequence. Negative indices | |
| # are adjusted by the length, so the result depends on when it is | |
| # evaluated. | |
| seq = list(range(10)) | |
| self.assertEqual(getindicesex_seq(slice(PopIndex(-3, seq), -1), seq), | |
| self.resized) | |
| self.assertEqual(len(seq), 9, seq) | |
| seq = list(range(10)) | |
| self.assertEqual(getindicesex_seq(slice(-3, PopIndex(-1, seq)), seq), | |
| self.resized) | |
| self.assertEqual(len(seq), 9, seq) | |
| seq = list(range(10)) | |
| self.assertEqual( | |
| getindicesex_seq(slice(-3, -1, PopIndex(1, seq)), seq), | |
| self.resized) | |
| self.assertEqual(len(seq), 9, seq) | |
| # CRASHES getindicesex_seq(slice(None), NULL) | |
| # CRASHES getindicesex_seq(slice(None), object()) | |
| class GetIndicesExFuncTest(GetIndicesExMacroTest): | |
| # The deprecated function is equivalent to the macro, except that the | |
| # length is evaluated before the call. | |
| getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_func) | |
| getindicesex_seq = staticmethod( | |
| _testlimitedcapi.slice_getindicesex_seq_func) | |
| # The size of the list before removing an item is used. | |
| resized = (7, 9, 1, 2) | |
| if __name__ == "__main__": | |
| unittest.main() |