Skip to content

Commit b88f657

Browse files
committed
gh-111495: Add tests for PyComplex C API
1 parent 33ed5fa commit b88f657

File tree

2 files changed

+223
-0
lines changed

2 files changed

+223
-0
lines changed

Lib/test/test_complex.py

+126
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
from test import support
44
from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
55
INVALID_UNDERSCORE_LITERALS)
6+
from test.support import import_helper
7+
from test.test_capi.test_getargs import (BadComplex, BadComplex2, Complex,
8+
FloatSubclass, Float, BadFloat,
9+
BadFloat2, ComplexSubclass)
610

711
from random import random
812
from math import atan2, isnan, copysign
913
import operator
1014

15+
_testcapi = import_helper.import_module('_testcapi')
16+
1117
INF = float("inf")
1218
NAN = float("nan")
1319
# These tests ensure that complex math does the right thing
@@ -20,6 +26,8 @@
2026
(1, 0+0j),
2127
)
2228

29+
NULL = None
30+
2331
class ComplexTest(unittest.TestCase):
2432

2533
def assertAlmostEqual(self, a, b):
@@ -72,6 +80,15 @@ def assertFloatsAreIdentical(self, x, y):
7280
msg += ': zeros have different signs'
7381
self.fail(msg.format(x, y))
7482

83+
def assertComplexesAreIdentical(self, x, y):
84+
"""assert that complex numbers x and y are identical
85+
86+
I.e. they have identical real and imag components.
87+
88+
"""
89+
(self.assertFloatsAreIdentical(x.real, y.real)
90+
and self.assertFloatsAreIdentical(x.imag, y.imag))
91+
7592
def assertClose(self, x, y, eps=1e-9):
7693
"""Return true iff complexes x and y "are close"."""
7794
self.assertCloseAbs(x.real, y.real, eps)
@@ -792,5 +809,114 @@ def test_format(self):
792809
self.assertEqual(format(complex(INF, -1), 'F'), 'INF-1.000000j')
793810

794811

812+
class CAPIComplexTest(ComplexTest):
813+
def test_check(self):
814+
# Test PyComplex_Check()
815+
check = _testcapi.complex_check
816+
817+
self.assertTrue(check(1+2j))
818+
self.assertTrue(check(ComplexSubclass(1+2j)))
819+
self.assertFalse(check(Complex()))
820+
self.assertFalse(check(3))
821+
self.assertFalse(check([]))
822+
self.assertFalse(check(object()))
823+
824+
# CRASHES check(NULL)
825+
826+
def test_checkexact(self):
827+
# PyComplex_CheckExact()
828+
checkexact = _testcapi.complex_checkexact
829+
830+
self.assertTrue(checkexact(1+2j))
831+
self.assertFalse(checkexact(ComplexSubclass(1+2j)))
832+
self.assertFalse(checkexact(Complex()))
833+
self.assertFalse(checkexact(3))
834+
self.assertFalse(checkexact([]))
835+
self.assertFalse(checkexact(object()))
836+
837+
# CRASHES checkexact(NULL)
838+
839+
def test_fromccomplex(self):
840+
# Test PyComplex_FromCComplex()
841+
fromccomplex = _testcapi.complex_fromccomplex
842+
843+
self.assertComplexesAreIdentical(fromccomplex(1+2j), 1.0+2.0j)
844+
845+
def test_fromdoubles(self):
846+
# Test PyComplex_FromDoubles()
847+
fromdoubles = _testcapi.complex_fromdoubles
848+
849+
self.assertComplexesAreIdentical(fromdoubles(1.0, 2.0), 1.0+2.0j)
850+
851+
def test_realasdouble(self):
852+
# Test PyComplex_RealAsDouble()
853+
realasdouble = _testcapi.complex_realasdouble
854+
855+
self.assertFloatsAreIdentical(realasdouble(1+2j), 1.0)
856+
self.assertFloatsAreIdentical(realasdouble(1), 1.0)
857+
self.assertFloatsAreIdentical(realasdouble(-1), -1.0)
858+
# Function doesn't support classes with __complex__ dunder, see #109598
859+
#self.assertFloatsAreIdentical(realasdouble(Complex()), 4.25)
860+
#self.assertFloatsAreIdentical(realasdouble(3.14), 3.14)
861+
#self.assertFloatsAreIdentical(realasdouble(FloatSubclass(3.14)), 3.14)
862+
#self.assertFloatsAreIdentical(realasdouble(Float()), 4.25)
863+
#with self.assertWarns(DeprecationWarning):
864+
# self.assertFloatsAreIdentical(realasdouble(BadComplex2()), 4.25)
865+
#with self.assertWarns(DeprecationWarning):
866+
# self.assertFloatsAreIdentical(realasdouble(BadFloat2()), 4.25)
867+
self.assertRaises(TypeError, realasdouble, BadComplex())
868+
self.assertRaises(TypeError, realasdouble, BadFloat())
869+
self.assertRaises(TypeError, realasdouble, object())
870+
871+
# CRASHES realasdouble(NULL)
872+
873+
def test_imagasdouble(self):
874+
# Test PyComplex_ImagAsDouble()
875+
imagasdouble = _testcapi.complex_imagasdouble
876+
877+
self.assertFloatsAreIdentical(imagasdouble(1+2j), 2.0)
878+
self.assertFloatsAreIdentical(imagasdouble(1), 0.0)
879+
self.assertFloatsAreIdentical(imagasdouble(-1), 0.0)
880+
# Function doesn't support classes with __complex__ dunder, see #109598
881+
#self.assertFloatsAreIdentical(imagasdouble(Complex()), 0.5)
882+
#self.assertFloatsAreIdentical(imagasdouble(3.14), 0.0)
883+
#self.assertFloatsAreIdentical(imagasdouble(FloatSubclass(3.14)), 0.0)
884+
#self.assertFloatsAreIdentical(imagasdouble(Float()), 0.0)
885+
#with self.assertWarns(DeprecationWarning):
886+
# self.assertFloatsAreIdentical(imagasdouble(BadComplex2()), 0.5)
887+
#with self.assertWarns(DeprecationWarning):
888+
# self.assertFloatsAreIdentical(imagasdouble(BadFloat2()), 0.0)
889+
# Function returns 0.0 anyway, see #109598
890+
#self.assertRaises(TypeError, imagasdouble, BadComplex())
891+
#self.assertRaises(TypeError, imagasdouble, BadFloat())
892+
#self.assertRaises(TypeError, imagasdouble, object())
893+
self.assertFloatsAreIdentical(imagasdouble(BadComplex()), 0.0)
894+
self.assertFloatsAreIdentical(imagasdouble(BadFloat()), 0.0)
895+
self.assertFloatsAreIdentical(imagasdouble(object()), 0.0)
896+
897+
# CRASHES imagasdouble(NULL)
898+
899+
def test_asccomplex(self):
900+
# Test PyComplex_AsCComplex()
901+
asccomplex = _testcapi.complex_asccomplex
902+
903+
self.assertComplexesAreIdentical(asccomplex(1+2j), 1.0+2.0j)
904+
self.assertComplexesAreIdentical(asccomplex(1), 1.0+0.0j)
905+
self.assertComplexesAreIdentical(asccomplex(-1), -1.0+0.0j)
906+
self.assertComplexesAreIdentical(asccomplex(Complex()), 4.25+0.5j)
907+
self.assertComplexesAreIdentical(asccomplex(3.14), 3.14+0.0j)
908+
self.assertComplexesAreIdentical(asccomplex(FloatSubclass(3.14)), 3.14+0.0j)
909+
self.assertComplexesAreIdentical(asccomplex(Float()), 4.25+0.0j)
910+
with self.assertWarns(DeprecationWarning):
911+
self.assertComplexesAreIdentical(asccomplex(BadComplex2()), 4.25+0.5j)
912+
with self.assertWarns(DeprecationWarning):
913+
self.assertComplexesAreIdentical(asccomplex(BadFloat2()), 4.25+0.0j)
914+
self.assertRaises(TypeError, asccomplex, BadComplex())
915+
self.assertRaises(TypeError, asccomplex, BadFloat())
916+
self.assertRaises(TypeError, asccomplex, object())
917+
918+
# CRASHES asccomplex(NULL)
919+
920+
795921
if __name__ == "__main__":
796922
unittest.main()

Modules/_testcapi/complex.c

+97
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,104 @@
11
#include "parts.h"
22
#include "util.h"
33

4+
5+
static PyObject *
6+
complex_check(PyObject *Py_UNUSED(module), PyObject *obj)
7+
{
8+
NULLABLE(obj);
9+
return PyLong_FromLong(PyComplex_Check(obj));
10+
}
11+
12+
static PyObject *
13+
complex_checkexact(PyObject *Py_UNUSED(module), PyObject *obj)
14+
{
15+
NULLABLE(obj);
16+
return PyLong_FromLong(PyComplex_CheckExact(obj));
17+
}
18+
19+
static PyObject *
20+
complex_fromccomplex(PyObject *Py_UNUSED(module), PyObject *obj)
21+
{
22+
Py_complex complex = ((PyComplexObject*)obj)->cval;
23+
24+
return PyComplex_FromCComplex(complex);
25+
}
26+
27+
static PyObject *
28+
complex_fromdoubles(PyObject *Py_UNUSED(module), PyObject *const *args,
29+
Py_ssize_t nargs)
30+
{
31+
double real, imag;
32+
33+
assert(nargs == 2);
34+
35+
real = PyFloat_AsDouble(args[0]);
36+
if (real == -1. && PyErr_Occurred()) {
37+
return NULL;
38+
}
39+
40+
imag = PyFloat_AsDouble(args[1]);
41+
if (imag == -1. && PyErr_Occurred()) {
42+
return NULL;
43+
}
44+
45+
return PyComplex_FromDoubles(real, imag);
46+
}
47+
48+
static PyObject *
49+
complex_realasdouble(PyObject *Py_UNUSED(module), PyObject *obj)
50+
{
51+
double real;
52+
53+
NULLABLE(obj);
54+
real = PyComplex_RealAsDouble(obj);
55+
56+
if (real == -1. && PyErr_Occurred()) {
57+
return NULL;
58+
}
59+
60+
return PyFloat_FromDouble(real);
61+
}
62+
63+
static PyObject *
64+
complex_imagasdouble(PyObject *Py_UNUSED(module), PyObject *obj)
65+
{
66+
double imag;
67+
68+
NULLABLE(obj);
69+
imag = PyComplex_ImagAsDouble(obj);
70+
71+
if (imag == -1. && PyErr_Occurred()) {
72+
return NULL;
73+
}
74+
75+
return PyFloat_FromDouble(imag);
76+
}
77+
78+
static PyObject *
79+
complex_asccomplex(PyObject *Py_UNUSED(module), PyObject *obj)
80+
{
81+
Py_complex complex;
82+
83+
NULLABLE(obj);
84+
complex = PyComplex_AsCComplex(obj);
85+
86+
if (complex.real == -1. && PyErr_Occurred()) {
87+
return NULL;
88+
}
89+
90+
return PyComplex_FromCComplex(complex);
91+
}
92+
93+
494
static PyMethodDef test_methods[] = {
95+
{"complex_check", complex_check, METH_O},
96+
{"complex_checkexact", complex_checkexact, METH_O},
97+
{"complex_fromccomplex", complex_fromccomplex, METH_O},
98+
{"complex_fromdoubles", _PyCFunction_CAST(complex_fromdoubles), METH_FASTCALL},
99+
{"complex_realasdouble", complex_realasdouble, METH_O},
100+
{"complex_imagasdouble", complex_imagasdouble, METH_O},
101+
{"complex_asccomplex", complex_asccomplex, METH_O},
5102
{NULL},
6103
};
7104

0 commit comments

Comments
 (0)