Skip to content

Commit 1d8ab25

Browse files
authored
[3.12] gh-111495: Add tests for PyNumber C API (GH-111996) (#123376)
(cherry picked from commit 2f20f5a)
1 parent 625072f commit 1d8ab25

File tree

2 files changed

+496
-0
lines changed

2 files changed

+496
-0
lines changed

Lib/test/test_capi/test_number.py

+335
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
import itertools
2+
import operator
3+
import sys
4+
import unittest
5+
import warnings
6+
7+
from test.support import cpython_only, import_helper
8+
9+
_testcapi = import_helper.import_module('_testcapi')
10+
from _testcapi import PY_SSIZE_T_MAX, PY_SSIZE_T_MIN
11+
12+
try:
13+
from _testbuffer import ndarray
14+
except ImportError:
15+
ndarray = None
16+
17+
NULL = None
18+
19+
class BadDescr:
20+
def __get__(self, obj, objtype=None):
21+
raise RuntimeError
22+
23+
class WithDunder:
24+
def _meth(self, *args):
25+
if self.val:
26+
return self.val
27+
if self.exc:
28+
raise self.exc
29+
@classmethod
30+
def with_val(cls, val):
31+
obj = super().__new__(cls)
32+
obj.val = val
33+
obj.exc = None
34+
setattr(cls, cls.methname, cls._meth)
35+
return obj
36+
37+
@classmethod
38+
def with_exc(cls, exc):
39+
obj = super().__new__(cls)
40+
obj.val = None
41+
obj.exc = exc
42+
setattr(cls, cls.methname, cls._meth)
43+
return obj
44+
45+
class HasBadAttr:
46+
def __new__(cls):
47+
obj = super().__new__(cls)
48+
setattr(cls, cls.methname, BadDescr())
49+
return obj
50+
51+
52+
class IndexLike(WithDunder):
53+
methname = '__index__'
54+
55+
class IntLike(WithDunder):
56+
methname = '__int__'
57+
58+
class FloatLike(WithDunder):
59+
methname = '__float__'
60+
61+
62+
def subclassof(base):
63+
return type(base.__name__ + 'Subclass', (base,), {})
64+
65+
66+
class SomeError(Exception):
67+
pass
68+
69+
class OtherError(Exception):
70+
pass
71+
72+
73+
class CAPITest(unittest.TestCase):
74+
def test_check(self):
75+
# Test PyNumber_Check()
76+
check = _testcapi.number_check
77+
78+
self.assertTrue(check(1))
79+
self.assertTrue(check(IndexLike.with_val(1)))
80+
self.assertTrue(check(IntLike.with_val(99)))
81+
self.assertTrue(check(0.5))
82+
self.assertTrue(check(FloatLike.with_val(4.25)))
83+
self.assertTrue(check(1+2j))
84+
85+
self.assertFalse(check([]))
86+
self.assertFalse(check("abc"))
87+
self.assertFalse(check(object()))
88+
self.assertFalse(check(NULL))
89+
90+
def test_unary_ops(self):
91+
methmap = {'__neg__': _testcapi.number_negative, # PyNumber_Negative()
92+
'__pos__': _testcapi.number_positive, # PyNumber_Positive()
93+
'__abs__': _testcapi.number_absolute, # PyNumber_Absolute()
94+
'__invert__': _testcapi.number_invert} # PyNumber_Invert()
95+
96+
for name, func in methmap.items():
97+
# Generic object, has no tp_as_number structure
98+
self.assertRaises(TypeError, func, object())
99+
100+
# C-API function accepts NULL
101+
self.assertRaises(SystemError, func, NULL)
102+
103+
# Behave as corresponding unary operation
104+
op = getattr(operator, name)
105+
for x in [0, 42, -1, 3.14, 1+2j]:
106+
try:
107+
op(x)
108+
except TypeError:
109+
self.assertRaises(TypeError, func, x)
110+
else:
111+
self.assertEqual(func(x), op(x))
112+
113+
def test_binary_ops(self):
114+
methmap = {'__add__': _testcapi.number_add, # PyNumber_Add()
115+
'__sub__': _testcapi.number_subtract, # PyNumber_Subtract()
116+
'__mul__': _testcapi.number_multiply, # PyNumber_Multiply()
117+
'__matmul__': _testcapi.number_matrixmultiply, # PyNumber_MatrixMultiply()
118+
'__floordiv__': _testcapi.number_floordivide, # PyNumber_FloorDivide()
119+
'__truediv__': _testcapi.number_truedivide, # PyNumber_TrueDivide()
120+
'__mod__': _testcapi.number_remainder, # PyNumber_Remainder()
121+
'__divmod__': _testcapi.number_divmod, # PyNumber_Divmod()
122+
'__lshift__': _testcapi.number_lshift, # PyNumber_Lshift()
123+
'__rshift__': _testcapi.number_rshift, # PyNumber_Rshift()
124+
'__and__': _testcapi.number_and, # PyNumber_And()
125+
'__xor__': _testcapi.number_xor, # PyNumber_Xor()
126+
'__or__': _testcapi.number_or, # PyNumber_Or()
127+
'__pow__': _testcapi.number_power, # PyNumber_Power()
128+
'__iadd__': _testcapi.number_inplaceadd, # PyNumber_InPlaceAdd()
129+
'__isub__': _testcapi.number_inplacesubtract, # PyNumber_InPlaceSubtract()
130+
'__imul__': _testcapi.number_inplacemultiply, # PyNumber_InPlaceMultiply()
131+
'__imatmul__': _testcapi.number_inplacematrixmultiply, # PyNumber_InPlaceMatrixMultiply()
132+
'__ifloordiv__': _testcapi.number_inplacefloordivide, # PyNumber_InPlaceFloorDivide()
133+
'__itruediv__': _testcapi.number_inplacetruedivide, # PyNumber_InPlaceTrueDivide()
134+
'__imod__': _testcapi.number_inplaceremainder, # PyNumber_InPlaceRemainder()
135+
'__ilshift__': _testcapi.number_inplacelshift, # PyNumber_InPlaceLshift()
136+
'__irshift__': _testcapi.number_inplacershift, # PyNumber_InPlaceRshift()
137+
'__iand__': _testcapi.number_inplaceand, # PyNumber_InPlaceAnd()
138+
'__ixor__': _testcapi.number_inplacexor, # PyNumber_InPlaceXor()
139+
'__ior__': _testcapi.number_inplaceor, # PyNumber_InPlaceOr()
140+
'__ipow__': _testcapi.number_inplacepower, # PyNumber_InPlacePower()
141+
}
142+
143+
for name, func in methmap.items():
144+
cases = [0, 42, 3.14, -1, 123, 1+2j]
145+
146+
# Generic object, has no tp_as_number structure
147+
for x in cases:
148+
self.assertRaises(TypeError, func, object(), x)
149+
self.assertRaises(TypeError, func, x, object())
150+
151+
# Behave as corresponding binary operation
152+
op = getattr(operator, name, divmod)
153+
for x, y in itertools.combinations(cases, 2):
154+
try:
155+
op(x, y)
156+
except (TypeError, ValueError, ZeroDivisionError) as exc:
157+
self.assertRaises(exc.__class__, func, x, y)
158+
else:
159+
self.assertEqual(func(x, y), op(x, y))
160+
161+
# CRASHES func(NULL, object())
162+
# CRASHES func(object(), NULL)
163+
164+
@unittest.skipIf(ndarray is None, "needs _testbuffer")
165+
def test_misc_add(self):
166+
# PyNumber_Add(), PyNumber_InPlaceAdd()
167+
add = _testcapi.number_add
168+
inplaceadd = _testcapi.number_inplaceadd
169+
170+
# test sq_concat/sq_inplace_concat slots
171+
a, b, r = [1, 2], [3, 4], [1, 2, 3, 4]
172+
self.assertEqual(add(a, b), r)
173+
self.assertEqual(a, [1, 2])
174+
self.assertRaises(TypeError, add, ndarray([1], (1,)), 2)
175+
a, b, r = [1, 2], [3, 4], [1, 2, 3, 4]
176+
self.assertEqual(inplaceadd(a, b), r)
177+
self.assertEqual(a, r)
178+
self.assertRaises(TypeError, inplaceadd, ndarray([1], (1,)), 2)
179+
180+
@unittest.skipIf(ndarray is None, "needs _testbuffer")
181+
def test_misc_multiply(self):
182+
# PyNumber_Multiply(), PyNumber_InPlaceMultiply()
183+
multiply = _testcapi.number_multiply
184+
inplacemultiply = _testcapi.number_inplacemultiply
185+
186+
# test sq_repeat/sq_inplace_repeat slots
187+
a, b, r = [1], 2, [1, 1]
188+
self.assertEqual(multiply(a, b), r)
189+
self.assertEqual((a, b), ([1], 2))
190+
self.assertEqual(multiply(b, a), r)
191+
self.assertEqual((a, b), ([1], 2))
192+
self.assertEqual(multiply([1], -1), [])
193+
self.assertRaises(TypeError, multiply, ndarray([1], (1,)), 2)
194+
self.assertRaises(TypeError, multiply, [1], 0.5)
195+
self.assertRaises(OverflowError, multiply, [1], PY_SSIZE_T_MAX + 1)
196+
self.assertRaises(MemoryError, multiply, [1, 2], PY_SSIZE_T_MAX//2 + 1)
197+
a, b, r = [1], 2, [1, 1]
198+
self.assertEqual(inplacemultiply(a, b), r)
199+
self.assertEqual((a, b), (r, 2))
200+
a = [1]
201+
self.assertEqual(inplacemultiply(b, a), r)
202+
self.assertEqual((a, b), ([1], 2))
203+
self.assertRaises(TypeError, inplacemultiply, ndarray([1], (1,)), 2)
204+
self.assertRaises(OverflowError, inplacemultiply, [1], PY_SSIZE_T_MAX + 1)
205+
self.assertRaises(MemoryError, inplacemultiply, [1, 2], PY_SSIZE_T_MAX//2 + 1)
206+
207+
def test_misc_power(self):
208+
# PyNumber_Power()
209+
power = _testcapi.number_power
210+
211+
class HasPow(WithDunder):
212+
methname = '__pow__'
213+
214+
# ternary op
215+
self.assertEqual(power(4, 11, 5), pow(4, 11, 5))
216+
self.assertRaises(TypeError, power, 4, 11, 1.25)
217+
self.assertRaises(TypeError, power, 4, 11, HasPow.with_val(NotImplemented))
218+
self.assertRaises(TypeError, power, 4, 11, object())
219+
220+
@cpython_only
221+
def test_rshift_print(self):
222+
# This tests correct syntax hint for py2 redirection (>>).
223+
rshift = _testcapi.number_rshift
224+
225+
with self.assertRaises(TypeError) as context:
226+
rshift(print, 42)
227+
self.assertIn('Did you mean "print(<message>, '
228+
'file=<output_stream>)"?', str(context.exception))
229+
with self.assertRaises(TypeError) as context:
230+
rshift(max, sys.stderr)
231+
self.assertNotIn('Did you mean ', str(context.exception))
232+
with self.assertRaises(TypeError) as context:
233+
rshift(1, "spam")
234+
235+
def test_long(self):
236+
# Test PyNumber_Long()
237+
long = _testcapi.number_long
238+
239+
self.assertEqual(long(42), 42)
240+
self.assertEqual(long(1.25), 1)
241+
self.assertEqual(long("42"), 42)
242+
self.assertEqual(long(b"42"), 42)
243+
self.assertEqual(long(bytearray(b"42")), 42)
244+
self.assertEqual(long(memoryview(b"42")), 42)
245+
self.assertEqual(long(IndexLike.with_val(99)), 99)
246+
self.assertEqual(long(IntLike.with_val(99)), 99)
247+
248+
self.assertRaises(TypeError, long, IntLike.with_val(1.0))
249+
with warnings.catch_warnings():
250+
warnings.simplefilter("error", DeprecationWarning)
251+
self.assertRaises(DeprecationWarning, long, IntLike.with_val(True))
252+
with self.assertWarns(DeprecationWarning):
253+
self.assertEqual(long(IntLike.with_val(True)), 1)
254+
self.assertRaises(RuntimeError, long, IntLike.with_exc(RuntimeError))
255+
256+
self.assertRaises(TypeError, long, 1j)
257+
self.assertRaises(TypeError, long, object())
258+
self.assertRaises(SystemError, long, NULL)
259+
260+
def test_float(self):
261+
# Test PyNumber_Float()
262+
float_ = _testcapi.number_float
263+
264+
self.assertEqual(float_(1.25), 1.25)
265+
self.assertEqual(float_(123), 123.)
266+
self.assertEqual(float_("1.25"), 1.25)
267+
268+
self.assertEqual(float_(FloatLike.with_val(4.25)), 4.25)
269+
self.assertEqual(float_(IndexLike.with_val(99)), 99.0)
270+
self.assertEqual(float_(IndexLike.with_val(-1)), -1.0)
271+
272+
self.assertRaises(TypeError, float_, FloatLike.with_val(687))
273+
with warnings.catch_warnings():
274+
warnings.simplefilter("error", DeprecationWarning)
275+
self.assertRaises(DeprecationWarning, float_, FloatLike.with_val(subclassof(float)(4.25)))
276+
with self.assertWarns(DeprecationWarning):
277+
self.assertEqual(float_(FloatLike.with_val(subclassof(float)(4.25))), 4.25)
278+
self.assertRaises(RuntimeError, float_, FloatLike.with_exc(RuntimeError))
279+
280+
self.assertRaises(TypeError, float_, IndexLike.with_val(1.25))
281+
self.assertRaises(OverflowError, float_, IndexLike.with_val(2**2000))
282+
283+
self.assertRaises(TypeError, float_, 1j)
284+
self.assertRaises(TypeError, float_, object())
285+
self.assertRaises(SystemError, float_, NULL)
286+
287+
def test_index(self):
288+
# Test PyNumber_Index()
289+
index = _testcapi.number_index
290+
291+
self.assertEqual(index(11), 11)
292+
293+
with warnings.catch_warnings():
294+
warnings.simplefilter("error", DeprecationWarning)
295+
self.assertRaises(DeprecationWarning, index, IndexLike.with_val(True))
296+
with self.assertWarns(DeprecationWarning):
297+
self.assertEqual(index(IndexLike.with_val(True)), 1)
298+
self.assertRaises(TypeError, index, IndexLike.with_val(1.0))
299+
self.assertRaises(RuntimeError, index, IndexLike.with_exc(RuntimeError))
300+
301+
self.assertRaises(TypeError, index, 1.25)
302+
self.assertRaises(TypeError, index, "42")
303+
self.assertRaises(TypeError, index, object())
304+
self.assertRaises(SystemError, index, NULL)
305+
306+
def test_tobase(self):
307+
# Test PyNumber_ToBase()
308+
tobase = _testcapi.number_tobase
309+
310+
self.assertEqual(tobase(10, 2), bin(10))
311+
self.assertEqual(tobase(11, 8), oct(11))
312+
self.assertEqual(tobase(16, 10), str(16))
313+
self.assertEqual(tobase(13, 16), hex(13))
314+
315+
self.assertRaises(SystemError, tobase, NULL, 2)
316+
self.assertRaises(SystemError, tobase, 2, 3)
317+
self.assertRaises(TypeError, tobase, 1.25, 2)
318+
self.assertRaises(TypeError, tobase, "42", 2)
319+
320+
def test_asssizet(self):
321+
# Test PyNumber_AsSsize_t()
322+
asssizet = _testcapi.number_asssizet
323+
324+
for n in [*range(-6, 7), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX]:
325+
self.assertEqual(asssizet(n, OverflowError), n)
326+
self.assertEqual(asssizet(PY_SSIZE_T_MAX+10, NULL), PY_SSIZE_T_MAX)
327+
self.assertEqual(asssizet(PY_SSIZE_T_MIN-10, NULL), PY_SSIZE_T_MIN)
328+
329+
self.assertRaises(OverflowError, asssizet, PY_SSIZE_T_MAX + 10, OverflowError)
330+
self.assertRaises(RuntimeError, asssizet, PY_SSIZE_T_MAX + 10, RuntimeError)
331+
self.assertRaises(SystemError, asssizet, NULL, TypeError)
332+
333+
334+
if __name__ == "__main__":
335+
unittest.main()

0 commit comments

Comments
 (0)