Skip to content

Commit 24b5cbd

Browse files
authored
gh-111495: Add tests for PyComplex C API (GH-111591)
1 parent b452202 commit 24b5cbd

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed

Lib/test/test_capi/test_complex.py

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import unittest
2+
import warnings
3+
4+
from test.test_capi.test_getargs import (BadComplex, BadComplex2, Complex,
5+
FloatSubclass, Float, BadFloat,
6+
BadFloat2, ComplexSubclass)
7+
from test.support import import_helper
8+
9+
10+
_testcapi = import_helper.import_module('_testcapi')
11+
12+
NULL = None
13+
14+
class BadComplex3:
15+
def __complex__(self):
16+
raise RuntimeError
17+
18+
19+
class CAPIComplexTest(unittest.TestCase):
20+
def test_check(self):
21+
# Test PyComplex_Check()
22+
check = _testcapi.complex_check
23+
24+
self.assertTrue(check(1+2j))
25+
self.assertTrue(check(ComplexSubclass(1+2j)))
26+
self.assertFalse(check(Complex()))
27+
self.assertFalse(check(3))
28+
self.assertFalse(check(3.0))
29+
self.assertFalse(check(object()))
30+
31+
# CRASHES check(NULL)
32+
33+
def test_checkexact(self):
34+
# PyComplex_CheckExact()
35+
checkexact = _testcapi.complex_checkexact
36+
37+
self.assertTrue(checkexact(1+2j))
38+
self.assertFalse(checkexact(ComplexSubclass(1+2j)))
39+
self.assertFalse(checkexact(Complex()))
40+
self.assertFalse(checkexact(3))
41+
self.assertFalse(checkexact(3.0))
42+
self.assertFalse(checkexact(object()))
43+
44+
# CRASHES checkexact(NULL)
45+
46+
def test_fromccomplex(self):
47+
# Test PyComplex_FromCComplex()
48+
fromccomplex = _testcapi.complex_fromccomplex
49+
50+
self.assertEqual(fromccomplex(1+2j), 1.0+2.0j)
51+
52+
def test_fromdoubles(self):
53+
# Test PyComplex_FromDoubles()
54+
fromdoubles = _testcapi.complex_fromdoubles
55+
56+
self.assertEqual(fromdoubles(1.0, 2.0), 1.0+2.0j)
57+
58+
def test_realasdouble(self):
59+
# Test PyComplex_RealAsDouble()
60+
realasdouble = _testcapi.complex_realasdouble
61+
62+
self.assertEqual(realasdouble(1+2j), 1.0)
63+
self.assertEqual(realasdouble(-1+0j), -1.0)
64+
self.assertEqual(realasdouble(4.25), 4.25)
65+
self.assertEqual(realasdouble(-1.0), -1.0)
66+
self.assertEqual(realasdouble(42), 42.)
67+
self.assertEqual(realasdouble(-1), -1.0)
68+
69+
# Test subclasses of complex/float
70+
self.assertEqual(realasdouble(ComplexSubclass(1+2j)), 1.0)
71+
self.assertEqual(realasdouble(FloatSubclass(4.25)), 4.25)
72+
73+
# Test types with __complex__ dunder method
74+
# Function doesn't support classes with __complex__ dunder, see #109598
75+
self.assertRaises(TypeError, realasdouble, Complex())
76+
77+
# Test types with __float__ dunder method
78+
self.assertEqual(realasdouble(Float()), 4.25)
79+
self.assertRaises(TypeError, realasdouble, BadFloat())
80+
with self.assertWarns(DeprecationWarning):
81+
self.assertEqual(realasdouble(BadFloat2()), 4.25)
82+
83+
self.assertRaises(TypeError, realasdouble, object())
84+
85+
# CRASHES realasdouble(NULL)
86+
87+
def test_imagasdouble(self):
88+
# Test PyComplex_ImagAsDouble()
89+
imagasdouble = _testcapi.complex_imagasdouble
90+
91+
self.assertEqual(imagasdouble(1+2j), 2.0)
92+
self.assertEqual(imagasdouble(1-1j), -1.0)
93+
self.assertEqual(imagasdouble(4.25), 0.0)
94+
self.assertEqual(imagasdouble(42), 0.0)
95+
96+
# Test subclasses of complex/float
97+
self.assertEqual(imagasdouble(ComplexSubclass(1+2j)), 2.0)
98+
self.assertEqual(imagasdouble(FloatSubclass(4.25)), 0.0)
99+
100+
# Test types with __complex__ dunder method
101+
# Function doesn't support classes with __complex__ dunder, see #109598
102+
self.assertEqual(imagasdouble(Complex()), 0.0)
103+
104+
# Function returns 0.0 anyway, see #109598
105+
self.assertEqual(imagasdouble(object()), 0.0)
106+
107+
# CRASHES imagasdouble(NULL)
108+
109+
def test_asccomplex(self):
110+
# Test PyComplex_AsCComplex()
111+
asccomplex = _testcapi.complex_asccomplex
112+
113+
self.assertEqual(asccomplex(1+2j), 1.0+2.0j)
114+
self.assertEqual(asccomplex(-1+2j), -1.0+2.0j)
115+
self.assertEqual(asccomplex(4.25), 4.25+0.0j)
116+
self.assertEqual(asccomplex(-1.0), -1.0+0.0j)
117+
self.assertEqual(asccomplex(42), 42+0j)
118+
self.assertEqual(asccomplex(-1), -1.0+0.0j)
119+
120+
# Test subclasses of complex/float
121+
self.assertEqual(asccomplex(ComplexSubclass(1+2j)), 1.0+2.0j)
122+
self.assertEqual(asccomplex(FloatSubclass(4.25)), 4.25+0.0j)
123+
124+
# Test types with __complex__ dunder method
125+
self.assertEqual(asccomplex(Complex()), 4.25+0.5j)
126+
self.assertRaises(TypeError, asccomplex, BadComplex())
127+
with self.assertWarns(DeprecationWarning):
128+
self.assertEqual(asccomplex(BadComplex2()), 4.25+0.5j)
129+
with warnings.catch_warnings():
130+
warnings.simplefilter("error", DeprecationWarning)
131+
self.assertRaises(DeprecationWarning, asccomplex, BadComplex2())
132+
self.assertRaises(RuntimeError, asccomplex, BadComplex3())
133+
134+
# Test types with __float__ dunder method
135+
self.assertEqual(asccomplex(Float()), 4.25+0.0j)
136+
self.assertRaises(TypeError, asccomplex, BadFloat())
137+
with self.assertWarns(DeprecationWarning):
138+
self.assertEqual(asccomplex(BadFloat2()), 4.25+0.0j)
139+
140+
self.assertRaises(TypeError, asccomplex, object())
141+
142+
# CRASHES asccomplex(NULL)
143+
144+
145+
if __name__ == "__main__":
146+
unittest.main()

Modules/_testcapi/complex.c

+92
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,99 @@
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;
23+
24+
if (!PyArg_Parse(obj, "D", &complex)) {
25+
return NULL;
26+
}
27+
28+
return PyComplex_FromCComplex(complex);
29+
}
30+
31+
static PyObject *
32+
complex_fromdoubles(PyObject *Py_UNUSED(module), PyObject *args)
33+
{
34+
double real, imag;
35+
36+
if (!PyArg_ParseTuple(args, "dd", &real, &imag)) {
37+
return NULL;
38+
}
39+
40+
return PyComplex_FromDoubles(real, imag);
41+
}
42+
43+
static PyObject *
44+
complex_realasdouble(PyObject *Py_UNUSED(module), PyObject *obj)
45+
{
46+
double real;
47+
48+
NULLABLE(obj);
49+
real = PyComplex_RealAsDouble(obj);
50+
51+
if (real == -1. && PyErr_Occurred()) {
52+
return NULL;
53+
}
54+
55+
return PyFloat_FromDouble(real);
56+
}
57+
58+
static PyObject *
59+
complex_imagasdouble(PyObject *Py_UNUSED(module), PyObject *obj)
60+
{
61+
double imag;
62+
63+
NULLABLE(obj);
64+
imag = PyComplex_ImagAsDouble(obj);
65+
66+
if (imag == -1. && PyErr_Occurred()) {
67+
return NULL;
68+
}
69+
70+
return PyFloat_FromDouble(imag);
71+
}
72+
73+
static PyObject *
74+
complex_asccomplex(PyObject *Py_UNUSED(module), PyObject *obj)
75+
{
76+
Py_complex complex;
77+
78+
NULLABLE(obj);
79+
complex = PyComplex_AsCComplex(obj);
80+
81+
if (complex.real == -1. && PyErr_Occurred()) {
82+
return NULL;
83+
}
84+
85+
return PyComplex_FromCComplex(complex);
86+
}
87+
88+
489
static PyMethodDef test_methods[] = {
90+
{"complex_check", complex_check, METH_O},
91+
{"complex_checkexact", complex_checkexact, METH_O},
92+
{"complex_fromccomplex", complex_fromccomplex, METH_O},
93+
{"complex_fromdoubles", complex_fromdoubles, METH_VARARGS},
94+
{"complex_realasdouble", complex_realasdouble, METH_O},
95+
{"complex_imagasdouble", complex_imagasdouble, METH_O},
96+
{"complex_asccomplex", complex_asccomplex, METH_O},
597
{NULL},
698
};
799

0 commit comments

Comments
 (0)