From cbca6588c5fdd2f9466b9011181e5c9d79b6050c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 5 Nov 2023 07:59:17 +0300 Subject: [PATCH 01/29] gh-111495: Add tests for PyNumber C API --- Lib/test/test_capi/test_number.py | 390 +++++++++++++++++++++++ Modules/_testcapi/numbers.c | 509 ++++++++++++++++++++++++++++++ 2 files changed, 899 insertions(+) create mode 100644 Lib/test/test_capi/test_number.py diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py new file mode 100644 index 00000000000000..6626a9c2878038 --- /dev/null +++ b/Lib/test/test_capi/test_number.py @@ -0,0 +1,390 @@ +import unittest +import warnings + +from test.support import import_helper +from test.test_capi.test_getargs import (Float, BadFloat, BadFloat2, Index, + BadIndex, BadIndex2, Int, BadInt, BadInt2) + +_testcapi = import_helper.import_module('_testcapi') +from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX + +NULL = None + + +class BadAdd: + def __add__(self, other): + raise RuntimeError + + +class BadAdd2(int): + def __radd__(self, other): + return NotImplemented + + +class IndexLike: + def __init__(self, value): + self.value = value + + def __index__(self): + return self.value + + +class CAPITest(unittest.TestCase): + def test_check(self): + # Test PyNumber_Check() + check = _testcapi.number_check + + self.assertTrue(check(1)) + self.assertTrue(check(1.0)) + self.assertTrue(check(1+2j)) + self.assertFalse(check([])) + self.assertFalse(check(object())) + self.assertFalse(check(NULL)) + + def test_add(self): + # Test PyNumber_Add() + add = _testcapi.number_add + + self.assertEqual(add(1, 2), 1 + 2) + self.assertEqual(add(1, 0.75), 1 + 0.75) + self.assertEqual(add(0.75, 1), 0.75 + 1) + self.assertEqual(add(1, BadAdd2(2)), 3) + self.assertEqual(add([1, 2], [3, 4]), [1, 2, 3, 4]) + + self.assertRaises(TypeError, add, 1, object()) + self.assertRaises(TypeError, add, object(), 1) + + # CRASHES add(NULL, 42) + # CRASHES add(42, NULL) + + def test_subtract(self): + # Test PyNumber_Subtract() + subtract = _testcapi.number_subtract + + self.assertEqual(subtract(1, 2), 1 - 2) + + self.assertRaises(TypeError, subtract, 1, object()) + self.assertRaises(TypeError, subtract, object(), 1) + + # CRASHES subtract(NULL, 42) + # CRASHES subtract(42, NULL) + + def test_multiply(self): + # Test PyNumber_Multiply() + multiply = _testcapi.number_multiply + + self.assertEqual(multiply(2, 3), 2 * 3) + self.assertEqual(multiply([1], 2), [1, 1]) + self.assertEqual(multiply(2, [1]), [1, 1]) + + self.assertRaises(TypeError, multiply, 1, object()) + self.assertRaises(TypeError, multiply, object(), 1) + + # CRASHES multiply(NULL, 42) + # CRASHES multiply(42, NULL) + + def test_matrixmultiply(self): + # Test PyNumber_MatrixMultiply() + matrixmultiply = _testcapi.number_matrixmultiply + + # CRASHES matrixmultiply(NULL, NULL) + + def test_floordivide(self): + # Test PyNumber_FloorDivide() + floordivide = _testcapi.number_floordivide + + self.assertEqual(floordivide(4, 3), 4 // 3) + + # CRASHES floordivide(NULL, 42) + # CRASHES floordivide(42, NULL) + + def test_truedivide(self): + # Test PyNumber_TrueDivide() + truedivide = _testcapi.number_truedivide + + self.assertEqual(truedivide(3, 4), 3 / 4) + + # CRASHES truedivide(NULL, 42) + # CRASHES truedivide(42, NULL) + + def test_remainder(self): + # Test PyNumber_Remainder() + remainder = _testcapi.number_remainder + + self.assertEqual(remainder(4, 3), 4 % 3) + + # CRASHES remainder(NULL, 42) + # CRASHES remainder(42, NULL) + + def test_divmod(self): + # Test PyNumber_divmod() + divmod_ = _testcapi.number_divmod + + self.assertEqual(divmod_(4, 3), divmod(4, 3)) + + # CRASHES divmod_(NULL, 42) + # CRASHES divmod_(42, NULL) + + def test_power(self): + # Test PyNumber_Power() + power = _testcapi.number_power + + self.assertEqual(power(4, 3, None), pow(4, 3)) + self.assertEqual(power(4, 11, 5), pow(4, 11, 5)) + + # CRASHES power(NULL, 42) + # CRASHES power(42, NULL) + + def test_negative(self): + # Test PyNumber_Negative() + negative = _testcapi.number_negative + + self.assertEqual(negative(42), -42) + + self.assertRaises(TypeError, negative, BadAdd()) + self.assertRaises(TypeError, negative, object()) + self.assertRaises(SystemError, negative, NULL) + + def test_positive(self): + # Test PyNumber_Positive() + positive = _testcapi.number_positive + + self.assertEqual(positive(-1), +(-1)) + + self.assertRaises(TypeError, positive, BadAdd()) + self.assertRaises(TypeError, positive, object()) + self.assertRaises(SystemError, positive, NULL) + + def test_absolute(self): + # Test PyNumber_Absolute() + absolute = _testcapi.number_absolute + + self.assertEqual(absolute(-1), abs(-1)) + + self.assertRaises(TypeError, absolute, BadAdd()) + self.assertRaises(TypeError, absolute, object()) + self.assertRaises(SystemError, absolute, NULL) + + def test_invert(self): + # Test PyNumber_Invert() + invert = _testcapi.number_invert + + self.assertEqual(invert(123), ~123) + + self.assertRaises(TypeError, invert, BadAdd()) + self.assertRaises(TypeError, invert, object()) + self.assertRaises(SystemError, invert, NULL) + + def test_lshift(self): + # Test PyNumber_Lshift() + lshift = _testcapi.number_lshift + + self.assertEqual(lshift(3, 5), 3 << 5) + + # CRASHES lshift(NULL, 1) + # CRASHES lshift(1, NULL) + + def test_rshift(self): + # Test PyNumber_Rshift() + rshift = _testcapi.number_rshift + + self.assertEqual(rshift(5, 3), 5 >> 3) + + # CRASHES rshift(NULL, 1) + # CRASHES rshift(1, NULL) + + def test_and(self): + # Test PyNumber_And() + and_ = _testcapi.number_and + + self.assertEqual(and_(0b10, 0b01), 0b10 & 0b01) + + # CRASHES and_(NULL, 1) + # CRASHES and_(1, NULL) + + def test_xor(self): + # Test PyNumber_Xor() + xor = _testcapi.number_xor + + self.assertEqual(xor(0b10, 0b01), 0b10 ^ 0b01) + + # CRASHES xor(NULL, 1) + # CRASHES xor(1, NULL) + + def test_or(self): + # Test PyNumber_Or() + or_ = _testcapi.number_or + + self.assertEqual(or_(0b10, 0b01), 0b10 | 0b01) + + # CRASHES or_(NULL, 1) + # CRASHES or_(1, NULL) + + def test_inplaceadd(self): + # Test PyNumber_InPlaceAdd() + inplaceadd = _testcapi.number_inplaceadd + + # CRASHES inplaceadd(NULL) + + def test_inplacesubtract(self): + # Test PyNumber_InPlaceSubtract() + inplacesubtract = _testcapi.number_inplacesubtract + + # CRASHES inplacesubtract(NULL) + + def test_inplacemultiply(self): + # Test PyNumber_InPlaceMultiply() + inplacemultiply = _testcapi.number_inplacemultiply + + # CRASHES inplacemultiply(NULL) + + def test_inplacematrixmultiply(self): + # Test PyNumber_InPlaceMatrixMultiply() + inplacematrixmultiply = _testcapi.number_inplacematrixmultiply + + # CRASHES inplacematrixmultiply(NULL) + + def test_inplacefloordivide(self): + # Test PyNumber_InPlaceFloorDivide() + inplacefloordivide = _testcapi.number_inplacefloordivide + + # CRASHES inplacefloordivide(NULL) + + def test_inplacetruedivide(self): + # Test PyNumber_InPlaceTrueDivide() + inplacetruedivide = _testcapi.number_inplacetruedivide + + # CRASHES inplacetruedivide(NULL) + + def test_inplaceremainder(self): + # Test PyNumber_InPlaceRemainder() + inplaceremainder = _testcapi.number_inplaceremainder + + # CRASHES inplaceremainder(NULL) + + def test_inplacepower(self): + # Test PyNumber_InPlacePower() + inplacepower = _testcapi.number_inplacepower + + # CRASHES inplacepower(NULL) + + def test_inplacelshift(self): + # Test PyNumber_InPlaceLshift() + inplacelshift = _testcapi.number_inplacelshift + + # CRASHES inplacelshift(NULL) + + def test_inplacershift(self): + # Test PyNumber_InPlaceRshift() + inplacershift = _testcapi.number_inplacershift + + # CRASHES inplacershift(NULL) + + def test_inplaceand(self): + # Test PyNumber_InPlaceAnd() + inplaceand = _testcapi.number_inplaceand + + # CRASHES inplaceand(NULL) + + def test_inplacexor(self): + # Test PyNumber_InPlaceXor() + inplacexor = _testcapi.number_inplacexor + + # CRASHES inplacexor(NULL) + + def test_inplaceor(self): + # Test PyNumber_InPlaceOr() + inplaceor = _testcapi.number_inplaceor + + # CRASHES inplaceor(NULL) + + def test_long(self): + # Test PyNumber_Long() + long = _testcapi.number_long + + self.assertEqual(long(42), 42) + self.assertEqual(long(1.25), 1) + self.assertEqual(long("42"), 42) + self.assertEqual(long(b"42"), 42) + self.assertEqual(long(bytearray(b"42")), 42) + self.assertEqual(long(memoryview(b"42")), 42) + self.assertEqual(long(IndexLike(99)), 99) + self.assertEqual(long(Int()), 99) + + self.assertRaises(TypeError, long, BadInt()) + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.assertRaises(DeprecationWarning, long, BadInt2()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(long(BadInt2()), 1) + + self.assertRaises(TypeError, long, 1j) + self.assertRaises(TypeError, long, object()) + self.assertRaises(SystemError, long, NULL) + + def test_float(self): + # Test PyNumber_Float() + float_ = _testcapi.number_float + + self.assertEqual(float_(1.25), 1.25) + self.assertEqual(float_(123), 123.) + + self.assertEqual(float_(Float()), 4.25) + self.assertEqual(float_(IndexLike(99)), 99.0) + self.assertEqual(float_(IndexLike(-1)), -1.0) + + self.assertRaises(TypeError, float_, BadFloat()) + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.assertRaises(DeprecationWarning, float_, BadFloat2()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(float_(BadFloat2()), 4.25) + self.assertRaises(TypeError, float_, IndexLike(1.25)) + self.assertRaises(OverflowError, float_, IndexLike(2**2000)) + self.assertRaises(TypeError, float_, 1j) + self.assertRaises(TypeError, float_, object()) + self.assertRaises(SystemError, float_, NULL) + + def test_index(self): + # Test PyNumber_Index() + index = _testcapi.number_index + + self.assertEqual(index(11), 11) + + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.assertRaises(DeprecationWarning, index, BadIndex2()) + with self.assertWarns(DeprecationWarning): + self.assertEqual(index(BadIndex2()), 1) + self.assertRaises(TypeError, index, BadIndex()) + self.assertRaises(TypeError, index, object()) + self.assertRaises(SystemError, index, NULL) + + def test_tobase(self): + # Test PyNumber_ToBase() + tobase = _testcapi.number_tobase + + self.assertEqual(tobase(10, 2), bin(10)) + self.assertEqual(tobase(11, 8), oct(11)) + self.assertEqual(tobase(16, 10), str(16)) + self.assertEqual(tobase(13, 16), hex(13)) + + self.assertRaises(SystemError, tobase, NULL, 2) + self.assertRaises(SystemError, tobase, 2, 42) + + def test_asssizet(self): + # Test PyNumber_AsSsize_t() + asssizet = _testcapi.number_asssizet + + for n in [*range(-6, 7), PY_SSIZE_T_MIN, PY_SSIZE_T_MAX]: + self.assertEqual(asssizet(n, OverflowError), n) + self.assertEqual(asssizet(PY_SSIZE_T_MAX+10, NULL), PY_SSIZE_T_MAX) + self.assertEqual(asssizet(PY_SSIZE_T_MIN-10, NULL), PY_SSIZE_T_MIN) + + self.assertRaises(OverflowError, asssizet, PY_SSIZE_T_MAX + 10, OverflowError) + self.assertRaises(RuntimeError, asssizet, PY_SSIZE_T_MAX + 10, RuntimeError) + self.assertRaises(SystemError, asssizet, NULL, TypeError) + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/_testcapi/numbers.c b/Modules/_testcapi/numbers.c index 6f7fa3fa7a4186..5be36764a632ec 100644 --- a/Modules/_testcapi/numbers.c +++ b/Modules/_testcapi/numbers.c @@ -1,7 +1,516 @@ #include "parts.h" #include "util.h" + +static PyObject * +number_check(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyNumber_Check(obj)); +} + +static PyObject * +number_add(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Add(o1, o2); +} + +static PyObject * +number_subtract(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Subtract(o1, o2); +} + +static PyObject * +number_multiply(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Multiply(o1, o2); +} + +static PyObject * +number_matrixmultiply(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_MatrixMultiply(o1, o2); +} + +static PyObject * +number_floordivide(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_FloorDivide(o1, o2); +} + +static PyObject * +number_truedivide(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_TrueDivide(o1, o2); +} + +static PyObject * +number_remainder(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Remainder(o1, o2); +} + +static PyObject * +number_divmod(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Divmod(o1, o2); +} + +static PyObject * +number_power(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2, *o3; + + if (!PyArg_ParseTuple(args, "OOO", &o1, &o2, &o3)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Power(o1, o2, o3); +} + +static PyObject * +number_negative(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyNumber_Negative(obj); +} + +static PyObject * +number_positive(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyNumber_Positive(obj); +} + +static PyObject * +number_absolute(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyNumber_Absolute(obj); +} + +static PyObject * +number_invert(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyNumber_Invert(obj); +} + +static PyObject * +number_lshift(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Lshift(o1, o2); +} + +static PyObject * +number_rshift(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Rshift(o1, o2); +} + +static PyObject * +number_and(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_And(o1, o2); +} + +static PyObject * +number_xor(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Xor(o1, o2); +} + +static PyObject * +number_or(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_Or(o1, o2); +} + +static PyObject * +number_inplaceadd(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceAdd(o1, o2); +} + +static PyObject * +number_inplacesubtract(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceSubtract(o1, o2); +} + +static PyObject * +number_inplacemultiply(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceMultiply(o1, o2); +} + +static PyObject * +number_inplacematrixmultiply(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceMatrixMultiply(o1, o2); +} + +static PyObject * +number_inplacefloordivide(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceFloorDivide(o1, o2); +} + +static PyObject * +number_inplacetruedivide(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceTrueDivide(o1, o2); +} + +static PyObject * +number_inplaceremainder(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceRemainder(o1, o2); +} + +static PyObject * +number_inplacepower(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2, *o3; + + if (!PyArg_ParseTuple(args, "OOO", &o1, &o2, &o3)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + NULLABLE(o3); + return PyNumber_InPlacePower(o1, o2, o3); +} + +static PyObject * +number_inplacelshift(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceLshift(o1, o2); +} + +static PyObject * +number_inplacershift(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceRshift(o1, o2); +} + +static PyObject * +number_inplaceand(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceAnd(o1, o2); +} + +static PyObject * +number_inplacexor(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceXor(o1, o2); +} + +static PyObject * +number_inplaceor(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o1, *o2; + + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { + return NULL; + } + + NULLABLE(o1); + NULLABLE(o2); + return PyNumber_InPlaceOr(o1, o2); +} + +static PyObject * +number_long(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyNumber_Long(obj); +} + +static PyObject * +number_float(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyNumber_Float(obj); +} + +static PyObject * +number_index(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyNumber_Index(obj); +} + +static PyObject * +number_tobase(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *n; + int base; + + if (!PyArg_ParseTuple(args, "Oi", &n, &base)) { + return NULL; + } + + NULLABLE(n); + return PyNumber_ToBase(n, base); +} + +static PyObject * +number_asssizet(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *o, *exc; + Py_ssize_t ret; + + if (!PyArg_ParseTuple(args, "OO", &o, &exc)) { + return NULL; + } + + NULLABLE(o); + NULLABLE(exc); + ret = PyNumber_AsSsize_t(o, exc); + + if (ret == (Py_ssize_t)(-1) && PyErr_Occurred()) { + return NULL; + } + + return PyLong_FromSsize_t(ret); +} + + static PyMethodDef test_methods[] = { + {"number_check", number_check, METH_O}, + {"number_add", number_add, METH_VARARGS}, + {"number_subtract", number_subtract, METH_VARARGS}, + {"number_multiply", number_multiply, METH_VARARGS}, + {"number_matrixmultiply", number_matrixmultiply, METH_VARARGS}, + {"number_floordivide", number_floordivide, METH_VARARGS}, + {"number_truedivide", number_truedivide, METH_VARARGS}, + {"number_remainder", number_remainder, METH_VARARGS}, + {"number_divmod", number_divmod, METH_VARARGS}, + {"number_power", number_power, METH_VARARGS}, + {"number_negative", number_negative, METH_O}, + {"number_positive", number_positive, METH_O}, + {"number_absolute", number_absolute, METH_O}, + {"number_invert", number_invert, METH_O}, + {"number_lshift", number_lshift, METH_VARARGS}, + {"number_rshift", number_rshift, METH_VARARGS}, + {"number_and", number_and, METH_VARARGS}, + {"number_xor", number_xor, METH_VARARGS}, + {"number_or", number_or, METH_VARARGS}, + {"number_inplaceadd", number_inplaceadd, METH_VARARGS}, + {"number_inplacesubtract", number_inplacesubtract, METH_VARARGS}, + {"number_inplacemultiply", number_inplacemultiply, METH_VARARGS}, + {"number_inplacematrixmultiply", number_inplacematrixmultiply, METH_VARARGS}, + {"number_inplacefloordivide", number_inplacefloordivide, METH_VARARGS}, + {"number_inplacetruedivide", number_inplacetruedivide, METH_VARARGS}, + {"number_inplaceremainder", number_inplaceremainder, METH_VARARGS}, + {"number_inplacepower", number_inplacepower, METH_VARARGS}, + {"number_inplacelshift", number_inplacelshift, METH_VARARGS}, + {"number_inplacershift", number_inplacershift, METH_VARARGS}, + {"number_inplaceand", number_inplaceand, METH_VARARGS}, + {"number_inplacexor", number_inplacexor, METH_VARARGS}, + {"number_inplaceor", number_inplaceor, METH_VARARGS}, + {"number_long", number_long, METH_O}, + {"number_float", number_float, METH_O}, + {"number_index", number_index, METH_O}, + {"number_tobase", number_tobase, METH_VARARGS}, + {"number_asssizet", number_asssizet, METH_VARARGS}, {NULL}, }; From 0d95d578fca466ac10b014f7c127dc34dc8e6444 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 10 Nov 2023 08:47:24 +0300 Subject: [PATCH 02/29] Remove inaccessible code (PyLong_AsSsize_t raises OverflowError) --- Objects/abstract.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index b6762893b8fd5d..95d3fd459afa77 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1542,11 +1542,7 @@ PyNumber_AsSsize_t(PyObject *item, PyObject *err) if (!runerr) { goto finish; } - - /* Error handling code -- only manage OverflowError differently */ - if (!PyErr_GivenExceptionMatches(runerr, PyExc_OverflowError)) { - goto finish; - } + assert(PyErr_GivenExceptionMatches(runerr, PyExc_OverflowError)); _PyErr_Clear(tstate); /* If no error-handling desired then the default clipping From 7b8adb48673962c3274f66242979d40138826904 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 11 Nov 2023 08:10:20 +0300 Subject: [PATCH 03/29] Drop checks for broken float subclasses (like for PyNumber_Long in 31a655411a) --- Objects/abstract.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index 95d3fd459afa77..4b1542673ff959 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1737,11 +1737,6 @@ PyNumber_Float(PyObject *o) } return PyFloat_FromDouble(val); } - - /* A float subclass with nb_float == NULL */ - if (PyFloat_Check(o)) { - return PyFloat_FromDouble(PyFloat_AS_DOUBLE(o)); - } return PyFloat_FromString(o); } From 4665f25652ce6fd51996b46a7aa18e7a49254b38 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 13 Nov 2023 10:56:43 +0300 Subject: [PATCH 04/29] + tests with sets for intersection/union/etc --- Lib/test/test_capi/test_number.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 6626a9c2878038..23ba837fff4cc4 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -198,6 +198,7 @@ def test_and(self): and_ = _testcapi.number_and self.assertEqual(and_(0b10, 0b01), 0b10 & 0b01) + self.assertEqual(and_({1, 2}, {2, 3}), {1, 2} & {2, 3}) # CRASHES and_(NULL, 1) # CRASHES and_(1, NULL) @@ -207,6 +208,7 @@ def test_xor(self): xor = _testcapi.number_xor self.assertEqual(xor(0b10, 0b01), 0b10 ^ 0b01) + self.assertEqual(xor({1, 2}, {2, 3}), {1, 2} ^ {2, 3}) # CRASHES xor(NULL, 1) # CRASHES xor(1, NULL) @@ -216,6 +218,7 @@ def test_or(self): or_ = _testcapi.number_or self.assertEqual(or_(0b10, 0b01), 0b10 | 0b01) + self.assertEqual(or_({1, 2}, {2, 3}), {1, 2} | {2, 3}) # CRASHES or_(NULL, 1) # CRASHES or_(1, NULL) From 9e149056d74a90beebb43eddc4380dc9d8874ad5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 13 Nov 2023 10:57:24 +0300 Subject: [PATCH 05/29] Use macroses --- Modules/_testcapi/numbers.c | 506 ++++++------------------------------ 1 file changed, 79 insertions(+), 427 deletions(-) diff --git a/Modules/_testcapi/numbers.c b/Modules/_testcapi/numbers.c index 5be36764a632ec..81290a211f1767 100644 --- a/Modules/_testcapi/numbers.c +++ b/Modules/_testcapi/numbers.c @@ -9,433 +9,85 @@ number_check(PyObject *Py_UNUSED(module), PyObject *obj) return PyLong_FromLong(PyNumber_Check(obj)); } -static PyObject * -number_add(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Add(o1, o2); -} - -static PyObject * -number_subtract(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Subtract(o1, o2); -} - -static PyObject * -number_multiply(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Multiply(o1, o2); -} - -static PyObject * -number_matrixmultiply(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_MatrixMultiply(o1, o2); -} - -static PyObject * -number_floordivide(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_FloorDivide(o1, o2); -} - -static PyObject * -number_truedivide(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_TrueDivide(o1, o2); -} - -static PyObject * -number_remainder(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Remainder(o1, o2); -} - -static PyObject * -number_divmod(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Divmod(o1, o2); -} - -static PyObject * -number_power(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2, *o3; - - if (!PyArg_ParseTuple(args, "OOO", &o1, &o2, &o3)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Power(o1, o2, o3); -} - -static PyObject * -number_negative(PyObject *Py_UNUSED(module), PyObject *obj) -{ - NULLABLE(obj); - return PyNumber_Negative(obj); -} - -static PyObject * -number_positive(PyObject *Py_UNUSED(module), PyObject *obj) -{ - NULLABLE(obj); - return PyNumber_Positive(obj); -} - -static PyObject * -number_absolute(PyObject *Py_UNUSED(module), PyObject *obj) -{ - NULLABLE(obj); - return PyNumber_Absolute(obj); -} - -static PyObject * -number_invert(PyObject *Py_UNUSED(module), PyObject *obj) -{ - NULLABLE(obj); - return PyNumber_Invert(obj); -} - -static PyObject * -number_lshift(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Lshift(o1, o2); -} - -static PyObject * -number_rshift(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Rshift(o1, o2); -} - -static PyObject * -number_and(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_And(o1, o2); -} - -static PyObject * -number_xor(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Xor(o1, o2); -} - -static PyObject * -number_or(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_Or(o1, o2); -} - -static PyObject * -number_inplaceadd(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceAdd(o1, o2); -} - -static PyObject * -number_inplacesubtract(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceSubtract(o1, o2); -} - -static PyObject * -number_inplacemultiply(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceMultiply(o1, o2); -} - -static PyObject * -number_inplacematrixmultiply(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceMatrixMultiply(o1, o2); -} - -static PyObject * -number_inplacefloordivide(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceFloorDivide(o1, o2); -} - -static PyObject * -number_inplacetruedivide(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceTrueDivide(o1, o2); -} - -static PyObject * -number_inplaceremainder(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceRemainder(o1, o2); -} - -static PyObject * -number_inplacepower(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2, *o3; - - if (!PyArg_ParseTuple(args, "OOO", &o1, &o2, &o3)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - NULLABLE(o3); - return PyNumber_InPlacePower(o1, o2, o3); -} - -static PyObject * -number_inplacelshift(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceLshift(o1, o2); -} - -static PyObject * -number_inplacershift(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceRshift(o1, o2); -} - -static PyObject * -number_inplaceand(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceAnd(o1, o2); -} - -static PyObject * -number_inplacexor(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceXor(o1, o2); -} - -static PyObject * -number_inplaceor(PyObject *Py_UNUSED(module), PyObject *args) -{ - PyObject *o1, *o2; - - if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { - return NULL; - } - - NULLABLE(o1); - NULLABLE(o2); - return PyNumber_InPlaceOr(o1, o2); -} - -static PyObject * -number_long(PyObject *Py_UNUSED(module), PyObject *obj) -{ - NULLABLE(obj); - return PyNumber_Long(obj); -} - -static PyObject * -number_float(PyObject *Py_UNUSED(module), PyObject *obj) -{ - NULLABLE(obj); - return PyNumber_Float(obj); -} - -static PyObject * -number_index(PyObject *Py_UNUSED(module), PyObject *obj) -{ - NULLABLE(obj); - return PyNumber_Index(obj); -} +#define BINARYFUNC(funcsuffix, methsuffix) \ + static PyObject * \ + number_##methsuffix(PyObject *Py_UNUSED(module), PyObject *args) \ + { \ + PyObject *o1, *o2; \ + \ + if (!PyArg_ParseTuple(args, "OO", &o1, &o2)) { \ + return NULL; \ + } \ + \ + NULLABLE(o1); \ + NULLABLE(o2); \ + return PyNumber_##funcsuffix(o1, o2); \ + }; + +BINARYFUNC(Add, add) +BINARYFUNC(Subtract, subtract) +BINARYFUNC(Multiply, multiply) +BINARYFUNC(MatrixMultiply, matrixmultiply) +BINARYFUNC(FloorDivide, floordivide) +BINARYFUNC(TrueDivide, truedivide) +BINARYFUNC(Remainder, remainder) +BINARYFUNC(Divmod, divmod) + +#define TERNARYFUNC(funcsuffix, methsuffix) \ + static PyObject * \ + number_##methsuffix(PyObject *Py_UNUSED(module), PyObject *args) \ + { \ + PyObject *o1, *o2, *o3; \ + \ + if (!PyArg_ParseTuple(args, "OOO", &o1, &o2, &o3)) { \ + return NULL; \ + } \ + \ + NULLABLE(o1); \ + NULLABLE(o2); \ + return PyNumber_Power(o1, o2, o3); \ + }; + +TERNARYFUNC(Power, power) + +#define UNARYFUNC(funcsuffix, methsuffix) \ + static PyObject * \ + number_##methsuffix(PyObject *Py_UNUSED(module), PyObject *obj) \ + { \ + NULLABLE(obj); \ + return PyNumber_##funcsuffix(obj); \ + }; + +UNARYFUNC(Negative, negative) +UNARYFUNC(Positive, positive) +UNARYFUNC(Absolute, absolute) +UNARYFUNC(Invert, invert) + +BINARYFUNC(Lshift, lshift) +BINARYFUNC(Rshift, rshift) +BINARYFUNC(And, and) +BINARYFUNC(Xor, xor) +BINARYFUNC(Or, or) + +BINARYFUNC(InPlaceAdd, inplaceadd) +BINARYFUNC(InPlaceSubtract, inplacesubtract) +BINARYFUNC(InPlaceMultiply, inplacemultiply) +BINARYFUNC(InPlaceMatrixMultiply, inplacematrixmultiply) +BINARYFUNC(InPlaceFloorDivide, inplacefloordivide) +BINARYFUNC(InPlaceTrueDivide, inplacetruedivide) +BINARYFUNC(InPlaceRemainder, inplaceremainder) + +TERNARYFUNC(InPlacePower, inplacepower) + +BINARYFUNC(InPlaceLshift, inplacelshift) +BINARYFUNC(InPlaceRshift, inplacershift) +BINARYFUNC(InPlaceAnd, inplaceand) +BINARYFUNC(InPlaceXor, inplacexor) +BINARYFUNC(InPlaceOr, inplaceor) + +UNARYFUNC(Long, long) +UNARYFUNC(Float, float) +UNARYFUNC(Index, index) static PyObject * number_tobase(PyObject *Py_UNUSED(module), PyObject *args) From 788e9c22ac83552e54f6a352f987338747195d1b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 08:28:24 +0300 Subject: [PATCH 06/29] Ternary ops (currently only pow/ipow) don't use __r*__ dunders --- Objects/abstract.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index d5f185c9b4f7e8..e2f3b0b533d6b0 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1012,7 +1012,7 @@ binary_op(PyObject *v, PyObject *w, const int op_slot, const char *op_name) Calling scheme used for ternary operations: Order operations are tried until either a valid result or error: - v.op(v,w,z), w.op(v,w,z), z.op(v,w,z) + v.op(v,w,z), w.op(v,w,z) */ static PyObject * @@ -1071,22 +1071,6 @@ ternary_op(PyObject *v, Py_DECREF(x); /* can't do it */ } - PyNumberMethods *mz = Py_TYPE(z)->tp_as_number; - if (mz != NULL) { - ternaryfunc slotz = NB_TERNOP(mz, op_slot); - if (slotz == slotv || slotz == slotw) { - slotz = NULL; - } - if (slotz) { - PyObject *x = slotz(v, w, z); - assert(_Py_CheckSlotResult(z, op_name, x != NULL)); - if (x != Py_NotImplemented) { - return x; - } - Py_DECREF(x); /* can't do it */ - } - } - if (z == Py_None) { PyErr_Format( PyExc_TypeError, From 8cf4429accc9cc2016587ea84e0a6e98419cf960 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 09:07:49 +0300 Subject: [PATCH 07/29] More tests --- Lib/test/test_capi/test_number.py | 41 ++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 23ba837fff4cc4..4fea7073d36e3e 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -1,4 +1,5 @@ import unittest +import sys import warnings from test.support import import_helper @@ -129,8 +130,32 @@ def test_power(self): # Test PyNumber_Power() power = _testcapi.number_power - self.assertEqual(power(4, 3, None), pow(4, 3)) + class IntSubclass(int): + pass + + class IntSubclass2(int): + def __new__(cls, value): + obj = super().__new__(cls) + obj.value = value + return obj + + def __rpow__(self, other, mod=None): + return self.value + + self.assertEqual(power(4, 3, NULL), pow(4, 3)) + self.assertEqual(power(0.5, 2, NULL), pow(0.5, 2)) + self.assertEqual(power(2, -1.0, NULL), pow(2, -1.0)) self.assertEqual(power(4, 11, 5), pow(4, 11, 5)) + self.assertEqual(power(4, IntSubclass(11), NULL), pow(4, 11)) + self.assertEqual(power(4, IntSubclass2(11), NULL), 11) + self.assertEqual(power(4, IntSubclass2(NotImplemented), NULL), 1) + + self.assertRaises(TypeError, power, "spam", 42, NULL) + self.assertRaises(TypeError, power, object(), 42, NULL) + self.assertRaises(TypeError, power, 42, "spam", NULL) + self.assertRaises(TypeError, power, 42, object(), NULL) + self.assertRaises(TypeError, power, 1, 2, "spam") + self.assertRaises(TypeError, power, 1, 2, object()) # CRASHES power(NULL, 42) # CRASHES power(42, NULL) @@ -190,6 +215,20 @@ def test_rshift(self): self.assertEqual(rshift(5, 3), 5 >> 3) + # RHS implements __rrshift__ but returns NotImplemented + with self.assertRaises(TypeError) as context: + print >> 42 + self.assertIn('Did you mean "print(, ' + 'file=)"?', str(context.exception)) + + # Test stream redirection hint is specific to print + with self.assertRaises(TypeError) as context: + max >> sys.stderr + self.assertNotIn('Did you mean ', str(context.exception)) + + with self.assertRaises(TypeError) as context: + 1 >> "spam" + # CRASHES rshift(NULL, 1) # CRASHES rshift(1, NULL) From 9f6fd152006f9d42b3b6702a6948a688de217620 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 11:26:50 +0300 Subject: [PATCH 08/29] More tests --- Lib/test/test_capi/test_number.py | 223 ++++++++++++++++++++++++++---- 1 file changed, 195 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 4fea7073d36e3e..6894b88f5b7474 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -4,7 +4,9 @@ from test.support import import_helper from test.test_capi.test_getargs import (Float, BadFloat, BadFloat2, Index, - BadIndex, BadIndex2, Int, BadInt, BadInt2) + BadIndex, BadIndex2, Int, BadInt, + BadInt2) +from _testbuffer import ndarray _testcapi = import_helper.import_module('_testcapi') from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX @@ -20,6 +22,7 @@ def __add__(self, other): class BadAdd2(int): def __radd__(self, other): return NotImplemented + __iadd__ = __radd__ class IndexLike: @@ -30,13 +33,68 @@ def __index__(self): return self.value +class IntSubclass(int): + pass + + +class IntSubclass2(int): + def __new__(cls, value): + obj = super().__new__(cls) + obj.value = value + return obj + + def __rpow__(self, other, mod=None): + return self.value + __ipow__ = __rpow__ + + +class WithMatrixMul(list): + def __matmul__(self, other): + return self*other + + def __imatmul__(self, other): + x = self*other + self.clear() + self.extend(x) + return self + + +class BadDescr: + def __get__(self, obj, objtype=None): + raise ValueError + + +class WithTrunc: + def __init__(self, value): + self.value = value + + def __trunc__(self): + return self.value + + +class WithBadTrunc: + def __trunc__(self): + raise RuntimeError + +class WithBadTrunc2: + __trunc__ = BadDescr() + + +class BadFloat3: + def __float__(self): + pass + + class CAPITest(unittest.TestCase): def test_check(self): # Test PyNumber_Check() check = _testcapi.number_check self.assertTrue(check(1)) + self.assertTrue(check(IndexLike(1))) + self.assertTrue(check(Int())) self.assertTrue(check(1.0)) + self.assertTrue(check(Float())) self.assertTrue(check(1+2j)) self.assertFalse(check([])) self.assertFalse(check(object())) @@ -54,6 +112,7 @@ def test_add(self): self.assertRaises(TypeError, add, 1, object()) self.assertRaises(TypeError, add, object(), 1) + self.assertRaises(TypeError, add, ndarray([1], (1,)), 2) # CRASHES add(NULL, 42) # CRASHES add(42, NULL) @@ -77,9 +136,14 @@ def test_multiply(self): self.assertEqual(multiply(2, 3), 2 * 3) self.assertEqual(multiply([1], 2), [1, 1]) self.assertEqual(multiply(2, [1]), [1, 1]) + self.assertEqual(multiply([1], -1), []) self.assertRaises(TypeError, multiply, 1, object()) self.assertRaises(TypeError, multiply, object(), 1) + self.assertRaises(TypeError, multiply, ndarray([1], (1,)), 2) + self.assertRaises(TypeError, multiply, 2, ndarray([1], (1,))) + self.assertRaises(TypeError, multiply, [1], 3.14) + self.assertRaises(OverflowError, multiply, [1], PY_SSIZE_T_MAX + 1) # CRASHES multiply(NULL, 42) # CRASHES multiply(42, NULL) @@ -88,6 +152,11 @@ def test_matrixmultiply(self): # Test PyNumber_MatrixMultiply() matrixmultiply = _testcapi.number_matrixmultiply + a, b, r = WithMatrixMul([1]), 2, [1, 1] + self.assertEqual(matrixmultiply(a, b), r) + self.assertEqual(a, [1]) + self.assertEqual(b, 2) + # CRASHES matrixmultiply(NULL, NULL) def test_floordivide(self): @@ -130,18 +199,6 @@ def test_power(self): # Test PyNumber_Power() power = _testcapi.number_power - class IntSubclass(int): - pass - - class IntSubclass2(int): - def __new__(cls, value): - obj = super().__new__(cls) - obj.value = value - return obj - - def __rpow__(self, other, mod=None): - return self.value - self.assertEqual(power(4, 3, NULL), pow(4, 3)) self.assertEqual(power(0.5, 2, NULL), pow(0.5, 2)) self.assertEqual(power(2, -1.0, NULL), pow(2, -1.0)) @@ -157,8 +214,8 @@ def __rpow__(self, other, mod=None): self.assertRaises(TypeError, power, 1, 2, "spam") self.assertRaises(TypeError, power, 1, 2, object()) - # CRASHES power(NULL, 42) - # CRASHES power(42, NULL) + # CRASHES power(NULL, 42, 123) + # CRASHES power(42, NULL, 123) def test_negative(self): # Test PyNumber_Negative() @@ -266,79 +323,174 @@ def test_inplaceadd(self): # Test PyNumber_InPlaceAdd() inplaceadd = _testcapi.number_inplaceadd - # CRASHES inplaceadd(NULL) + self.assertEqual(inplaceadd(1, 2), 1 + 2) + self.assertEqual(inplaceadd(1, 0.75), 1 + 0.75) + self.assertEqual(inplaceadd(0.75, 1), 0.75 + 1) + self.assertEqual(inplaceadd(1, BadAdd2(2)), 3) + + a, b, r = [1, 2], [3, 4], [1, 2, 3, 4] + self.assertEqual(inplaceadd(a, b), r) + self.assertEqual(a, r) + self.assertEqual(b, [3, 4]) + + self.assertRaises(TypeError, inplaceadd, BadAdd2(2), object()) + self.assertRaises(TypeError, inplaceadd, 1, object()) + self.assertRaises(TypeError, inplaceadd, object(), 1) + + # CRASHES inplaceadd(NULL, 42) + # CRASHES inplaceadd(42, NULL) def test_inplacesubtract(self): # Test PyNumber_InPlaceSubtract() inplacesubtract = _testcapi.number_inplacesubtract - # CRASHES inplacesubtract(NULL) + self.assertEqual(inplacesubtract(1, 2), 1 - 2) + + self.assertRaises(TypeError, inplacesubtract, 1, object()) + self.assertRaises(TypeError, inplacesubtract, object(), 1) + + # CRASHES inplacesubtract(NULL, 42) + # CRASHES inplacesubtract(42, NULL) def test_inplacemultiply(self): # Test PyNumber_InPlaceMultiply() inplacemultiply = _testcapi.number_inplacemultiply - # CRASHES inplacemultiply(NULL) + self.assertEqual(inplacemultiply(2, 3), 2 * 3) + + a, b, r = [1], 2, [1, 1] + self.assertEqual(inplacemultiply(a, b), r) + self.assertEqual(a, r) + self.assertEqual(b, 2) + + a, b = 2, [1] + self.assertEqual(inplacemultiply(a, b), r) + self.assertEqual(a, 2) + self.assertEqual(b, [1]) + + self.assertRaises(TypeError, inplacemultiply, 1, object()) + self.assertRaises(TypeError, inplacemultiply, object(), 1) + self.assertRaises(TypeError, inplacemultiply, ndarray([1], (1,)), 2) + self.assertRaises(TypeError, inplacemultiply, 2, ndarray([1], (1,))) + + # CRASHES inplacemultiply(NULL, 42) + # CRASHES inplacemultiply(42, NULL) def test_inplacematrixmultiply(self): # Test PyNumber_InPlaceMatrixMultiply() inplacematrixmultiply = _testcapi.number_inplacematrixmultiply - # CRASHES inplacematrixmultiply(NULL) + a, b, r = WithMatrixMul([1]), 2, [1, 1] + self.assertEqual(inplacematrixmultiply(a, b), r) + self.assertEqual(a, r) + self.assertEqual(b, 2) + + self.assertRaises(TypeError, inplacematrixmultiply, 2, a) + + # CRASHES inplacematrixmultiply(NULL, 42) + # CRASHES inplacematrixmultiply(42, NULL) def test_inplacefloordivide(self): # Test PyNumber_InPlaceFloorDivide() inplacefloordivide = _testcapi.number_inplacefloordivide - # CRASHES inplacefloordivide(NULL) + self.assertEqual(inplacefloordivide(4, 3), 4 // 3) + + # CRASHES inplacefloordivide(NULL, 42) + # CRASHES inplacefloordivide(42, NULL) def test_inplacetruedivide(self): # Test PyNumber_InPlaceTrueDivide() inplacetruedivide = _testcapi.number_inplacetruedivide - # CRASHES inplacetruedivide(NULL) + self.assertEqual(inplacetruedivide(3, 4), 3 / 4) + + # CRASHES inplacetruedivide(NULL, 42) + # CRASHES inplacetruedivide(42, NULL) def test_inplaceremainder(self): # Test PyNumber_InPlaceRemainder() inplaceremainder = _testcapi.number_inplaceremainder - # CRASHES inplaceremainder(NULL) + self.assertEqual(inplaceremainder(4, 3), 4 % 3) + + # CRASHES inplaceremainder(NULL, 42) + # CRASHES inplaceremainder(42, NULL) def test_inplacepower(self): # Test PyNumber_InPlacePower() inplacepower = _testcapi.number_inplacepower - # CRASHES inplacepower(NULL) + self.assertEqual(inplacepower(2, 3, NULL), pow(2, 3)) + self.assertEqual(inplacepower(2, 3, 4), pow(2, 3, 4)) + self.assertEqual(inplacepower(IntSubclass2(2), 2, NULL), 2) + + self.assertRaises(TypeError, inplacepower, + IntSubclass2(NotImplemented), object(), NULL) + self.assertRaises(TypeError, inplacepower, object(), 2, NULL) + + # CRASHES inplacepower(NULL, 42, 123) + # CRASHES inplacepower(42, NULL, 123) def test_inplacelshift(self): # Test PyNumber_InPlaceLshift() inplacelshift = _testcapi.number_inplacelshift - # CRASHES inplacelshift(NULL) + self.assertEqual(inplacelshift(3, 5), 3 << 5) + + # CRASHES inplacelshift(NULL, 42) + # CRASHES inplacelshift(42, NULL) def test_inplacershift(self): # Test PyNumber_InPlaceRshift() inplacershift = _testcapi.number_inplacershift - # CRASHES inplacershift(NULL) + self.assertEqual(inplacershift(5, 3), 5 >> 3) + + # CRASHES inplacershift(NULL, 42) + # CRASHES inplacershift(42, NULL) def test_inplaceand(self): # Test PyNumber_InPlaceAnd() inplaceand = _testcapi.number_inplaceand - # CRASHES inplaceand(NULL) + self.assertEqual(inplaceand(0b10, 0b01), 0b10 & 0b01) + + a, b, r = {1, 2}, {2, 3}, {1, 2} & {2, 3} + self.assertEqual(inplaceand(a, b), r) + self.assertEqual(a, r) + self.assertEqual(b, {2, 3}) + + # CRASHES inplaceand(NULL, 42) + # CRASHES inplaceand(42, NULL) def test_inplacexor(self): # Test PyNumber_InPlaceXor() inplacexor = _testcapi.number_inplacexor - # CRASHES inplacexor(NULL) + self.assertEqual(inplacexor(0b10, 0b01), 0b10 ^ 0b01) + + a, b, r = {1, 2}, {2, 3}, {1, 2} ^ {2, 3} + self.assertEqual(inplacexor(a, b), r) + self.assertEqual(a, r) + self.assertEqual(b, {2, 3}) + + # CRASHES inplacexor(NULL, 42) + # CRASHES inplacexor(42, NULL) def test_inplaceor(self): # Test PyNumber_InPlaceOr() inplaceor = _testcapi.number_inplaceor - # CRASHES inplaceor(NULL) + self.assertEqual(inplaceor(0b10, 0b01), 0b10 | 0b01) + + a, b, r = {1, 2}, {2, 3}, {1, 2} | {2, 3} + self.assertEqual(inplaceor(a, b), r) + self.assertEqual(a, r) + self.assertEqual(b, {2, 3}) + + # CRASHES inplaceor(NULL, 42) + # CRASHES inplaceor(42, NULL) def test_long(self): # Test PyNumber_Long() @@ -359,10 +511,24 @@ def test_long(self): self.assertRaises(DeprecationWarning, long, BadInt2()) with self.assertWarns(DeprecationWarning): self.assertEqual(long(BadInt2()), 1) + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.assertRaises(DeprecationWarning, long, WithTrunc(42)) + with self.assertWarns(DeprecationWarning): + self.assertEqual(long(WithTrunc(42)), 42) + with self.assertWarns(DeprecationWarning): + self.assertEqual(long(WithTrunc(IntSubclass(42))), 42) + with self.assertWarns(DeprecationWarning): + self.assertEqual(long(WithTrunc(IndexLike(42))), 42) + + with self.assertWarns(DeprecationWarning): + self.assertRaises(TypeError, long, WithTrunc(1.25)) self.assertRaises(TypeError, long, 1j) self.assertRaises(TypeError, long, object()) self.assertRaises(SystemError, long, NULL) + self.assertRaises(RuntimeError, long, WithBadTrunc()) + self.assertRaises(ValueError, long, WithBadTrunc2()) def test_float(self): # Test PyNumber_Float() @@ -385,6 +551,7 @@ def test_float(self): self.assertRaises(OverflowError, float_, IndexLike(2**2000)) self.assertRaises(TypeError, float_, 1j) self.assertRaises(TypeError, float_, object()) + self.assertRaises(TypeError, float_, BadFloat3()) self.assertRaises(SystemError, float_, NULL) def test_index(self): From 9f5b7a123e28e59fceb51f5623db535f620ab4aa Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 11:31:15 +0300 Subject: [PATCH 09/29] Use BINARY_FUNC macro for some remaining ops --- Objects/abstract.c | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index e2f3b0b533d6b0..0a2d29d88323f7 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1164,29 +1164,10 @@ PyNumber_Multiply(PyObject *v, PyObject *w) return result; } -PyObject * -PyNumber_MatrixMultiply(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_matrix_multiply), "@"); -} - -PyObject * -PyNumber_FloorDivide(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_floor_divide), "//"); -} - -PyObject * -PyNumber_TrueDivide(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_true_divide), "/"); -} - -PyObject * -PyNumber_Remainder(PyObject *v, PyObject *w) -{ - return binary_op(v, w, NB_SLOT(nb_remainder), "%"); -} +BINARY_FUNC(PyNumber_MatrixMultiply, nb_matrix_multiply, "@") +BINARY_FUNC(PyNumber_FloorDivide, nb_floor_divide, "//") +BINARY_FUNC(PyNumber_TrueDivide, nb_true_divide, "/") +BINARY_FUNC(PyNumber_Remainder, nb_remainder, "%") PyObject * PyNumber_Power(PyObject *v, PyObject *w, PyObject *z) From 2675738d7de884b30f44b919c8971d1814695149 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 11:39:36 +0300 Subject: [PATCH 10/29] Add UNARY_FUNC macro to define unary PyNumber_* functions --- Objects/abstract.c | 88 +++++++++++----------------------------------- 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/Objects/abstract.c b/Objects/abstract.c index 0a2d29d88323f7..decbf626b3d094 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1344,73 +1344,27 @@ _PyNumber_InPlacePowerNoMod(PyObject *lhs, PyObject *rhs) /* Unary operators and functions */ -PyObject * -PyNumber_Negative(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_negative) { - PyObject *res = (*m->nb_negative)(o); - assert(_Py_CheckSlotResult(o, "__neg__", res != NULL)); - return res; - } - - return type_error("bad operand type for unary -: '%.200s'", o); -} - -PyObject * -PyNumber_Positive(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_positive) { - PyObject *res = (*m->nb_positive)(o); - assert(_Py_CheckSlotResult(o, "__pos__", res != NULL)); - return res; - } - - return type_error("bad operand type for unary +: '%.200s'", o); -} - -PyObject * -PyNumber_Invert(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_invert) { - PyObject *res = (*m->nb_invert)(o); - assert(_Py_CheckSlotResult(o, "__invert__", res != NULL)); - return res; - } - - return type_error("bad operand type for unary ~: '%.200s'", o); -} - -PyObject * -PyNumber_Absolute(PyObject *o) -{ - if (o == NULL) { - return null_error(); - } - - PyNumberMethods *m = Py_TYPE(o)->tp_as_number; - if (m && m->nb_absolute) { - PyObject *res = m->nb_absolute(o); - assert(_Py_CheckSlotResult(o, "__abs__", res != NULL)); - return res; - } - - return type_error("bad operand type for abs(): '%.200s'", o); -} +#define UNARY_FUNC(func, op, meth_name) \ + PyObject * \ + func(PyObject *o) { \ + if (o == NULL) { \ + return null_error(); \ + } \ + \ + PyNumberMethods *m = Py_TYPE(o)->tp_as_number; \ + if (m && m->op) { \ + PyObject *res = (*m->op)(o); \ + assert(_Py_CheckSlotResult(o, #meth_name, res != NULL)); \ + return res; \ + } \ + \ + return type_error("bad operand type for unary -: '%.200s'", o); \ + } + +UNARY_FUNC(PyNumber_Negative, nb_negative, __neg__) +UNARY_FUNC(PyNumber_Positive, nb_positive, __pow__) +UNARY_FUNC(PyNumber_Invert, nb_invert, __invert__) +UNARY_FUNC(PyNumber_Absolute, nb_absolute, __abs__) int From 2bc6bc75348fd86ba61f0824e8e198e8aebbf7bc Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 12:33:38 +0300 Subject: [PATCH 11/29] Fix typo --- Modules/_testcapi/numbers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testcapi/numbers.c b/Modules/_testcapi/numbers.c index 81290a211f1767..174909654c0ae5 100644 --- a/Modules/_testcapi/numbers.c +++ b/Modules/_testcapi/numbers.c @@ -45,7 +45,7 @@ BINARYFUNC(Divmod, divmod) \ NULLABLE(o1); \ NULLABLE(o2); \ - return PyNumber_Power(o1, o2, o3); \ + return PyNumber_##funcsuffix(o1, o2, o3); \ }; TERNARYFUNC(Power, power) From 49d673babfb6f41d667d9d68101d564aec643ced Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 15:56:09 +0300 Subject: [PATCH 12/29] Make last argument optional for PyNumber_Power/InPlacePower --- Modules/_testcapi/numbers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_testcapi/numbers.c b/Modules/_testcapi/numbers.c index 174909654c0ae5..e16ff73744067a 100644 --- a/Modules/_testcapi/numbers.c +++ b/Modules/_testcapi/numbers.c @@ -37,9 +37,9 @@ BINARYFUNC(Divmod, divmod) static PyObject * \ number_##methsuffix(PyObject *Py_UNUSED(module), PyObject *args) \ { \ - PyObject *o1, *o2, *o3; \ + PyObject *o1, *o2, *o3 = Py_None; \ \ - if (!PyArg_ParseTuple(args, "OOO", &o1, &o2, &o3)) { \ + if (!PyArg_ParseTuple(args, "OO|O", &o1, &o2, &o3)) { \ return NULL; \ } \ \ From b2dda899ac0fead355769c447fadd9d0ab4076ff Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 15:57:02 +0300 Subject: [PATCH 13/29] More tests --- Lib/test/test_capi/test_number.py | 58 +++++++++++++++++++------------ 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 6894b88f5b7474..d2a27bb5d93892 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -33,6 +33,11 @@ def __index__(self): return self.value +class BadIndex3: + def __index__(self): + raise RuntimeError + + class IntSubclass(int): pass @@ -48,6 +53,16 @@ def __rpow__(self, other, mod=None): __ipow__ = __rpow__ +class BadInt3: + def __int__(self): + raise RuntimeError + + +class BadFloat3: + def __float__(self): + raise RuntimeError + + class WithMatrixMul(list): def __matmul__(self, other): return self*other @@ -80,11 +95,6 @@ class WithBadTrunc2: __trunc__ = BadDescr() -class BadFloat3: - def __float__(self): - pass - - class CAPITest(unittest.TestCase): def test_check(self): # Test PyNumber_Check() @@ -199,23 +209,23 @@ def test_power(self): # Test PyNumber_Power() power = _testcapi.number_power - self.assertEqual(power(4, 3, NULL), pow(4, 3)) - self.assertEqual(power(0.5, 2, NULL), pow(0.5, 2)) - self.assertEqual(power(2, -1.0, NULL), pow(2, -1.0)) + self.assertEqual(power(4, 3), pow(4, 3)) + self.assertEqual(power(0.5, 2), pow(0.5, 2)) + self.assertEqual(power(2, -1.0), pow(2, -1.0)) self.assertEqual(power(4, 11, 5), pow(4, 11, 5)) - self.assertEqual(power(4, IntSubclass(11), NULL), pow(4, 11)) - self.assertEqual(power(4, IntSubclass2(11), NULL), 11) + self.assertEqual(power(4, IntSubclass(11)), pow(4, 11)) + self.assertEqual(power(4, IntSubclass2(11)), 11) self.assertEqual(power(4, IntSubclass2(NotImplemented), NULL), 1) - self.assertRaises(TypeError, power, "spam", 42, NULL) - self.assertRaises(TypeError, power, object(), 42, NULL) - self.assertRaises(TypeError, power, 42, "spam", NULL) - self.assertRaises(TypeError, power, 42, object(), NULL) + self.assertRaises(TypeError, power, "spam", 42) + self.assertRaises(TypeError, power, object(), 42) + self.assertRaises(TypeError, power, 42, "spam") + self.assertRaises(TypeError, power, 42, object()) self.assertRaises(TypeError, power, 1, 2, "spam") self.assertRaises(TypeError, power, 1, 2, object()) - # CRASHES power(NULL, 42, 123) - # CRASHES power(42, NULL, 123) + # CRASHES power(NULL, 42) + # CRASHES power(42, NULL) def test_negative(self): # Test PyNumber_Negative() @@ -421,16 +431,16 @@ def test_inplacepower(self): # Test PyNumber_InPlacePower() inplacepower = _testcapi.number_inplacepower - self.assertEqual(inplacepower(2, 3, NULL), pow(2, 3)) + self.assertEqual(inplacepower(2, 3), pow(2, 3)) self.assertEqual(inplacepower(2, 3, 4), pow(2, 3, 4)) - self.assertEqual(inplacepower(IntSubclass2(2), 2, NULL), 2) + self.assertEqual(inplacepower(IntSubclass2(2), 2), 2) self.assertRaises(TypeError, inplacepower, - IntSubclass2(NotImplemented), object(), NULL) - self.assertRaises(TypeError, inplacepower, object(), 2, NULL) + IntSubclass2(NotImplemented), object()) + self.assertRaises(TypeError, inplacepower, object(), 2) - # CRASHES inplacepower(NULL, 42, 123) - # CRASHES inplacepower(42, NULL, 123) + # CRASHES inplacepower(NULL, 42) + # CRASHES inplacepower(42, NULL) def test_inplacelshift(self): # Test PyNumber_InPlaceLshift() @@ -529,6 +539,7 @@ def test_long(self): self.assertRaises(SystemError, long, NULL) self.assertRaises(RuntimeError, long, WithBadTrunc()) self.assertRaises(ValueError, long, WithBadTrunc2()) + self.assertRaises(RuntimeError, long, BadInt3()) def test_float(self): # Test PyNumber_Float() @@ -551,8 +562,8 @@ def test_float(self): self.assertRaises(OverflowError, float_, IndexLike(2**2000)) self.assertRaises(TypeError, float_, 1j) self.assertRaises(TypeError, float_, object()) - self.assertRaises(TypeError, float_, BadFloat3()) self.assertRaises(SystemError, float_, NULL) + self.assertRaises(RuntimeError, float_, BadFloat3()) def test_index(self): # Test PyNumber_Index() @@ -568,6 +579,7 @@ def test_index(self): self.assertRaises(TypeError, index, BadIndex()) self.assertRaises(TypeError, index, object()) self.assertRaises(SystemError, index, NULL) + self.assertRaises(RuntimeError, index, BadIndex3()) def test_tobase(self): # Test PyNumber_ToBase() From b86d2eb2f56e718dc5bb04c5fdcaf3088abd95fd Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Nov 2023 15:59:14 +0300 Subject: [PATCH 14/29] Remove number_check() from _testcapi/abstract.c, move tests --- Lib/test/test_capi/test_abstract.py | 7 ------- Lib/test/test_capi/test_number.py | 3 ++- Modules/_testcapi/abstract.c | 8 -------- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_capi/test_abstract.py b/Lib/test/test_capi/test_abstract.py index 26152c3049848c..7bef43d285e518 100644 --- a/Lib/test/test_capi/test_abstract.py +++ b/Lib/test/test_capi/test_abstract.py @@ -907,13 +907,6 @@ def test_sequence_tuple(self): self.assertRaises(TypeError, xtuple, 42) self.assertRaises(SystemError, xtuple, NULL) - def test_number_check(self): - number_check = _testcapi.number_check - self.assertTrue(number_check(1 + 1j)) - self.assertTrue(number_check(1)) - self.assertTrue(number_check(0.5)) - self.assertFalse(number_check("1 + 1j")) - if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index d2a27bb5d93892..bf2331a341d012 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -103,10 +103,11 @@ def test_check(self): self.assertTrue(check(1)) self.assertTrue(check(IndexLike(1))) self.assertTrue(check(Int())) - self.assertTrue(check(1.0)) + self.assertTrue(check(0.5)) self.assertTrue(check(Float())) self.assertTrue(check(1+2j)) self.assertFalse(check([])) + self.assertFalse(check("1 + 1j")) self.assertFalse(check(object())) self.assertFalse(check(NULL)) diff --git a/Modules/_testcapi/abstract.c b/Modules/_testcapi/abstract.c index 4a9144e66f0fcd..0427bb6dfe0f57 100644 --- a/Modules/_testcapi/abstract.c +++ b/Modules/_testcapi/abstract.c @@ -180,13 +180,6 @@ object_delattrstring(PyObject *self, PyObject *args) RETURN_INT(PyObject_DelAttrString(obj, attr_name)); } -static PyObject * -number_check(PyObject *self, PyObject *obj) -{ - NULLABLE(obj); - return PyBool_FromLong(PyNumber_Check(obj)); -} - static PyObject * mapping_check(PyObject *self, PyObject *obj) { @@ -629,7 +622,6 @@ static PyMethodDef test_methods[] = { {"object_delattr", object_delattr, METH_VARARGS}, {"object_delattrstring", object_delattrstring, METH_VARARGS}, - {"number_check", number_check, METH_O}, {"mapping_check", mapping_check, METH_O}, {"mapping_size", mapping_size, METH_O}, {"mapping_length", mapping_length, METH_O}, From 0fbbba9a9ed1212e73bf8dcbbcc8b1a1cd8ea70a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 16 Nov 2023 11:34:17 +0300 Subject: [PATCH 15/29] address review: * Add test for [1, 2] and PY_SSIZE_T_MAX//2 + 1 (test_multiply) * test with floats for unary * test_float/index with strings * 42 is too large. What about 3? // tobase * test with float & string // tobase * complex test for absolute --- Lib/test/test_capi/test_number.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index bf2331a341d012..a76c9b922cb710 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -155,6 +155,7 @@ def test_multiply(self): self.assertRaises(TypeError, multiply, 2, ndarray([1], (1,))) self.assertRaises(TypeError, multiply, [1], 3.14) self.assertRaises(OverflowError, multiply, [1], PY_SSIZE_T_MAX + 1) + self.assertRaises(MemoryError, multiply, [1, 2], PY_SSIZE_T_MAX//2 + 1) # CRASHES multiply(NULL, 42) # CRASHES multiply(42, NULL) @@ -233,6 +234,7 @@ def test_negative(self): negative = _testcapi.number_negative self.assertEqual(negative(42), -42) + self.assertEqual(negative(1.25), -1.25) self.assertRaises(TypeError, negative, BadAdd()) self.assertRaises(TypeError, negative, object()) @@ -243,6 +245,7 @@ def test_positive(self): positive = _testcapi.number_positive self.assertEqual(positive(-1), +(-1)) + self.assertEqual(positive(1.25), 1.25) self.assertRaises(TypeError, positive, BadAdd()) self.assertRaises(TypeError, positive, object()) @@ -253,6 +256,8 @@ def test_absolute(self): absolute = _testcapi.number_absolute self.assertEqual(absolute(-1), abs(-1)) + self.assertEqual(absolute(-1.25), 1.25) + self.assertEqual(absolute(1j), 1.0) self.assertRaises(TypeError, absolute, BadAdd()) self.assertRaises(TypeError, absolute, object()) @@ -264,6 +269,7 @@ def test_invert(self): self.assertEqual(invert(123), ~123) + self.assertRaises(TypeError, invert, 1.25) self.assertRaises(TypeError, invert, BadAdd()) self.assertRaises(TypeError, invert, object()) self.assertRaises(SystemError, invert, NULL) @@ -548,6 +554,7 @@ def test_float(self): self.assertEqual(float_(1.25), 1.25) self.assertEqual(float_(123), 123.) + self.assertEqual(float_("1.25"), 1.25) self.assertEqual(float_(Float()), 4.25) self.assertEqual(float_(IndexLike(99)), 99.0) @@ -577,6 +584,8 @@ def test_index(self): self.assertRaises(DeprecationWarning, index, BadIndex2()) with self.assertWarns(DeprecationWarning): self.assertEqual(index(BadIndex2()), 1) + self.assertRaises(TypeError, index, 1.25) + self.assertRaises(TypeError, index, "42") self.assertRaises(TypeError, index, BadIndex()) self.assertRaises(TypeError, index, object()) self.assertRaises(SystemError, index, NULL) @@ -592,7 +601,9 @@ def test_tobase(self): self.assertEqual(tobase(13, 16), hex(13)) self.assertRaises(SystemError, tobase, NULL, 2) - self.assertRaises(SystemError, tobase, 2, 42) + self.assertRaises(SystemError, tobase, 2, 3) + self.assertRaises(TypeError, tobase, 1.25, 2) + self.assertRaises(TypeError, tobase, "42", 2) def test_asssizet(self): # Test PyNumber_AsSsize_t() From 9aae51b11aa772b6d5fbb04dcfbad98c2211707a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 17 Nov 2023 10:21:44 +0300 Subject: [PATCH 16/29] some cleanup for support classes --- Lib/test/test_capi/test_number.py | 177 +++++++++++++----------------- 1 file changed, 79 insertions(+), 98 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index a76c9b922cb710..3a387347d07d8e 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -3,9 +3,6 @@ import warnings from test.support import import_helper -from test.test_capi.test_getargs import (Float, BadFloat, BadFloat2, Index, - BadIndex, BadIndex2, Int, BadInt, - BadInt2) from _testbuffer import ndarray _testcapi = import_helper.import_module('_testcapi') @@ -14,53 +11,58 @@ NULL = None -class BadAdd: - def __add__(self, other): - raise RuntimeError - - -class BadAdd2(int): - def __radd__(self, other): - return NotImplemented - __iadd__ = __radd__ +class WithDunder: + @classmethod + def with_val(cls, val): + obj = super().__new__(cls) + def meth(*args): + return val + setattr(cls, cls.methname, meth) + return obj + @classmethod + def with_exc(cls, exc): + obj = super().__new__(cls) + def meth(*args): + raise exc + setattr(cls, cls.methname, meth) + return obj -class IndexLike: - def __init__(self, value): - self.value = value +class IndexLike(WithDunder): + methname = '__index__' - def __index__(self): - return self.value +class IntLike(WithDunder): + methname = '__int__' +class FloatLike(WithDunder): + methname = '__float__' -class BadIndex3: - def __index__(self): - raise RuntimeError +class HasTrunc(WithDunder): + methname = '__trunc__' +class BadDescr: + def __get__(self, obj, objtype=None): + raise ValueError -class IntSubclass(int): - pass +class HasBadTrunc: + __trunc__ = BadDescr() -class IntSubclass2(int): - def __new__(cls, value): - obj = super().__new__(cls) - obj.value = value - return obj +def subclassof(base): + return type(base.__name__ + 'Subclass', (base,), {}) - def __rpow__(self, other, mod=None): - return self.value - __ipow__ = __rpow__ +class HasRAddIntSubclass(WithDunder, int): + methname = '__radd__' -class BadInt3: - def __int__(self): - raise RuntimeError +class HasIAdd(WithDunder): + methname = '__iadd__' +class HasRPowIntSubclass(WithDunder, int): + methname = '__rpow__' -class BadFloat3: - def __float__(self): - raise RuntimeError +class HasIPow(WithDunder): + methname = '__ipow__' class WithMatrixMul(list): @@ -74,37 +76,16 @@ def __imatmul__(self, other): return self -class BadDescr: - def __get__(self, obj, objtype=None): - raise ValueError - - -class WithTrunc: - def __init__(self, value): - self.value = value - - def __trunc__(self): - return self.value - - -class WithBadTrunc: - def __trunc__(self): - raise RuntimeError - -class WithBadTrunc2: - __trunc__ = BadDescr() - - class CAPITest(unittest.TestCase): def test_check(self): # Test PyNumber_Check() check = _testcapi.number_check self.assertTrue(check(1)) - self.assertTrue(check(IndexLike(1))) - self.assertTrue(check(Int())) + self.assertTrue(check(IndexLike.with_val(1))) + self.assertTrue(check(IntLike.with_val(99))) self.assertTrue(check(0.5)) - self.assertTrue(check(Float())) + self.assertTrue(check(FloatLike.with_val(4.25))) self.assertTrue(check(1+2j)) self.assertFalse(check([])) self.assertFalse(check("1 + 1j")) @@ -118,9 +99,10 @@ def test_add(self): self.assertEqual(add(1, 2), 1 + 2) self.assertEqual(add(1, 0.75), 1 + 0.75) self.assertEqual(add(0.75, 1), 0.75 + 1) - self.assertEqual(add(1, BadAdd2(2)), 3) + self.assertEqual(add(42, HasRAddIntSubclass.with_val(NotImplemented)), 42) self.assertEqual(add([1, 2], [3, 4]), [1, 2, 3, 4]) + self.assertRaises(TypeError, add, 1, object()) self.assertRaises(TypeError, add, object(), 1) self.assertRaises(TypeError, add, ndarray([1], (1,)), 2) @@ -215,9 +197,9 @@ def test_power(self): self.assertEqual(power(0.5, 2), pow(0.5, 2)) self.assertEqual(power(2, -1.0), pow(2, -1.0)) self.assertEqual(power(4, 11, 5), pow(4, 11, 5)) - self.assertEqual(power(4, IntSubclass(11)), pow(4, 11)) - self.assertEqual(power(4, IntSubclass2(11)), 11) - self.assertEqual(power(4, IntSubclass2(NotImplemented), NULL), 1) + self.assertEqual(power(4, subclassof(int)(11)), pow(4, 11)) + self.assertEqual(power(4, HasRPowIntSubclass.with_val(42)), 42) + self.assertEqual(power(4, HasRPowIntSubclass.with_val(NotImplemented)), 1) self.assertRaises(TypeError, power, "spam", 42) self.assertRaises(TypeError, power, object(), 42) @@ -236,7 +218,7 @@ def test_negative(self): self.assertEqual(negative(42), -42) self.assertEqual(negative(1.25), -1.25) - self.assertRaises(TypeError, negative, BadAdd()) + self.assertRaises(TypeError, negative, "123") self.assertRaises(TypeError, negative, object()) self.assertRaises(SystemError, negative, NULL) @@ -247,7 +229,7 @@ def test_positive(self): self.assertEqual(positive(-1), +(-1)) self.assertEqual(positive(1.25), 1.25) - self.assertRaises(TypeError, positive, BadAdd()) + self.assertRaises(TypeError, positive, "123") self.assertRaises(TypeError, positive, object()) self.assertRaises(SystemError, positive, NULL) @@ -259,7 +241,7 @@ def test_absolute(self): self.assertEqual(absolute(-1.25), 1.25) self.assertEqual(absolute(1j), 1.0) - self.assertRaises(TypeError, absolute, BadAdd()) + self.assertRaises(TypeError, absolute, "123") self.assertRaises(TypeError, absolute, object()) self.assertRaises(SystemError, absolute, NULL) @@ -270,7 +252,7 @@ def test_invert(self): self.assertEqual(invert(123), ~123) self.assertRaises(TypeError, invert, 1.25) - self.assertRaises(TypeError, invert, BadAdd()) + self.assertRaises(TypeError, invert, "123") self.assertRaises(TypeError, invert, object()) self.assertRaises(SystemError, invert, NULL) @@ -343,14 +325,13 @@ def test_inplaceadd(self): self.assertEqual(inplaceadd(1, 2), 1 + 2) self.assertEqual(inplaceadd(1, 0.75), 1 + 0.75) self.assertEqual(inplaceadd(0.75, 1), 0.75 + 1) - self.assertEqual(inplaceadd(1, BadAdd2(2)), 3) a, b, r = [1, 2], [3, 4], [1, 2, 3, 4] self.assertEqual(inplaceadd(a, b), r) self.assertEqual(a, r) self.assertEqual(b, [3, 4]) - self.assertRaises(TypeError, inplaceadd, BadAdd2(2), object()) + self.assertRaises(TypeError, inplaceadd, HasIAdd.with_val(NotImplemented), object()) self.assertRaises(TypeError, inplaceadd, 1, object()) self.assertRaises(TypeError, inplaceadd, object(), 1) @@ -440,10 +421,10 @@ def test_inplacepower(self): self.assertEqual(inplacepower(2, 3), pow(2, 3)) self.assertEqual(inplacepower(2, 3, 4), pow(2, 3, 4)) - self.assertEqual(inplacepower(IntSubclass2(2), 2), 2) + self.assertEqual(inplacepower(HasIPow.with_val(42), 2), 42) self.assertRaises(TypeError, inplacepower, - IntSubclass2(NotImplemented), object()) + HasIPow.with_val(NotImplemented), object()) self.assertRaises(TypeError, inplacepower, object(), 2) # CRASHES inplacepower(NULL, 42) @@ -519,34 +500,34 @@ def test_long(self): self.assertEqual(long(b"42"), 42) self.assertEqual(long(bytearray(b"42")), 42) self.assertEqual(long(memoryview(b"42")), 42) - self.assertEqual(long(IndexLike(99)), 99) - self.assertEqual(long(Int()), 99) + self.assertEqual(long(IndexLike.with_val(99)), 99) + self.assertEqual(long(IntLike.with_val(99)), 99) - self.assertRaises(TypeError, long, BadInt()) + self.assertRaises(TypeError, long, IntLike.with_val(1.0)) with warnings.catch_warnings(): warnings.simplefilter("error", DeprecationWarning) - self.assertRaises(DeprecationWarning, long, BadInt2()) + self.assertRaises(DeprecationWarning, long, IntLike.with_val(True)) with self.assertWarns(DeprecationWarning): - self.assertEqual(long(BadInt2()), 1) + self.assertEqual(long(IntLike.with_val(True)), 1) with warnings.catch_warnings(): warnings.simplefilter("error", DeprecationWarning) - self.assertRaises(DeprecationWarning, long, WithTrunc(42)) + self.assertRaises(DeprecationWarning, long, HasTrunc.with_val(42)) with self.assertWarns(DeprecationWarning): - self.assertEqual(long(WithTrunc(42)), 42) + self.assertEqual(long(HasTrunc.with_val(42)), 42) with self.assertWarns(DeprecationWarning): - self.assertEqual(long(WithTrunc(IntSubclass(42))), 42) + self.assertEqual(long(HasTrunc.with_val(subclassof(int)(42))), 42) with self.assertWarns(DeprecationWarning): - self.assertEqual(long(WithTrunc(IndexLike(42))), 42) + self.assertEqual(long(HasTrunc.with_val(IndexLike.with_val(42))), 42) with self.assertWarns(DeprecationWarning): - self.assertRaises(TypeError, long, WithTrunc(1.25)) + self.assertRaises(TypeError, long, HasTrunc.with_val(1.25)) self.assertRaises(TypeError, long, 1j) self.assertRaises(TypeError, long, object()) self.assertRaises(SystemError, long, NULL) - self.assertRaises(RuntimeError, long, WithBadTrunc()) - self.assertRaises(ValueError, long, WithBadTrunc2()) - self.assertRaises(RuntimeError, long, BadInt3()) + self.assertRaises(RuntimeError, long, HasTrunc.with_exc(RuntimeError)) + self.assertRaises(ValueError, long, HasBadTrunc()) + self.assertRaises(RuntimeError, long, IntLike.with_exc(RuntimeError)) def test_float(self): # Test PyNumber_Float() @@ -556,22 +537,22 @@ def test_float(self): self.assertEqual(float_(123), 123.) self.assertEqual(float_("1.25"), 1.25) - self.assertEqual(float_(Float()), 4.25) - self.assertEqual(float_(IndexLike(99)), 99.0) - self.assertEqual(float_(IndexLike(-1)), -1.0) + self.assertEqual(float_(FloatLike.with_val(4.25)), 4.25) + self.assertEqual(float_(IndexLike.with_val(99)), 99.0) + self.assertEqual(float_(IndexLike.with_val(-1)), -1.0) - self.assertRaises(TypeError, float_, BadFloat()) + self.assertRaises(TypeError, float_, FloatLike.with_val(687)) with warnings.catch_warnings(): warnings.simplefilter("error", DeprecationWarning) - self.assertRaises(DeprecationWarning, float_, BadFloat2()) + self.assertRaises(DeprecationWarning, float_, FloatLike.with_val(subclassof(float)(4.25))) with self.assertWarns(DeprecationWarning): - self.assertEqual(float_(BadFloat2()), 4.25) - self.assertRaises(TypeError, float_, IndexLike(1.25)) - self.assertRaises(OverflowError, float_, IndexLike(2**2000)) + self.assertEqual(float_(FloatLike.with_val(subclassof(float)(4.25))), 4.25) + self.assertRaises(TypeError, float_, IndexLike.with_val(1.25)) + self.assertRaises(OverflowError, float_, IndexLike.with_val(2**2000)) self.assertRaises(TypeError, float_, 1j) self.assertRaises(TypeError, float_, object()) self.assertRaises(SystemError, float_, NULL) - self.assertRaises(RuntimeError, float_, BadFloat3()) + self.assertRaises(RuntimeError, float_, FloatLike.with_exc(RuntimeError)) def test_index(self): # Test PyNumber_Index() @@ -581,15 +562,15 @@ def test_index(self): with warnings.catch_warnings(): warnings.simplefilter("error", DeprecationWarning) - self.assertRaises(DeprecationWarning, index, BadIndex2()) + self.assertRaises(DeprecationWarning, index, IndexLike.with_val(True)) with self.assertWarns(DeprecationWarning): - self.assertEqual(index(BadIndex2()), 1) + self.assertEqual(index(IndexLike.with_val(True)), 1) self.assertRaises(TypeError, index, 1.25) self.assertRaises(TypeError, index, "42") - self.assertRaises(TypeError, index, BadIndex()) + self.assertRaises(TypeError, index, IndexLike.with_val(1.0)) self.assertRaises(TypeError, index, object()) self.assertRaises(SystemError, index, NULL) - self.assertRaises(RuntimeError, index, BadIndex3()) + self.assertRaises(RuntimeError, index, IndexLike.with_exc(RuntimeError)) def test_tobase(self): # Test PyNumber_ToBase() From d052280e1a734f82bd69886444617e39f48cf971 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 17 Nov 2023 13:00:35 +0300 Subject: [PATCH 17/29] + cleanup --- Lib/test/test_capi/test_number.py | 66 +++++++++++++++---------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 3a387347d07d8e..11414f496bfd55 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -2,7 +2,7 @@ import sys import warnings -from test.support import import_helper +from test.support import cpython_only, import_helper from _testbuffer import ndarray _testcapi = import_helper.import_module('_testcapi') @@ -87,6 +87,7 @@ def test_check(self): self.assertTrue(check(0.5)) self.assertTrue(check(FloatLike.with_val(4.25))) self.assertTrue(check(1+2j)) + self.assertFalse(check([])) self.assertFalse(check("1 + 1j")) self.assertFalse(check(object())) @@ -102,7 +103,6 @@ def test_add(self): self.assertEqual(add(42, HasRAddIntSubclass.with_val(NotImplemented)), 42) self.assertEqual(add([1, 2], [3, 4]), [1, 2, 3, 4]) - self.assertRaises(TypeError, add, 1, object()) self.assertRaises(TypeError, add, object(), 1) self.assertRaises(TypeError, add, ndarray([1], (1,)), 2) @@ -148,8 +148,7 @@ def test_matrixmultiply(self): a, b, r = WithMatrixMul([1]), 2, [1, 1] self.assertEqual(matrixmultiply(a, b), r) - self.assertEqual(a, [1]) - self.assertEqual(b, 2) + self.assertEqual((a, b), ([1], 2)) # CRASHES matrixmultiply(NULL, NULL) @@ -271,22 +270,23 @@ def test_rshift(self): self.assertEqual(rshift(5, 3), 5 >> 3) - # RHS implements __rrshift__ but returns NotImplemented + # CRASHES rshift(NULL, 1) + # CRASHES rshift(1, NULL) + + @cpython_only + def test_rshift_print(self): + # This tests correct syntax hint for py2 redirection (>>). + rshift = _testcapi.number_rshift + with self.assertRaises(TypeError) as context: - print >> 42 + rshift(print, 42) self.assertIn('Did you mean "print(, ' 'file=)"?', str(context.exception)) - - # Test stream redirection hint is specific to print with self.assertRaises(TypeError) as context: - max >> sys.stderr + rshift(max, sys.stderr) self.assertNotIn('Did you mean ', str(context.exception)) - with self.assertRaises(TypeError) as context: - 1 >> "spam" - - # CRASHES rshift(NULL, 1) - # CRASHES rshift(1, NULL) + rshift(1, "spam") def test_and(self): # Test PyNumber_And() @@ -328,8 +328,7 @@ def test_inplaceadd(self): a, b, r = [1, 2], [3, 4], [1, 2, 3, 4] self.assertEqual(inplaceadd(a, b), r) - self.assertEqual(a, r) - self.assertEqual(b, [3, 4]) + self.assertEqual((a, b), (r, [3, 4])) self.assertRaises(TypeError, inplaceadd, HasIAdd.with_val(NotImplemented), object()) self.assertRaises(TypeError, inplaceadd, 1, object()) @@ -358,13 +357,11 @@ def test_inplacemultiply(self): a, b, r = [1], 2, [1, 1] self.assertEqual(inplacemultiply(a, b), r) - self.assertEqual(a, r) - self.assertEqual(b, 2) + self.assertEqual((a, b), (r, 2)) a, b = 2, [1] self.assertEqual(inplacemultiply(a, b), r) - self.assertEqual(a, 2) - self.assertEqual(b, [1]) + self.assertEqual((a, b), (2, [1])) self.assertRaises(TypeError, inplacemultiply, 1, object()) self.assertRaises(TypeError, inplacemultiply, object(), 1) @@ -380,8 +377,7 @@ def test_inplacematrixmultiply(self): a, b, r = WithMatrixMul([1]), 2, [1, 1] self.assertEqual(inplacematrixmultiply(a, b), r) - self.assertEqual(a, r) - self.assertEqual(b, 2) + self.assertEqual((a, b), (r, 2)) self.assertRaises(TypeError, inplacematrixmultiply, 2, a) @@ -456,8 +452,7 @@ def test_inplaceand(self): a, b, r = {1, 2}, {2, 3}, {1, 2} & {2, 3} self.assertEqual(inplaceand(a, b), r) - self.assertEqual(a, r) - self.assertEqual(b, {2, 3}) + self.assertEqual((a, b), (r, {2, 3})) # CRASHES inplaceand(NULL, 42) # CRASHES inplaceand(42, NULL) @@ -470,8 +465,7 @@ def test_inplacexor(self): a, b, r = {1, 2}, {2, 3}, {1, 2} ^ {2, 3} self.assertEqual(inplacexor(a, b), r) - self.assertEqual(a, r) - self.assertEqual(b, {2, 3}) + self.assertEqual((a, b), (r, {2, 3})) # CRASHES inplacexor(NULL, 42) # CRASHES inplacexor(42, NULL) @@ -484,8 +478,7 @@ def test_inplaceor(self): a, b, r = {1, 2}, {2, 3}, {1, 2} | {2, 3} self.assertEqual(inplaceor(a, b), r) - self.assertEqual(a, r) - self.assertEqual(b, {2, 3}) + self.assertEqual((a, b), (r, {2, 3})) # CRASHES inplaceor(NULL, 42) # CRASHES inplaceor(42, NULL) @@ -509,6 +502,8 @@ def test_long(self): self.assertRaises(DeprecationWarning, long, IntLike.with_val(True)) with self.assertWarns(DeprecationWarning): self.assertEqual(long(IntLike.with_val(True)), 1) + self.assertRaises(RuntimeError, long, IntLike.with_exc(RuntimeError)) + with warnings.catch_warnings(): warnings.simplefilter("error", DeprecationWarning) self.assertRaises(DeprecationWarning, long, HasTrunc.with_val(42)) @@ -518,16 +513,14 @@ def test_long(self): self.assertEqual(long(HasTrunc.with_val(subclassof(int)(42))), 42) with self.assertWarns(DeprecationWarning): self.assertEqual(long(HasTrunc.with_val(IndexLike.with_val(42))), 42) - with self.assertWarns(DeprecationWarning): self.assertRaises(TypeError, long, HasTrunc.with_val(1.25)) + self.assertRaises(RuntimeError, long, HasTrunc.with_exc(RuntimeError)) + self.assertRaises(ValueError, long, HasBadTrunc()) self.assertRaises(TypeError, long, 1j) self.assertRaises(TypeError, long, object()) self.assertRaises(SystemError, long, NULL) - self.assertRaises(RuntimeError, long, HasTrunc.with_exc(RuntimeError)) - self.assertRaises(ValueError, long, HasBadTrunc()) - self.assertRaises(RuntimeError, long, IntLike.with_exc(RuntimeError)) def test_float(self): # Test PyNumber_Float() @@ -547,12 +540,14 @@ def test_float(self): self.assertRaises(DeprecationWarning, float_, FloatLike.with_val(subclassof(float)(4.25))) with self.assertWarns(DeprecationWarning): self.assertEqual(float_(FloatLike.with_val(subclassof(float)(4.25))), 4.25) + self.assertRaises(RuntimeError, float_, FloatLike.with_exc(RuntimeError)) + self.assertRaises(TypeError, float_, IndexLike.with_val(1.25)) self.assertRaises(OverflowError, float_, IndexLike.with_val(2**2000)) + self.assertRaises(TypeError, float_, 1j) self.assertRaises(TypeError, float_, object()) self.assertRaises(SystemError, float_, NULL) - self.assertRaises(RuntimeError, float_, FloatLike.with_exc(RuntimeError)) def test_index(self): # Test PyNumber_Index() @@ -565,12 +560,13 @@ def test_index(self): self.assertRaises(DeprecationWarning, index, IndexLike.with_val(True)) with self.assertWarns(DeprecationWarning): self.assertEqual(index(IndexLike.with_val(True)), 1) + self.assertRaises(TypeError, index, IndexLike.with_val(1.0)) + self.assertRaises(RuntimeError, index, IndexLike.with_exc(RuntimeError)) + self.assertRaises(TypeError, index, 1.25) self.assertRaises(TypeError, index, "42") - self.assertRaises(TypeError, index, IndexLike.with_val(1.0)) self.assertRaises(TypeError, index, object()) self.assertRaises(SystemError, index, NULL) - self.assertRaises(RuntimeError, index, IndexLike.with_exc(RuntimeError)) def test_tobase(self): # Test PyNumber_ToBase() From c04679c3b68c9957a0b1c2440c52838ac62a72c9 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 18 Nov 2023 18:25:23 +0300 Subject: [PATCH 18/29] use instead generic tests for unary functions --- Lib/test/test_capi/test_number.py | 77 +++++++++++++++---------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 11414f496bfd55..464361e725897c 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -10,6 +10,9 @@ NULL = None +class BadDescr: + def __get__(self, obj, objtype=None): + raise RuntimeError class WithDunder: @classmethod @@ -28,6 +31,13 @@ def meth(*args): setattr(cls, cls.methname, meth) return obj + @classmethod + def with_badattr(cls): + obj = super().__new__(cls) + setattr(cls, cls.methname, BadDescr()) + return obj + + class IndexLike(WithDunder): methname = '__index__' @@ -40,9 +50,6 @@ class FloatLike(WithDunder): class HasTrunc(WithDunder): methname = '__trunc__' -class BadDescr: - def __get__(self, obj, objtype=None): - raise ValueError class HasBadTrunc: __trunc__ = BadDescr() @@ -210,50 +217,38 @@ def test_power(self): # CRASHES power(NULL, 42) # CRASHES power(42, NULL) - def test_negative(self): - # Test PyNumber_Negative() - negative = _testcapi.number_negative - - self.assertEqual(negative(42), -42) - self.assertEqual(negative(1.25), -1.25) - - self.assertRaises(TypeError, negative, "123") - self.assertRaises(TypeError, negative, object()) - self.assertRaises(SystemError, negative, NULL) - - def test_positive(self): - # Test PyNumber_Positive() - positive = _testcapi.number_positive - - self.assertEqual(positive(-1), +(-1)) - self.assertEqual(positive(1.25), 1.25) + def test_unary_ops(self): + # Common tests for unary functions (PyNumber_Negative(), + # PyNumber_Positive(), PyNumber_Absolute() and PyNumber_Invert() + methmap = {'__neg__': _testcapi.number_negative, + '__pos__': _testcapi.number_positive, + '__abs__': _testcapi.number_absolute, + '__invert__': _testcapi.number_invert} - self.assertRaises(TypeError, positive, "123") - self.assertRaises(TypeError, positive, object()) - self.assertRaises(SystemError, positive, NULL) + for name, func in methmap.items(): + # Generic object: no tp_as_number structure + self.assertRaises(TypeError, func, object()) - def test_absolute(self): - # Test PyNumber_Absolute() - absolute = _testcapi.number_absolute + # Has tp_as_number, but not unary + class HasAdd(WithDunder): + methname = '__add__' + self.assertRaises(TypeError, func, HasAdd.with_val("don't care")) - self.assertEqual(absolute(-1), abs(-1)) - self.assertEqual(absolute(-1.25), 1.25) - self.assertEqual(absolute(1j), 1.0) + class HasMeth(WithDunder): + methname = name - self.assertRaises(TypeError, absolute, "123") - self.assertRaises(TypeError, absolute, object()) - self.assertRaises(SystemError, absolute, NULL) + # Has dunder method, but it's a bad descriptor + self.assertRaises(RuntimeError, func, HasMeth.with_badattr()) - def test_invert(self): - # Test PyNumber_Invert() - invert = _testcapi.number_invert + # Dunder method trigger an error + self.assertRaises(ValueError, func, HasMeth.with_exc(ValueError)) - self.assertEqual(invert(123), ~123) + # Finally, it returns something + self.assertEqual(func(HasMeth.with_val(42)), 42) + self.assertEqual(func(HasMeth.with_val(NotImplemented)), NotImplemented) - self.assertRaises(TypeError, invert, 1.25) - self.assertRaises(TypeError, invert, "123") - self.assertRaises(TypeError, invert, object()) - self.assertRaises(SystemError, invert, NULL) + # And accept NULL + self.assertRaises(SystemError, func, NULL) def test_lshift(self): # Test PyNumber_Lshift() @@ -516,7 +511,7 @@ def test_long(self): with self.assertWarns(DeprecationWarning): self.assertRaises(TypeError, long, HasTrunc.with_val(1.25)) self.assertRaises(RuntimeError, long, HasTrunc.with_exc(RuntimeError)) - self.assertRaises(ValueError, long, HasBadTrunc()) + self.assertRaises(RuntimeError, long, HasBadTrunc()) self.assertRaises(TypeError, long, 1j) self.assertRaises(TypeError, long, object()) From efe4aab18c18501060362e57ef49ec19d922543b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 18 Nov 2023 19:03:05 +0300 Subject: [PATCH 19/29] +1 --- Lib/test/test_capi/test_number.py | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 464361e725897c..ce55fe6d50a801 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -1,3 +1,4 @@ +import operator import unittest import sys import warnings @@ -218,37 +219,36 @@ def test_power(self): # CRASHES power(42, NULL) def test_unary_ops(self): - # Common tests for unary functions (PyNumber_Negative(), - # PyNumber_Positive(), PyNumber_Absolute() and PyNumber_Invert() - methmap = {'__neg__': _testcapi.number_negative, - '__pos__': _testcapi.number_positive, - '__abs__': _testcapi.number_absolute, - '__invert__': _testcapi.number_invert} + methmap = {'__neg__': _testcapi.number_negative, # PyNumber_Negative() + '__pos__': _testcapi.number_positive, # PyNumber_Positive() + '__abs__': _testcapi.number_absolute, # PyNumber_Absolute() + '__invert__': _testcapi.number_invert} # PyNumber_Invert() - for name, func in methmap.items(): - # Generic object: no tp_as_number structure - self.assertRaises(TypeError, func, object()) + for name in methmap: + for func in (methmap[name], getattr(operator, name)): + # Generic object, has no tp_as_number structure + self.assertRaises(TypeError, func, object()) - # Has tp_as_number, but not unary - class HasAdd(WithDunder): - methname = '__add__' - self.assertRaises(TypeError, func, HasAdd.with_val("don't care")) + # Has tp_as_number, but not the given unary op + class HasAdd(WithDunder): + methname = '__add__' + self.assertRaises(TypeError, func, HasAdd.with_val("don't care about")) - class HasMeth(WithDunder): - methname = name + class HasMeth(WithDunder): + methname = name - # Has dunder method, but it's a bad descriptor - self.assertRaises(RuntimeError, func, HasMeth.with_badattr()) + # Has dunder method, but it's a bad descriptor + self.assertRaises(RuntimeError, func, HasMeth.with_badattr()) - # Dunder method trigger an error - self.assertRaises(ValueError, func, HasMeth.with_exc(ValueError)) + # Dunder method triggers an error + self.assertRaises(ValueError, func, HasMeth.with_exc(ValueError)) - # Finally, it returns something - self.assertEqual(func(HasMeth.with_val(42)), 42) - self.assertEqual(func(HasMeth.with_val(NotImplemented)), NotImplemented) + # Finally, it returns something + self.assertEqual(func(HasMeth.with_val(42)), 42) + self.assertEqual(func(HasMeth.with_val(NotImplemented)), NotImplemented) - # And accept NULL - self.assertRaises(SystemError, func, NULL) + # C-API function accepts NULL + self.assertRaises(SystemError, methmap[name], NULL) def test_lshift(self): # Test PyNumber_Lshift() From 4d96d35a09f786ee1d952540ae6b971ab2bee8d3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 18 Nov 2023 19:36:50 +0300 Subject: [PATCH 20/29] use instead generic tests for binary functions + misc tests --- Lib/test/test_capi/test_number.py | 747 +++++++++++++++--------------- 1 file changed, 376 insertions(+), 371 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index ce55fe6d50a801..907d5443cf2453 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -16,24 +16,29 @@ def __get__(self, obj, objtype=None): raise RuntimeError class WithDunder: + def _meth(self, *args): + if self.val: + return self.val + if self.exc: + raise self.exc @classmethod def with_val(cls, val): obj = super().__new__(cls) - def meth(*args): - return val - setattr(cls, cls.methname, meth) + obj.val = val + obj.exc = None + setattr(cls, cls.methname, cls._meth) return obj @classmethod def with_exc(cls, exc): obj = super().__new__(cls) - def meth(*args): - raise exc - setattr(cls, cls.methname, meth) + obj.val = None + obj.exc = exc + setattr(cls, cls.methname, cls._meth) return obj - @classmethod - def with_badattr(cls): +class HasBadAttr: + def __new__(cls): obj = super().__new__(cls) setattr(cls, cls.methname, BadDescr()) return obj @@ -60,28 +65,11 @@ def subclassof(base): return type(base.__name__ + 'Subclass', (base,), {}) -class HasRAddIntSubclass(WithDunder, int): - methname = '__radd__' - -class HasIAdd(WithDunder): - methname = '__iadd__' - -class HasRPowIntSubclass(WithDunder, int): - methname = '__rpow__' - -class HasIPow(WithDunder): - methname = '__ipow__' - +class SomeError(Exception): + pass -class WithMatrixMul(list): - def __matmul__(self, other): - return self*other - - def __imatmul__(self, other): - x = self*other - self.clear() - self.extend(x) - return self +class OtherError(Exception): + pass class CAPITest(unittest.TestCase): @@ -101,172 +89,384 @@ def test_check(self): self.assertFalse(check(object())) self.assertFalse(check(NULL)) - def test_add(self): - # Test PyNumber_Add() - add = _testcapi.number_add + def test_unary_ops(self): + methmap = {'__neg__': _testcapi.number_negative, # PyNumber_Negative() + '__pos__': _testcapi.number_positive, # PyNumber_Positive() + '__abs__': _testcapi.number_absolute, # PyNumber_Absolute() + '__invert__': _testcapi.number_invert} # PyNumber_Invert() - self.assertEqual(add(1, 2), 1 + 2) - self.assertEqual(add(1, 0.75), 1 + 0.75) - self.assertEqual(add(0.75, 1), 0.75 + 1) - self.assertEqual(add(42, HasRAddIntSubclass.with_val(NotImplemented)), 42) - self.assertEqual(add([1, 2], [3, 4]), [1, 2, 3, 4]) + for name, func in methmap.items(): + class HasAdd(WithDunder): + methname = '__add__' + class HasMeth(WithDunder): + methname = name + class BadMeth(HasBadAttr): + methname = name - self.assertRaises(TypeError, add, 1, object()) - self.assertRaises(TypeError, add, object(), 1) - self.assertRaises(TypeError, add, ndarray([1], (1,)), 2) + # Generic object, has no tp_as_number structure + self.assertRaises(TypeError, func, object()) - # CRASHES add(NULL, 42) - # CRASHES add(42, NULL) + # Has tp_as_number, but not the given unary op slot + self.assertRaises(TypeError, func, HasAdd.with_val("don't care about")) - def test_subtract(self): - # Test PyNumber_Subtract() - subtract = _testcapi.number_subtract + # Dunder method triggers an error + self.assertRaises(SomeError, func, HasMeth.with_exc(SomeError)) - self.assertEqual(subtract(1, 2), 1 - 2) + # Finally, it returns something + self.assertEqual(func(HasMeth.with_val(42)), 42) + self.assertEqual(func(HasMeth.with_val(NotImplemented)), NotImplemented) - self.assertRaises(TypeError, subtract, 1, object()) - self.assertRaises(TypeError, subtract, object(), 1) + # C-API function accepts NULL + self.assertRaises(SystemError, func, NULL) + + def test_binary_ops(self): + methmap = {'__add__': _testcapi.number_add, # PyNumber_Add() + '__sub__': _testcapi.number_subtract, # PyNumber_Subtract() + '__mul__': _testcapi.number_multiply, # PyNumber_Multiply() + '__matmul__': _testcapi.number_matrixmultiply, # PyNumber_MatrixMultiply() + '__floordiv__': _testcapi.number_floordivide, # PyNumber_FloorDivide() + '__truediv__': _testcapi.number_truedivide, # PyNumber_TrueDivide() + '__mod__': _testcapi.number_remainder, # PyNumber_Remainder() + '__divmod__': _testcapi.number_divmod, # PyNumber_Divmod() + '__lshift__': _testcapi.number_lshift, # PyNumber_Lshift() + '__rshift__': _testcapi.number_rshift, # PyNumber_Rshift() + '__and__': _testcapi.number_and, # Test PyNumber_And() + '__xor__': _testcapi.number_xor, # Test PyNumber_Xor() + '__or__': _testcapi.number_or, # PyNumber_Or() + '__pow__': _testcapi.number_power, # PyNumber_Power() + } + + for name, func in methmap.items(): + rname = '__r' + name[2:] + class HasPos(WithDunder): + methname = '__pos__' + class HasMeth(WithDunder): + methname = name + class HasRMeth(WithDunder): + methname = rname + + # First argument has no tp_as_number structure + x = object() + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertEqual(func(x, HasRMeth.with_val(42)), 42) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) + + # Has tp_as_number, but not the given binary op slot + x = HasPos.with_val("don't care about") + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertEqual(func(x, HasRMeth.with_val(42)), 42) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) + + # First argument has dunder/rdunder method, but it triggers an error + x = HasMeth.with_exc(SomeError) + self.assertRaises(SomeError, func, x, object()) + self.assertRaises(SomeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(SomeError, func, x, HasMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(SomeError, func, x, HasRMeth.with_val(NotImplemented)) + x = HasRMeth.with_exc(SomeError) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(OtherError)) + self.assertRaises(TypeError, func, x, HasRMeth.with_exc(OtherError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + + # First argument has dunder/rdunder method, but it returns NotImplemented + x = HasMeth.with_val(NotImplemented) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertEqual(func(x, HasRMeth.with_val(42)), 42) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + x = HasRMeth.with_val(NotImplemented) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + + # First argument has dunder/rdunder method and it returns something + x = HasMeth.with_val(123) + self.assertEqual(func(x, object()), 123) + self.assertEqual(func(x, HasPos.with_val("don't care about")), 123) + self.assertEqual(func(x, HasMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasRMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasMeth.with_val(42)), 123) + self.assertEqual(func(x, HasRMeth.with_val(42)), 123) + self.assertEqual(func(x, HasMeth.with_val(NotImplemented)), 123) + self.assertEqual(func(x, HasRMeth.with_val(NotImplemented)), 123) + x = HasRMeth.with_val(123) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + + class IntSubclsWithRMeth2(WithDunder, int): + methname = rname + class FloatSubclsWithRMeth2(WithDunder, float): + methname = rname + + # Test subtype with overloaded rdunder method (slotv != slotw) + if hasattr(int, name): + op = getattr(operator, name, divmod) + try: + self.assertEqual(func(123, IntSubclsWithRMeth2.with_val(NotImplemented)), op(123, 0)) + except ZeroDivisionError: + self.assertRaises(ZeroDivisionError, func, 123, IntSubclsWithRMeth2.with_val(NotImplemented)) + self.assertRaises(ZeroDivisionError, op, 123, 0) + + self.assertEqual(func(123, IntSubclsWithRMeth2.with_val(42)), 42) + self.assertEqual(func(123, FloatSubclsWithRMeth2.with_val(0.5)), 0.5) + + # CRASHES func(NULL, object()) + # CRASHES func(object(), NULL) + + def test_inplace_binary_ops(self): + methmap = {'__iadd__': _testcapi.number_inplaceadd, # PyNumber_InPlaceAdd() + '__isub__': _testcapi.number_inplacesubtract, # PyNumber_InPlaceSubtract() + '__imul__': _testcapi.number_inplacemultiply, # PyNumber_InPlaceMultiply() + '__imatmul__': _testcapi.number_inplacematrixmultiply, # PyNumber_InPlaceMatrixMultiply() + '__ifloordiv__': _testcapi.number_inplacefloordivide, # PyNumber_InPlaceFloorDivide() + '__itruediv__': _testcapi.number_inplacetruedivide, # PyNumber_InPlaceTrueDivide() + '__imod__': _testcapi.number_inplaceremainder, # PyNumber_InPlaceRemainder() + '__ilshift__': _testcapi.number_inplacelshift, # PyNumber_InPlaceLshift() + '__irshift__': _testcapi.number_inplacershift, # PyNumber_InPlaceRshift() + '__iand__': _testcapi.number_inplaceand, # Test PyNumber_InPlaceAnd() + '__ixor__': _testcapi.number_inplacexor, # Test PyNumber_InPlaceXor() + '__ior__': _testcapi.number_inplaceor, # PyNumber_InPlaceOr() + '__ipow__': _testcapi.number_inplacepower, # PyNumber_InPlacePower() + } + + for iname, func in methmap.items(): + name = '__' + iname[3:] + rname = '__r' + name[2:] + class HasPos(WithDunder): + methname = '__pos__' + class HasMeth(WithDunder): + methname = name + class HasRMeth(WithDunder): + methname = rname + class HasIMeth(WithDunder): + methname = iname + + # First argument has no tp_as_number structure + x = object() + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertEqual(func(x, HasRMeth.with_val(42)), 42) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, x, HasIMeth.with_val(NotImplemented)) + + # Has tp_as_number, but not the given binary op slot + x = HasPos.with_val("don't care about") + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertEqual(func(x, HasRMeth.with_val(42)), 42) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, x, HasIMeth.with_val(NotImplemented)) + + # First argument has dunder/rdunder/idunder method, but it triggers an error + x = HasMeth.with_exc(SomeError) + self.assertRaises(SomeError, func, x, object()) + self.assertRaises(SomeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(SomeError, func, x, HasMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasIMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(SomeError, func, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(SomeError, func, x, HasIMeth.with_val(NotImplemented)) + x = HasRMeth.with_exc(SomeError) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(OtherError)) + self.assertRaises(TypeError, func, x, HasRMeth.with_exc(OtherError)) + self.assertRaises(TypeError, func, x, HasIMeth.with_exc(OtherError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) + x = HasIMeth.with_exc(SomeError) + self.assertRaises(SomeError, func, x, object()) + self.assertRaises(SomeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(SomeError, func, x, HasMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasIMeth.with_exc(OtherError)) + self.assertRaises(SomeError, func, x, HasMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(SomeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(SomeError, func, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(SomeError, func, x, HasIMeth.with_val(NotImplemented)) + + # First argument has dunder/rdunder/idunder method, but it returns NotImplemented + x = HasMeth.with_val(NotImplemented) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertEqual(func(x, HasRMeth.with_val(42)), 42) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) + x = HasRMeth.with_val(NotImplemented) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) + x = HasIMeth.with_val(NotImplemented) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertEqual(func(x, HasRMeth.with_val(42)), 42) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) + + # First argument has dunder/rdunder/idunder method and it returns something + x = HasMeth.with_val(123) + self.assertEqual(func(x, object()), 123) + self.assertEqual(func(x, HasPos.with_val("don't care about")), 123) + self.assertEqual(func(x, HasMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasRMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasIMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasMeth.with_val(42)), 123) + self.assertEqual(func(x, HasRMeth.with_val(42)), 123) + self.assertEqual(func(x, HasIMeth.with_val(42)), 123) + self.assertEqual(func(x, HasMeth.with_val(NotImplemented)), 123) + self.assertEqual(func(x, HasRMeth.with_val(NotImplemented)), 123) + self.assertEqual(func(x, HasIMeth.with_val(NotImplemented)), 123) + x = HasRMeth.with_val(123) + self.assertRaises(TypeError, func, x, object()) + self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) + self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) + self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) + self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) + x = HasIMeth.with_val(123) + self.assertEqual(func(x, object()), 123) + self.assertEqual(func(x, HasPos.with_val("don't care about")), 123) + self.assertEqual(func(x, HasMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasRMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasIMeth.with_exc(SomeError)), 123) + self.assertEqual(func(x, HasMeth.with_val(42)), 123) + self.assertEqual(func(x, HasRMeth.with_val(42)), 123) + self.assertEqual(func(x, HasIMeth.with_val(42)), 123) + self.assertEqual(func(x, HasMeth.with_val(NotImplemented)), 123) + self.assertEqual(func(x, HasRMeth.with_val(NotImplemented)), 123) + self.assertEqual(func(x, HasIMeth.with_val(NotImplemented)), 123) + + # CRASHES func(NULL, object()) + # CRASHES func(object(), NULL) + + def test_misc(self): + # PyNumber_Add(), PyNumber_InPlaceAdd() + add = _testcapi.number_add + inplaceadd = _testcapi.number_inplaceadd - # CRASHES subtract(NULL, 42) - # CRASHES subtract(42, NULL) + # test sq_concat/sq_inplace_concat slots + a, b, r = [1, 2], [3, 4], [1, 2, 3, 4] + self.assertEqual(add(a, b), r) + self.assertEqual(a, [1, 2]) + self.assertRaises(TypeError, add, ndarray([1], (1,)), 2) + a, b, r = [1, 2], [3, 4], [1, 2, 3, 4] + self.assertEqual(inplaceadd(a, b), r) + self.assertEqual(a, r) + self.assertRaises(TypeError, inplaceadd, ndarray([1], (1,)), 2) - def test_multiply(self): - # Test PyNumber_Multiply() + # PyNumber_Multiply(), PyNumber_InPlaceMultiply() multiply = _testcapi.number_multiply + inplacemultiply = _testcapi.number_inplacemultiply - self.assertEqual(multiply(2, 3), 2 * 3) - self.assertEqual(multiply([1], 2), [1, 1]) - self.assertEqual(multiply(2, [1]), [1, 1]) + # test sq_repeat/sq_inplace_repeat slots + a, b, r = [1], 2, [1, 1] + self.assertEqual(multiply(a, b), r) + self.assertEqual((a, b), ([1], 2)) + self.assertEqual(multiply(b, a), r) + self.assertEqual((a, b), ([1], 2)) self.assertEqual(multiply([1], -1), []) - - self.assertRaises(TypeError, multiply, 1, object()) - self.assertRaises(TypeError, multiply, object(), 1) self.assertRaises(TypeError, multiply, ndarray([1], (1,)), 2) - self.assertRaises(TypeError, multiply, 2, ndarray([1], (1,))) - self.assertRaises(TypeError, multiply, [1], 3.14) + self.assertRaises(TypeError, multiply, [1], 0.5) self.assertRaises(OverflowError, multiply, [1], PY_SSIZE_T_MAX + 1) self.assertRaises(MemoryError, multiply, [1, 2], PY_SSIZE_T_MAX//2 + 1) - - # CRASHES multiply(NULL, 42) - # CRASHES multiply(42, NULL) - - def test_matrixmultiply(self): - # Test PyNumber_MatrixMultiply() - matrixmultiply = _testcapi.number_matrixmultiply - - a, b, r = WithMatrixMul([1]), 2, [1, 1] - self.assertEqual(matrixmultiply(a, b), r) + a, b, r = [1], 2, [1, 1] + self.assertEqual(inplacemultiply(a, b), r) + self.assertEqual((a, b), (r, 2)) + a = [1] + self.assertEqual(inplacemultiply(b, a), r) self.assertEqual((a, b), ([1], 2)) + self.assertRaises(TypeError, inplacemultiply, ndarray([1], (1,)), 2) + self.assertRaises(OverflowError, inplacemultiply, [1], PY_SSIZE_T_MAX + 1) + self.assertRaises(MemoryError, inplacemultiply, [1, 2], PY_SSIZE_T_MAX//2 + 1) - # CRASHES matrixmultiply(NULL, NULL) - - def test_floordivide(self): - # Test PyNumber_FloorDivide() - floordivide = _testcapi.number_floordivide - - self.assertEqual(floordivide(4, 3), 4 // 3) - - # CRASHES floordivide(NULL, 42) - # CRASHES floordivide(42, NULL) - - def test_truedivide(self): - # Test PyNumber_TrueDivide() - truedivide = _testcapi.number_truedivide - - self.assertEqual(truedivide(3, 4), 3 / 4) - - # CRASHES truedivide(NULL, 42) - # CRASHES truedivide(42, NULL) - - def test_remainder(self): - # Test PyNumber_Remainder() - remainder = _testcapi.number_remainder - - self.assertEqual(remainder(4, 3), 4 % 3) - - # CRASHES remainder(NULL, 42) - # CRASHES remainder(42, NULL) - - def test_divmod(self): - # Test PyNumber_divmod() - divmod_ = _testcapi.number_divmod - - self.assertEqual(divmod_(4, 3), divmod(4, 3)) - - # CRASHES divmod_(NULL, 42) - # CRASHES divmod_(42, NULL) - - def test_power(self): - # Test PyNumber_Power() + # PyNumber_Power() power = _testcapi.number_power - self.assertEqual(power(4, 3), pow(4, 3)) - self.assertEqual(power(0.5, 2), pow(0.5, 2)) - self.assertEqual(power(2, -1.0), pow(2, -1.0)) + # ternary op self.assertEqual(power(4, 11, 5), pow(4, 11, 5)) - self.assertEqual(power(4, subclassof(int)(11)), pow(4, 11)) - self.assertEqual(power(4, HasRPowIntSubclass.with_val(42)), 42) - self.assertEqual(power(4, HasRPowIntSubclass.with_val(NotImplemented)), 1) - - self.assertRaises(TypeError, power, "spam", 42) - self.assertRaises(TypeError, power, object(), 42) - self.assertRaises(TypeError, power, 42, "spam") - self.assertRaises(TypeError, power, 42, object()) - self.assertRaises(TypeError, power, 1, 2, "spam") - self.assertRaises(TypeError, power, 1, 2, object()) - - # CRASHES power(NULL, 42) - # CRASHES power(42, NULL) - - def test_unary_ops(self): - methmap = {'__neg__': _testcapi.number_negative, # PyNumber_Negative() - '__pos__': _testcapi.number_positive, # PyNumber_Positive() - '__abs__': _testcapi.number_absolute, # PyNumber_Absolute() - '__invert__': _testcapi.number_invert} # PyNumber_Invert() - - for name in methmap: - for func in (methmap[name], getattr(operator, name)): - # Generic object, has no tp_as_number structure - self.assertRaises(TypeError, func, object()) - - # Has tp_as_number, but not the given unary op - class HasAdd(WithDunder): - methname = '__add__' - self.assertRaises(TypeError, func, HasAdd.with_val("don't care about")) - - class HasMeth(WithDunder): - methname = name - - # Has dunder method, but it's a bad descriptor - self.assertRaises(RuntimeError, func, HasMeth.with_badattr()) - - # Dunder method triggers an error - self.assertRaises(ValueError, func, HasMeth.with_exc(ValueError)) - - # Finally, it returns something - self.assertEqual(func(HasMeth.with_val(42)), 42) - self.assertEqual(func(HasMeth.with_val(NotImplemented)), NotImplemented) - - # C-API function accepts NULL - self.assertRaises(SystemError, methmap[name], NULL) - - def test_lshift(self): - # Test PyNumber_Lshift() - lshift = _testcapi.number_lshift - - self.assertEqual(lshift(3, 5), 3 << 5) - - # CRASHES lshift(NULL, 1) - # CRASHES lshift(1, NULL) - - def test_rshift(self): - # Test PyNumber_Rshift() - rshift = _testcapi.number_rshift - - self.assertEqual(rshift(5, 3), 5 >> 3) - - # CRASHES rshift(NULL, 1) - # CRASHES rshift(1, NULL) + self.assertRaises(TypeError, power, 4, 11, 1.25) + self.assertRaises(TypeError, power, 4, 11, object()) @cpython_only def test_rshift_print(self): @@ -283,201 +483,6 @@ def test_rshift_print(self): with self.assertRaises(TypeError) as context: rshift(1, "spam") - def test_and(self): - # Test PyNumber_And() - and_ = _testcapi.number_and - - self.assertEqual(and_(0b10, 0b01), 0b10 & 0b01) - self.assertEqual(and_({1, 2}, {2, 3}), {1, 2} & {2, 3}) - - # CRASHES and_(NULL, 1) - # CRASHES and_(1, NULL) - - def test_xor(self): - # Test PyNumber_Xor() - xor = _testcapi.number_xor - - self.assertEqual(xor(0b10, 0b01), 0b10 ^ 0b01) - self.assertEqual(xor({1, 2}, {2, 3}), {1, 2} ^ {2, 3}) - - # CRASHES xor(NULL, 1) - # CRASHES xor(1, NULL) - - def test_or(self): - # Test PyNumber_Or() - or_ = _testcapi.number_or - - self.assertEqual(or_(0b10, 0b01), 0b10 | 0b01) - self.assertEqual(or_({1, 2}, {2, 3}), {1, 2} | {2, 3}) - - # CRASHES or_(NULL, 1) - # CRASHES or_(1, NULL) - - def test_inplaceadd(self): - # Test PyNumber_InPlaceAdd() - inplaceadd = _testcapi.number_inplaceadd - - self.assertEqual(inplaceadd(1, 2), 1 + 2) - self.assertEqual(inplaceadd(1, 0.75), 1 + 0.75) - self.assertEqual(inplaceadd(0.75, 1), 0.75 + 1) - - a, b, r = [1, 2], [3, 4], [1, 2, 3, 4] - self.assertEqual(inplaceadd(a, b), r) - self.assertEqual((a, b), (r, [3, 4])) - - self.assertRaises(TypeError, inplaceadd, HasIAdd.with_val(NotImplemented), object()) - self.assertRaises(TypeError, inplaceadd, 1, object()) - self.assertRaises(TypeError, inplaceadd, object(), 1) - - # CRASHES inplaceadd(NULL, 42) - # CRASHES inplaceadd(42, NULL) - - def test_inplacesubtract(self): - # Test PyNumber_InPlaceSubtract() - inplacesubtract = _testcapi.number_inplacesubtract - - self.assertEqual(inplacesubtract(1, 2), 1 - 2) - - self.assertRaises(TypeError, inplacesubtract, 1, object()) - self.assertRaises(TypeError, inplacesubtract, object(), 1) - - # CRASHES inplacesubtract(NULL, 42) - # CRASHES inplacesubtract(42, NULL) - - def test_inplacemultiply(self): - # Test PyNumber_InPlaceMultiply() - inplacemultiply = _testcapi.number_inplacemultiply - - self.assertEqual(inplacemultiply(2, 3), 2 * 3) - - a, b, r = [1], 2, [1, 1] - self.assertEqual(inplacemultiply(a, b), r) - self.assertEqual((a, b), (r, 2)) - - a, b = 2, [1] - self.assertEqual(inplacemultiply(a, b), r) - self.assertEqual((a, b), (2, [1])) - - self.assertRaises(TypeError, inplacemultiply, 1, object()) - self.assertRaises(TypeError, inplacemultiply, object(), 1) - self.assertRaises(TypeError, inplacemultiply, ndarray([1], (1,)), 2) - self.assertRaises(TypeError, inplacemultiply, 2, ndarray([1], (1,))) - - # CRASHES inplacemultiply(NULL, 42) - # CRASHES inplacemultiply(42, NULL) - - def test_inplacematrixmultiply(self): - # Test PyNumber_InPlaceMatrixMultiply() - inplacematrixmultiply = _testcapi.number_inplacematrixmultiply - - a, b, r = WithMatrixMul([1]), 2, [1, 1] - self.assertEqual(inplacematrixmultiply(a, b), r) - self.assertEqual((a, b), (r, 2)) - - self.assertRaises(TypeError, inplacematrixmultiply, 2, a) - - # CRASHES inplacematrixmultiply(NULL, 42) - # CRASHES inplacematrixmultiply(42, NULL) - - def test_inplacefloordivide(self): - # Test PyNumber_InPlaceFloorDivide() - inplacefloordivide = _testcapi.number_inplacefloordivide - - self.assertEqual(inplacefloordivide(4, 3), 4 // 3) - - # CRASHES inplacefloordivide(NULL, 42) - # CRASHES inplacefloordivide(42, NULL) - - def test_inplacetruedivide(self): - # Test PyNumber_InPlaceTrueDivide() - inplacetruedivide = _testcapi.number_inplacetruedivide - - self.assertEqual(inplacetruedivide(3, 4), 3 / 4) - - # CRASHES inplacetruedivide(NULL, 42) - # CRASHES inplacetruedivide(42, NULL) - - def test_inplaceremainder(self): - # Test PyNumber_InPlaceRemainder() - inplaceremainder = _testcapi.number_inplaceremainder - - self.assertEqual(inplaceremainder(4, 3), 4 % 3) - - # CRASHES inplaceremainder(NULL, 42) - # CRASHES inplaceremainder(42, NULL) - - def test_inplacepower(self): - # Test PyNumber_InPlacePower() - inplacepower = _testcapi.number_inplacepower - - self.assertEqual(inplacepower(2, 3), pow(2, 3)) - self.assertEqual(inplacepower(2, 3, 4), pow(2, 3, 4)) - self.assertEqual(inplacepower(HasIPow.with_val(42), 2), 42) - - self.assertRaises(TypeError, inplacepower, - HasIPow.with_val(NotImplemented), object()) - self.assertRaises(TypeError, inplacepower, object(), 2) - - # CRASHES inplacepower(NULL, 42) - # CRASHES inplacepower(42, NULL) - - def test_inplacelshift(self): - # Test PyNumber_InPlaceLshift() - inplacelshift = _testcapi.number_inplacelshift - - self.assertEqual(inplacelshift(3, 5), 3 << 5) - - # CRASHES inplacelshift(NULL, 42) - # CRASHES inplacelshift(42, NULL) - - def test_inplacershift(self): - # Test PyNumber_InPlaceRshift() - inplacershift = _testcapi.number_inplacershift - - self.assertEqual(inplacershift(5, 3), 5 >> 3) - - # CRASHES inplacershift(NULL, 42) - # CRASHES inplacershift(42, NULL) - - def test_inplaceand(self): - # Test PyNumber_InPlaceAnd() - inplaceand = _testcapi.number_inplaceand - - self.assertEqual(inplaceand(0b10, 0b01), 0b10 & 0b01) - - a, b, r = {1, 2}, {2, 3}, {1, 2} & {2, 3} - self.assertEqual(inplaceand(a, b), r) - self.assertEqual((a, b), (r, {2, 3})) - - # CRASHES inplaceand(NULL, 42) - # CRASHES inplaceand(42, NULL) - - def test_inplacexor(self): - # Test PyNumber_InPlaceXor() - inplacexor = _testcapi.number_inplacexor - - self.assertEqual(inplacexor(0b10, 0b01), 0b10 ^ 0b01) - - a, b, r = {1, 2}, {2, 3}, {1, 2} ^ {2, 3} - self.assertEqual(inplacexor(a, b), r) - self.assertEqual((a, b), (r, {2, 3})) - - # CRASHES inplacexor(NULL, 42) - # CRASHES inplacexor(42, NULL) - - def test_inplaceor(self): - # Test PyNumber_InPlaceOr() - inplaceor = _testcapi.number_inplaceor - - self.assertEqual(inplaceor(0b10, 0b01), 0b10 | 0b01) - - a, b, r = {1, 2}, {2, 3}, {1, 2} | {2, 3} - self.assertEqual(inplaceor(a, b), r) - self.assertEqual((a, b), (r, {2, 3})) - - # CRASHES inplaceor(NULL, 42) - # CRASHES inplaceor(42, NULL) - def test_long(self): # Test PyNumber_Long() long = _testcapi.number_long From 74abc5d9980823f84d7d2ac06f6b5a7805698fdb Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 3 Dec 2023 04:15:33 +0300 Subject: [PATCH 21/29] +1 --- Lib/test/test_capi/test_number.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 907d5443cf2453..7b0a2de4c2edf3 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -130,8 +130,8 @@ def test_binary_ops(self): '__divmod__': _testcapi.number_divmod, # PyNumber_Divmod() '__lshift__': _testcapi.number_lshift, # PyNumber_Lshift() '__rshift__': _testcapi.number_rshift, # PyNumber_Rshift() - '__and__': _testcapi.number_and, # Test PyNumber_And() - '__xor__': _testcapi.number_xor, # Test PyNumber_Xor() + '__and__': _testcapi.number_and, # PyNumber_And() + '__xor__': _testcapi.number_xor, # PyNumber_Xor() '__or__': _testcapi.number_or, # PyNumber_Or() '__pow__': _testcapi.number_power, # PyNumber_Power() } @@ -257,8 +257,8 @@ def test_inplace_binary_ops(self): '__imod__': _testcapi.number_inplaceremainder, # PyNumber_InPlaceRemainder() '__ilshift__': _testcapi.number_inplacelshift, # PyNumber_InPlaceLshift() '__irshift__': _testcapi.number_inplacershift, # PyNumber_InPlaceRshift() - '__iand__': _testcapi.number_inplaceand, # Test PyNumber_InPlaceAnd() - '__ixor__': _testcapi.number_inplacexor, # Test PyNumber_InPlaceXor() + '__iand__': _testcapi.number_inplaceand, # PyNumber_InPlaceAnd() + '__ixor__': _testcapi.number_inplacexor, # PyNumber_InPlaceXor() '__ior__': _testcapi.number_inplaceor, # PyNumber_InPlaceOr() '__ipow__': _testcapi.number_inplacepower, # PyNumber_InPlacePower() } @@ -463,9 +463,13 @@ def test_misc(self): # PyNumber_Power() power = _testcapi.number_power + class HasPow(WithDunder): + methname = '__pow__' + # ternary op self.assertEqual(power(4, 11, 5), pow(4, 11, 5)) self.assertRaises(TypeError, power, 4, 11, 1.25) + self.assertRaises(TypeError, power, 4, 11, HasPow.with_val(NotImplemented)) self.assertRaises(TypeError, power, 4, 11, object()) @cpython_only From de8f7f316ec6bf8bdc3704f6e1916062fce2a09f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 26 Mar 2024 07:37:40 +0300 Subject: [PATCH 22/29] Include only basic tests for unary/binary ops (with builtin types) --- Lib/test/test_capi/test_number.py | 317 +++--------------------------- 1 file changed, 31 insertions(+), 286 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 7b0a2de4c2edf3..9068aa8ffdde11 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -1,3 +1,4 @@ +import itertools import operator import unittest import sys @@ -96,29 +97,22 @@ def test_unary_ops(self): '__invert__': _testcapi.number_invert} # PyNumber_Invert() for name, func in methmap.items(): - class HasAdd(WithDunder): - methname = '__add__' - class HasMeth(WithDunder): - methname = name - class BadMeth(HasBadAttr): - methname = name - # Generic object, has no tp_as_number structure self.assertRaises(TypeError, func, object()) - # Has tp_as_number, but not the given unary op slot - self.assertRaises(TypeError, func, HasAdd.with_val("don't care about")) - - # Dunder method triggers an error - self.assertRaises(SomeError, func, HasMeth.with_exc(SomeError)) - - # Finally, it returns something - self.assertEqual(func(HasMeth.with_val(42)), 42) - self.assertEqual(func(HasMeth.with_val(NotImplemented)), NotImplemented) - # C-API function accepts NULL self.assertRaises(SystemError, func, NULL) + # Behave as corresponding unary operation + op = getattr(operator, name) + for x in [0, 42, -1, 3.14, 1+2j]: + try: + op(x) + except TypeError: + self.assertRaises(TypeError, func, x) + else: + self.assertEqual(func(x), op(x)) + def test_binary_ops(self): methmap = {'__add__': _testcapi.number_add, # PyNumber_Add() '__sub__': _testcapi.number_subtract, # PyNumber_Subtract() @@ -134,121 +128,7 @@ def test_binary_ops(self): '__xor__': _testcapi.number_xor, # PyNumber_Xor() '__or__': _testcapi.number_or, # PyNumber_Or() '__pow__': _testcapi.number_power, # PyNumber_Power() - } - - for name, func in methmap.items(): - rname = '__r' + name[2:] - class HasPos(WithDunder): - methname = '__pos__' - class HasMeth(WithDunder): - methname = name - class HasRMeth(WithDunder): - methname = rname - - # First argument has no tp_as_number structure - x = object() - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertEqual(func(x, HasRMeth.with_val(42)), 42) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) - - # Has tp_as_number, but not the given binary op slot - x = HasPos.with_val("don't care about") - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertEqual(func(x, HasRMeth.with_val(42)), 42) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) - - # First argument has dunder/rdunder method, but it triggers an error - x = HasMeth.with_exc(SomeError) - self.assertRaises(SomeError, func, x, object()) - self.assertRaises(SomeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(SomeError, func, x, HasMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(SomeError, func, x, HasRMeth.with_val(NotImplemented)) - x = HasRMeth.with_exc(SomeError) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(OtherError)) - self.assertRaises(TypeError, func, x, HasRMeth.with_exc(OtherError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - - # First argument has dunder/rdunder method, but it returns NotImplemented - x = HasMeth.with_val(NotImplemented) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertEqual(func(x, HasRMeth.with_val(42)), 42) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - x = HasRMeth.with_val(NotImplemented) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - - # First argument has dunder/rdunder method and it returns something - x = HasMeth.with_val(123) - self.assertEqual(func(x, object()), 123) - self.assertEqual(func(x, HasPos.with_val("don't care about")), 123) - self.assertEqual(func(x, HasMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasRMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasMeth.with_val(42)), 123) - self.assertEqual(func(x, HasRMeth.with_val(42)), 123) - self.assertEqual(func(x, HasMeth.with_val(NotImplemented)), 123) - self.assertEqual(func(x, HasRMeth.with_val(NotImplemented)), 123) - x = HasRMeth.with_val(123) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - - class IntSubclsWithRMeth2(WithDunder, int): - methname = rname - class FloatSubclsWithRMeth2(WithDunder, float): - methname = rname - - # Test subtype with overloaded rdunder method (slotv != slotw) - if hasattr(int, name): - op = getattr(operator, name, divmod) - try: - self.assertEqual(func(123, IntSubclsWithRMeth2.with_val(NotImplemented)), op(123, 0)) - except ZeroDivisionError: - self.assertRaises(ZeroDivisionError, func, 123, IntSubclsWithRMeth2.with_val(NotImplemented)) - self.assertRaises(ZeroDivisionError, op, 123, 0) - - self.assertEqual(func(123, IntSubclsWithRMeth2.with_val(42)), 42) - self.assertEqual(func(123, FloatSubclsWithRMeth2.with_val(0.5)), 0.5) - - # CRASHES func(NULL, object()) - # CRASHES func(object(), NULL) - - def test_inplace_binary_ops(self): - methmap = {'__iadd__': _testcapi.number_inplaceadd, # PyNumber_InPlaceAdd() + '__iadd__': _testcapi.number_inplaceadd, # PyNumber_InPlaceAdd() '__isub__': _testcapi.number_inplacesubtract, # PyNumber_InPlaceSubtract() '__imul__': _testcapi.number_inplacemultiply, # PyNumber_InPlaceMultiply() '__imatmul__': _testcapi.number_inplacematrixmultiply, # PyNumber_InPlaceMatrixMultiply() @@ -263,159 +143,23 @@ def test_inplace_binary_ops(self): '__ipow__': _testcapi.number_inplacepower, # PyNumber_InPlacePower() } - for iname, func in methmap.items(): - name = '__' + iname[3:] - rname = '__r' + name[2:] - class HasPos(WithDunder): - methname = '__pos__' - class HasMeth(WithDunder): - methname = name - class HasRMeth(WithDunder): - methname = rname - class HasIMeth(WithDunder): - methname = iname - - # First argument has no tp_as_number structure - x = object() - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertEqual(func(x, HasRMeth.with_val(42)), 42) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, x, HasIMeth.with_val(NotImplemented)) - - # Has tp_as_number, but not the given binary op slot - x = HasPos.with_val("don't care about") - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertEqual(func(x, HasRMeth.with_val(42)), 42) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, x, HasIMeth.with_val(NotImplemented)) - - # First argument has dunder/rdunder/idunder method, but it triggers an error - x = HasMeth.with_exc(SomeError) - self.assertRaises(SomeError, func, x, object()) - self.assertRaises(SomeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(SomeError, func, x, HasMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasIMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(SomeError, func, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(SomeError, func, x, HasIMeth.with_val(NotImplemented)) - x = HasRMeth.with_exc(SomeError) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(OtherError)) - self.assertRaises(TypeError, func, x, HasRMeth.with_exc(OtherError)) - self.assertRaises(TypeError, func, x, HasIMeth.with_exc(OtherError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) - x = HasIMeth.with_exc(SomeError) - self.assertRaises(SomeError, func, x, object()) - self.assertRaises(SomeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(SomeError, func, x, HasMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasIMeth.with_exc(OtherError)) - self.assertRaises(SomeError, func, x, HasMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(SomeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(SomeError, func, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(SomeError, func, x, HasIMeth.with_val(NotImplemented)) - - # First argument has dunder/rdunder/idunder method, but it returns NotImplemented - x = HasMeth.with_val(NotImplemented) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertEqual(func(x, HasRMeth.with_val(42)), 42) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) - x = HasRMeth.with_val(NotImplemented) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) - x = HasIMeth.with_val(NotImplemented) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(SomeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertEqual(func(x, HasRMeth.with_val(42)), 42) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) - - # First argument has dunder/rdunder/idunder method and it returns something - x = HasMeth.with_val(123) - self.assertEqual(func(x, object()), 123) - self.assertEqual(func(x, HasPos.with_val("don't care about")), 123) - self.assertEqual(func(x, HasMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasRMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasIMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasMeth.with_val(42)), 123) - self.assertEqual(func(x, HasRMeth.with_val(42)), 123) - self.assertEqual(func(x, HasIMeth.with_val(42)), 123) - self.assertEqual(func(x, HasMeth.with_val(NotImplemented)), 123) - self.assertEqual(func(x, HasRMeth.with_val(NotImplemented)), 123) - self.assertEqual(func(x, HasIMeth.with_val(NotImplemented)), 123) - x = HasRMeth.with_val(123) - self.assertRaises(TypeError, func, x, object()) - self.assertRaises(TypeError, func, x, HasPos.with_val("don't care about")) - self.assertRaises(TypeError, func, x, HasMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasRMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasIMeth.with_exc(SomeError)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(42)) - self.assertRaises(TypeError, func, x, HasMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasRMeth.with_val(NotImplemented)) - self.assertRaises(TypeError, func, x, HasIMeth.with_val(NotImplemented)) - x = HasIMeth.with_val(123) - self.assertEqual(func(x, object()), 123) - self.assertEqual(func(x, HasPos.with_val("don't care about")), 123) - self.assertEqual(func(x, HasMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasRMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasIMeth.with_exc(SomeError)), 123) - self.assertEqual(func(x, HasMeth.with_val(42)), 123) - self.assertEqual(func(x, HasRMeth.with_val(42)), 123) - self.assertEqual(func(x, HasIMeth.with_val(42)), 123) - self.assertEqual(func(x, HasMeth.with_val(NotImplemented)), 123) - self.assertEqual(func(x, HasRMeth.with_val(NotImplemented)), 123) - self.assertEqual(func(x, HasIMeth.with_val(NotImplemented)), 123) + for name, func in methmap.items(): + cases = [0, 42, 3.14, -1, 123, 1+2j] + + # Generic object, has no tp_as_number structure + for x in cases: + self.assertRaises(TypeError, func, object(), x) + self.assertRaises(TypeError, func, x, object()) + + # Behave as corresponding binary operation + op = getattr(operator, name, divmod) + for x, y in itertools.combinations(cases, 2): + try: + op(x, y) + except (TypeError, ValueError, ZeroDivisionError) as exc: + self.assertRaises(exc.__class__, func, x, y) + else: + self.assertEqual(func(x, y), op(x, y)) # CRASHES func(NULL, object()) # CRASHES func(object(), NULL) @@ -519,7 +263,8 @@ def test_long(self): self.assertEqual(long(HasTrunc.with_val(IndexLike.with_val(42))), 42) with self.assertWarns(DeprecationWarning): self.assertRaises(TypeError, long, HasTrunc.with_val(1.25)) - self.assertRaises(RuntimeError, long, HasTrunc.with_exc(RuntimeError)) + with self.assertWarns(DeprecationWarning): + self.assertRaises(RuntimeError, long, HasTrunc.with_exc(RuntimeError)) self.assertRaises(RuntimeError, long, HasBadTrunc()) self.assertRaises(TypeError, long, 1j) From ab0843054d8e72ab6f42d1e95fbee592f19a3733 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 9 Aug 2024 13:09:53 +0300 Subject: [PATCH 23/29] address review: sort imports --- Lib/test/test_capi/test_number.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 9068aa8ffdde11..2b4a57cc685e94 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -1,14 +1,14 @@ import itertools import operator -import unittest import sys +import unittest import warnings from test.support import cpython_only, import_helper from _testbuffer import ndarray _testcapi = import_helper.import_module('_testcapi') -from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX +from _testcapi import PY_SSIZE_T_MAX, PY_SSIZE_T_MIN NULL = None From 0bccf1c2610186ad1c2c5680c53bd5af8f5bc3e6 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 9 Aug 2024 13:12:01 +0300 Subject: [PATCH 24/29] address review: split test_misc() --- Lib/test/test_capi/test_number.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 2b4a57cc685e94..d705a22e8463fa 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -164,7 +164,7 @@ def test_binary_ops(self): # CRASHES func(NULL, object()) # CRASHES func(object(), NULL) - def test_misc(self): + def test_misc_add(self): # PyNumber_Add(), PyNumber_InPlaceAdd() add = _testcapi.number_add inplaceadd = _testcapi.number_inplaceadd @@ -179,6 +179,7 @@ def test_misc(self): self.assertEqual(a, r) self.assertRaises(TypeError, inplaceadd, ndarray([1], (1,)), 2) + def test_misc_multiply(self) # PyNumber_Multiply(), PyNumber_InPlaceMultiply() multiply = _testcapi.number_multiply inplacemultiply = _testcapi.number_inplacemultiply @@ -204,6 +205,7 @@ def test_misc(self): self.assertRaises(OverflowError, inplacemultiply, [1], PY_SSIZE_T_MAX + 1) self.assertRaises(MemoryError, inplacemultiply, [1, 2], PY_SSIZE_T_MAX//2 + 1) + def test_misc_power(self): # PyNumber_Power() power = _testcapi.number_power From abde55bc46fa93da7c5cda9a79b6ffb51718361a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 9 Aug 2024 13:17:38 +0300 Subject: [PATCH 25/29] Update Lib/test/test_capi/test_number.py Co-authored-by: Victor Stinner --- Lib/test/test_capi/test_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index d705a22e8463fa..7d24ec063487e9 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -86,7 +86,7 @@ def test_check(self): self.assertTrue(check(1+2j)) self.assertFalse(check([])) - self.assertFalse(check("1 + 1j")) + self.assertFalse(check("abc")) self.assertFalse(check(object())) self.assertFalse(check(NULL)) From f4be9c5816d367c7aa8e67eb8edd6f4961803075 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 9 Aug 2024 13:29:53 +0300 Subject: [PATCH 26/29] address review: use import_helper for _testbuffer --- Lib/test/test_capi/test_number.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 7d24ec063487e9..559e53a74f9227 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -5,10 +5,11 @@ import warnings from test.support import cpython_only, import_helper -from _testbuffer import ndarray +_testbuffer = import_helper.import_module('_testbuffer') _testcapi = import_helper.import_module('_testcapi') from _testcapi import PY_SSIZE_T_MAX, PY_SSIZE_T_MIN +from _testbuffer import ndarray NULL = None From 86df16cc82a0014b9d08ea2c5b91726f858ed91b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 9 Aug 2024 13:32:52 +0300 Subject: [PATCH 27/29] fix typo --- Lib/test/test_capi/test_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 559e53a74f9227..22fed603e72904 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -180,7 +180,7 @@ def test_misc_add(self): self.assertEqual(a, r) self.assertRaises(TypeError, inplaceadd, ndarray([1], (1,)), 2) - def test_misc_multiply(self) + def test_misc_multiply(self): # PyNumber_Multiply(), PyNumber_InPlaceMultiply() multiply = _testcapi.number_multiply inplacemultiply = _testcapi.number_inplacemultiply From c620c720d1201a0138aaae80711d89fa880be8a6 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 9 Aug 2024 13:34:11 +0300 Subject: [PATCH 28/29] drop support classes with __trunc__ dunder --- Lib/test/test_capi/test_number.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 22fed603e72904..241d5db278df6f 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -55,13 +55,6 @@ class IntLike(WithDunder): class FloatLike(WithDunder): methname = '__float__' -class HasTrunc(WithDunder): - methname = '__trunc__' - - -class HasBadTrunc: - __trunc__ = BadDescr() - def subclassof(base): return type(base.__name__ + 'Subclass', (base,), {}) @@ -255,21 +248,6 @@ def test_long(self): self.assertEqual(long(IntLike.with_val(True)), 1) self.assertRaises(RuntimeError, long, IntLike.with_exc(RuntimeError)) - with warnings.catch_warnings(): - warnings.simplefilter("error", DeprecationWarning) - self.assertRaises(DeprecationWarning, long, HasTrunc.with_val(42)) - with self.assertWarns(DeprecationWarning): - self.assertEqual(long(HasTrunc.with_val(42)), 42) - with self.assertWarns(DeprecationWarning): - self.assertEqual(long(HasTrunc.with_val(subclassof(int)(42))), 42) - with self.assertWarns(DeprecationWarning): - self.assertEqual(long(HasTrunc.with_val(IndexLike.with_val(42))), 42) - with self.assertWarns(DeprecationWarning): - self.assertRaises(TypeError, long, HasTrunc.with_val(1.25)) - with self.assertWarns(DeprecationWarning): - self.assertRaises(RuntimeError, long, HasTrunc.with_exc(RuntimeError)) - self.assertRaises(RuntimeError, long, HasBadTrunc()) - self.assertRaises(TypeError, long, 1j) self.assertRaises(TypeError, long, object()) self.assertRaises(SystemError, long, NULL) From 556b23bcc199f97803bb9ebcdcbeb601e83e35df Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 9 Aug 2024 13:42:50 +0300 Subject: [PATCH 29/29] address review: skip two test if no ndarray --- Lib/test/test_capi/test_number.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_number.py b/Lib/test/test_capi/test_number.py index 241d5db278df6f..3c1f0f248c37cb 100644 --- a/Lib/test/test_capi/test_number.py +++ b/Lib/test/test_capi/test_number.py @@ -6,10 +6,13 @@ from test.support import cpython_only, import_helper -_testbuffer = import_helper.import_module('_testbuffer') _testcapi = import_helper.import_module('_testcapi') from _testcapi import PY_SSIZE_T_MAX, PY_SSIZE_T_MIN -from _testbuffer import ndarray + +try: + from _testbuffer import ndarray +except ImportError: + ndarray = None NULL = None @@ -158,6 +161,7 @@ def test_binary_ops(self): # CRASHES func(NULL, object()) # CRASHES func(object(), NULL) + @unittest.skipIf(ndarray is None, "needs _testbuffer") def test_misc_add(self): # PyNumber_Add(), PyNumber_InPlaceAdd() add = _testcapi.number_add @@ -173,6 +177,7 @@ def test_misc_add(self): self.assertEqual(a, r) self.assertRaises(TypeError, inplaceadd, ndarray([1], (1,)), 2) + @unittest.skipIf(ndarray is None, "needs _testbuffer") def test_misc_multiply(self): # PyNumber_Multiply(), PyNumber_InPlaceMultiply() multiply = _testcapi.number_multiply