| import unittest | |
| import gc | |
| from sys import getrefcount | |
| from test.support import import_helper | |
| _testcapi = import_helper.import_module('_testcapi') | |
| _testlimitedcapi = import_helper.import_module('_testlimitedcapi') | |
| _testinternalcapi = import_helper.import_module('_testinternalcapi') | |
| NULL = None | |
| PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN | |
| PY_SSIZE_T_MAX = _testcapi.PY_SSIZE_T_MAX | |
| class TupleSubclass(tuple): | |
| pass | |
| class CAPITest(unittest.TestCase): | |
| def _not_tracked(self, t): | |
| self.assertFalse(gc.is_tracked(t), t) | |
| def _tracked(self, t): | |
| self.assertTrue(gc.is_tracked(t), t) | |
| def test_check(self): | |
| # Test PyTuple_Check() | |
| check = _testlimitedcapi.tuple_check | |
| self.assertTrue(check((1, 2))) | |
| self.assertTrue(check(())) | |
| self.assertTrue(check(TupleSubclass((1, 2)))) | |
| self.assertFalse(check({1: 2})) | |
| self.assertFalse(check([1, 2])) | |
| self.assertFalse(check(42)) | |
| self.assertFalse(check(object())) | |
| # CRASHES check(NULL) | |
| def test_tuple_checkexact(self): | |
| # Test PyTuple_CheckExact() | |
| check = _testlimitedcapi.tuple_checkexact | |
| self.assertTrue(check((1, 2))) | |
| self.assertTrue(check(())) | |
| self.assertFalse(check(TupleSubclass((1, 2)))) | |
| self.assertFalse(check({1: 2})) | |
| self.assertFalse(check([1, 2])) | |
| self.assertFalse(check(42)) | |
| self.assertFalse(check(object())) | |
| # CRASHES check(NULL) | |
| def test_tuple_new(self): | |
| # Test PyTuple_New() | |
| tuple_new = _testlimitedcapi.tuple_new | |
| size = _testlimitedcapi.tuple_size | |
| checknull = _testcapi._check_tuple_item_is_NULL | |
| tup1 = tuple_new(0) | |
| self.assertEqual(tup1, ()) | |
| self.assertEqual(size(tup1), 0) | |
| self.assertIs(type(tup1), tuple) | |
| self._not_tracked(tup1) | |
| tup2 = tuple_new(1) | |
| self.assertIs(type(tup2), tuple) | |
| self.assertEqual(size(tup2), 1) | |
| self.assertIsNot(tup2, tup1) | |
| self.assertTrue(checknull(tup2, 0)) | |
| self._tracked(tup2) | |
| self.assertRaises(SystemError, tuple_new, -1) | |
| self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) | |
| self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX) | |
| def test_tuple_fromarray(self): | |
| # Test PyTuple_FromArray() | |
| tuple_fromarray = _testcapi.tuple_fromarray | |
| tup = tuple([i] for i in range(5)) | |
| copy = tuple_fromarray(tup) | |
| self.assertEqual(copy, tup) | |
| self._tracked(copy) | |
| tup = tuple(42**i for i in range(5)) | |
| copy = tuple_fromarray(tup) | |
| self.assertEqual(copy, tup) | |
| self._not_tracked(copy) | |
| tup = () | |
| copy = tuple_fromarray(tup) | |
| self.assertIs(copy, tup) | |
| copy = tuple_fromarray(NULL, 0) | |
| self.assertIs(copy, ()) | |
| with self.assertRaises(SystemError): | |
| tuple_fromarray(NULL, -1) | |
| with self.assertRaises(SystemError): | |
| tuple_fromarray(NULL, PY_SSIZE_T_MIN) | |
| with self.assertRaises(MemoryError): | |
| tuple_fromarray(NULL, PY_SSIZE_T_MAX) | |
| def test_tuple_pack(self): | |
| # Test PyTuple_Pack() | |
| pack = _testlimitedcapi.tuple_pack | |
| self.assertEqual(pack(0), ()) | |
| self.assertEqual(pack(1, [1]), ([1],)) | |
| self.assertEqual(pack(2, [1], [2]), ([1], [2])) | |
| self._tracked(pack(1, [1])) | |
| self._tracked(pack(2, [1], b'abc')) | |
| self._not_tracked(pack(2, 42, b'abc')) | |
| self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN) | |
| self.assertRaises(SystemError, pack, -1) | |
| self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX) | |
| # CRASHES pack(1, NULL) | |
| # CRASHES pack(2, [1]) | |
| def check_tuple_from_pair(self, from_pair): | |
| self.assertEqual(type(from_pair(1, 2)), tuple) | |
| self.assertEqual(from_pair(1, 145325), (1, 145325)) | |
| self.assertEqual(from_pair(None, None), (None, None)) | |
| self.assertEqual(from_pair(True, False), (True, False)) | |
| # user class supports gc | |
| class Temp: | |
| pass | |
| temp = Temp() | |
| temp_rc = getrefcount(temp) | |
| self.assertEqual(from_pair(temp, temp), (temp, temp)) | |
| self.assertEqual(getrefcount(temp), temp_rc) | |
| self._not_tracked(from_pair(1, 2)) | |
| self._not_tracked(from_pair(None, None)) | |
| self._not_tracked(from_pair(True, False)) | |
| self._tracked(from_pair(temp, (1, 2))) | |
| self._tracked(from_pair(temp, 1)) | |
| self._tracked(from_pair([], {})) | |
| self.assertRaises(TypeError, from_pair, 1, 2, 3) | |
| self.assertRaises(TypeError, from_pair, 1) | |
| self.assertRaises(TypeError, from_pair) | |
| def test_tuple_from_pair(self): | |
| # Test _PyTuple_FromPair() | |
| from_pair = _testinternalcapi.tuple_from_pair | |
| self.check_tuple_from_pair(from_pair) | |
| def test_tuple_from_pair_steal(self): | |
| # Test _PyTuple_FromPairSteal() | |
| from_pair = _testinternalcapi.tuple_from_pair_steal | |
| self.check_tuple_from_pair(from_pair) | |
| def test_tuple_size(self): | |
| # Test PyTuple_Size() | |
| size = _testlimitedcapi.tuple_size | |
| self.assertEqual(size(()), 0) | |
| self.assertEqual(size((1, 2)), 2) | |
| self.assertEqual(size(TupleSubclass((1, 2))), 2) | |
| self.assertRaises(SystemError, size, []) | |
| self.assertRaises(SystemError, size, 42) | |
| self.assertRaises(SystemError, size, object()) | |
| # CRASHES size(NULL) | |
| def test_tuple_get_size(self): | |
| # Test PyTuple_GET_SIZE() | |
| size = _testcapi.tuple_get_size | |
| self.assertEqual(size(()), 0) | |
| self.assertEqual(size((1, 2)), 2) | |
| self.assertEqual(size(TupleSubclass((1, 2))), 2) | |
| def test_tuple_getitem(self): | |
| # Test PyTuple_GetItem() | |
| getitem = _testlimitedcapi.tuple_getitem | |
| tup = ([1], [2], [3]) | |
| self.assertEqual(getitem(tup, 0), [1]) | |
| self.assertEqual(getitem(tup, 2), [3]) | |
| tup2 = TupleSubclass(([1], [2], [3])) | |
| self.assertEqual(getitem(tup2, 0), [1]) | |
| self.assertEqual(getitem(tup2, 2), [3]) | |
| self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MIN) | |
| self.assertRaises(IndexError, getitem, tup, -1) | |
| self.assertRaises(IndexError, getitem, tup, len(tup)) | |
| self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MAX) | |
| self.assertRaises(SystemError, getitem, [1, 2, 3], 1) | |
| self.assertRaises(SystemError, getitem, 42, 1) | |
| # CRASHES getitem(NULL, 0) | |
| def test_tuple_get_item(self): | |
| # Test PyTuple_GET_ITEM() | |
| get_item = _testcapi.tuple_get_item | |
| tup = ([1], [2], [3]) | |
| self.assertEqual(get_item(tup, 0), [1]) | |
| self.assertEqual(get_item(tup, 2), [3]) | |
| tup2 = TupleSubclass(([1], [2], [3])) | |
| self.assertEqual(get_item(tup2, 0), [1]) | |
| self.assertEqual(get_item(tup2, 2), [3]) | |
| # CRASHES get_item(NULL, 0) | |
| def test_tuple_getslice(self): | |
| # Test PyTuple_GetSlice() | |
| getslice = _testlimitedcapi.tuple_getslice | |
| # empty | |
| tup = ([1], [2], [3]) | |
| self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ()) | |
| self.assertEqual(getslice(tup, -1, 0), ()) | |
| self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ()) | |
| self.assertEqual(getslice(tup, 1, 1), ()) | |
| self.assertEqual(getslice(tup, 2, 1), ()) | |
| tup = TupleSubclass(([1], [2], [3])) | |
| self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ()) | |
| self.assertEqual(getslice(tup, -1, 0), ()) | |
| self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ()) | |
| self.assertEqual(getslice(tup, 1, 1), ()) | |
| self.assertEqual(getslice(tup, 2, 1), ()) | |
| # slice | |
| tup = ([1], [2], [3], [4]) | |
| self.assertEqual(getslice(tup, 1, 3), ([2], [3])) | |
| tup = TupleSubclass(([1], [2], [3], [4])) | |
| self.assertEqual(getslice(tup, 1, 3), ([2], [3])) | |
| # whole | |
| tup = ([1], [2], [3]) | |
| self.assertEqual(getslice(tup, 0, 3), tup) | |
| self.assertEqual(getslice(tup, 0, 100), tup) | |
| self.assertEqual(getslice(tup, -100, 100), tup) | |
| tup = TupleSubclass(([1], [2], [3])) | |
| self.assertEqual(getslice(tup, 0, 3), tup) | |
| self.assertEqual(getslice(tup, 0, 100), tup) | |
| self.assertEqual(getslice(tup, -100, 100), tup) | |
| self.assertRaises(SystemError, getslice, [[1], [2], [3]], 0, 0) | |
| self.assertRaises(SystemError, getslice, 42, 0, 0) | |
| # CRASHES getslice(NULL, 0, 0) | |
| def test_tuple_setitem(self): | |
| # Test PyTuple_SetItem() | |
| setitem = _testlimitedcapi.tuple_setitem | |
| checknull = _testcapi._check_tuple_item_is_NULL | |
| tup = ([1], [2]) | |
| self.assertEqual(setitem(tup, 0, []), ([], [2])) | |
| self.assertEqual(setitem(tup, 1, []), ([1], [])) | |
| tup2 = setitem(tup, 1, NULL) | |
| self.assertTrue(checknull(tup2, 1)) | |
| tup2 = TupleSubclass(([1], [2])) | |
| self.assertRaises(SystemError, setitem, tup2, 0, []) | |
| self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, []) | |
| self.assertRaises(IndexError, setitem, tup, -1, []) | |
| self.assertRaises(IndexError, setitem, tup, len(tup), []) | |
| self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MAX, []) | |
| self.assertRaises(SystemError, setitem, [1], 0, []) | |
| self.assertRaises(SystemError, setitem, 42, 0, []) | |
| # CRASHES setitem(NULL, 0, []) | |
| def test_tuple_set_item(self): | |
| # Test PyTuple_SET_ITEM() | |
| set_item = _testcapi.tuple_set_item | |
| checknull = _testcapi._check_tuple_item_is_NULL | |
| tup = ([1], [2]) | |
| self.assertEqual(set_item(tup, 0, []), ([], [2])) | |
| self.assertEqual(set_item(tup, 1, []), ([1], [])) | |
| tup2 = set_item(tup, 1, NULL) | |
| self.assertTrue(checknull(tup2, 1)) | |
| tup2 = TupleSubclass(([1], [2])) | |
| self.assertIs(set_item(tup2, 0, []), tup2) | |
| self.assertEqual(tup2, ([], [2])) | |
| # CRASHES set_item(tup, -1, []) | |
| # CRASHES set_item(tup, len(tup), []) | |
| # CRASHES set_item([1], 0, []) | |
| # CRASHES set_item(NULL, 0, []) | |
| def test__tuple_resize(self): | |
| # Test _PyTuple_Resize() | |
| resize = _testcapi._tuple_resize | |
| checknull = _testcapi._check_tuple_item_is_NULL | |
| a = () | |
| b = resize(a, 0, False) | |
| self.assertEqual(len(a), 0) | |
| self.assertEqual(len(b), 0) | |
| b = resize(a, 2, False) | |
| self.assertEqual(len(a), 0) | |
| self.assertEqual(len(b), 2) | |
| self.assertTrue(checknull(b, 0)) | |
| self.assertTrue(checknull(b, 1)) | |
| a = ([1], [2], [3]) | |
| b = resize(a, 3) | |
| self.assertEqual(b, a) | |
| b = resize(a, 2) | |
| self.assertEqual(b, a[:2]) | |
| b = resize(a, 5) | |
| self.assertEqual(len(b), 5) | |
| self.assertEqual(b[:3], a) | |
| self.assertTrue(checknull(b, 3)) | |
| self.assertTrue(checknull(b, 4)) | |
| a = () | |
| self.assertRaises(MemoryError, resize, a, PY_SSIZE_T_MAX) | |
| self.assertRaises(SystemError, resize, a, -1) | |
| self.assertRaises(SystemError, resize, a, PY_SSIZE_T_MIN) | |
| # refcount > 1 | |
| a = (1, 2, 3) | |
| self.assertRaises(SystemError, resize, a, 3, False) | |
| self.assertRaises(SystemError, resize, a, 0, False) | |
| # non-tuple | |
| self.assertRaises(SystemError, resize, [1, 2, 3], 0, False) | |
| self.assertRaises(SystemError, resize, NULL, 0, False) | |
| def test_bug_59313(self): | |
| # Before 3.14, the C-API function PySequence_Tuple | |
| # would create incomplete tuples which were visible to | |
| # the cycle GC, and this test would crash the interpeter. | |
| TAG = object() | |
| tuples = [] | |
| def referrer_tuples(): | |
| return [x for x in gc.get_referrers(TAG) | |
| if isinstance(x, tuple)] | |
| def my_iter(): | |
| nonlocal tuples | |
| yield TAG # 'tag' gets stored in the result tuple | |
| tuples += referrer_tuples() | |
| for x in range(10): | |
| tuples += referrer_tuples() | |
| # Prior to 3.13 would raise a SystemError when the tuple needs to be resized | |
| yield x | |
| self.assertEqual(tuple(my_iter()), (TAG, *range(10))) | |
| self.assertEqual(tuples, []) | |
| def test_uninitialized_tuple_repr(self): | |
| tup = _testlimitedcapi.tuple_new(3) | |
| self.assertEqual(repr(tup), '(<NULL>, <NULL>, <NULL>)') | |
| if __name__ == "__main__": | |
| unittest.main() |