gh-109218: Refactor tests for the complex() constructor (GH-119635)

* Share common classes.
* Use exactly representable floats and exact tests.
* Check the sign of zero components.
* Remove duplicated tests (mostly left after merging int and long).
* Reorder tests in more consistent way.
* Test more error messages.
* Add tests for missed cases.
This commit is contained in:
Serhiy Storchaka 2024-05-30 20:35:59 +03:00 committed by GitHub
parent e875c2d752
commit bf098d4157
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -5,7 +5,7 @@
INVALID_UNDERSCORE_LITERALS)
from random import random
from math import atan2, isnan, copysign
from math import isnan, copysign
import operator
INF = float("inf")
@ -21,6 +21,27 @@
(1, 0+0j),
)
class WithIndex:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
class WithFloat:
def __init__(self, value):
self.value = value
def __float__(self):
return self.value
class ComplexSubclass(complex):
pass
class WithComplex:
def __init__(self, value):
self.value = value
def __complex__(self):
return self.value
class ComplexTest(unittest.TestCase):
def assertAlmostEqual(self, a, b):
@ -340,137 +361,90 @@ def test_conjugate(self):
self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j)
def test_constructor(self):
class NS:
def __init__(self, value): self.value = value
def __complex__(self): return self.value
self.assertEqual(complex(NS(1+10j)), 1+10j)
self.assertRaises(TypeError, complex, NS(None))
self.assertRaises(TypeError, complex, {})
self.assertRaises(TypeError, complex, NS(1.5))
self.assertRaises(TypeError, complex, NS(1))
self.assertRaises(TypeError, complex, object())
self.assertRaises(TypeError, complex, NS(4.25+0.5j), object())
def check(z, x, y):
self.assertIs(type(z), complex)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
self.assertAlmostEqual(complex("1+10j"), 1+10j)
self.assertAlmostEqual(complex(10), 10+0j)
self.assertAlmostEqual(complex(10.0), 10+0j)
self.assertAlmostEqual(complex(10), 10+0j)
self.assertAlmostEqual(complex(10+0j), 10+0j)
self.assertAlmostEqual(complex(1,10), 1+10j)
self.assertAlmostEqual(complex(1,10), 1+10j)
self.assertAlmostEqual(complex(1,10.0), 1+10j)
self.assertAlmostEqual(complex(1,10), 1+10j)
self.assertAlmostEqual(complex(1,10), 1+10j)
self.assertAlmostEqual(complex(1,10.0), 1+10j)
self.assertAlmostEqual(complex(1.0,10), 1+10j)
self.assertAlmostEqual(complex(1.0,10), 1+10j)
self.assertAlmostEqual(complex(1.0,10.0), 1+10j)
self.assertAlmostEqual(complex(3.14+0j), 3.14+0j)
self.assertAlmostEqual(complex(3.14), 3.14+0j)
self.assertAlmostEqual(complex(314), 314.0+0j)
self.assertAlmostEqual(complex(314), 314.0+0j)
self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j)
self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j)
self.assertAlmostEqual(complex(314, 0), 314.0+0j)
self.assertAlmostEqual(complex(314, 0), 314.0+0j)
self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j)
self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j)
self.assertAlmostEqual(complex(0j, 3.14), 3.14j)
self.assertAlmostEqual(complex(0.0, 3.14), 3.14j)
self.assertAlmostEqual(complex("1"), 1+0j)
self.assertAlmostEqual(complex("1j"), 1j)
self.assertAlmostEqual(complex(), 0)
self.assertAlmostEqual(complex("-1"), -1)
self.assertAlmostEqual(complex("+1"), +1)
self.assertAlmostEqual(complex("(1+2j)"), 1+2j)
self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j)
self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j)
self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j)
self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j)
self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j)
self.assertAlmostEqual(complex("J"), 1j)
self.assertAlmostEqual(complex("( j )"), 1j)
self.assertAlmostEqual(complex("+J"), 1j)
self.assertAlmostEqual(complex("( -j)"), -1j)
self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j)
self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j)
self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j)
self.assertEqual(complex('1-1j'), 1.0 - 1j)
self.assertEqual(complex('1J'), 1j)
check(complex(), 0.0, 0.0)
check(complex(10), 10.0, 0.0)
check(complex(4.25), 4.25, 0.0)
check(complex(4.25+0j), 4.25, 0.0)
check(complex(4.25+0.5j), 4.25, 0.5)
check(complex(ComplexSubclass(4.25+0.5j)), 4.25, 0.5)
check(complex(WithComplex(4.25+0.5j)), 4.25, 0.5)
class complex2(complex): pass
self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j)
self.assertAlmostEqual(complex(real=17, imag=23), 17+23j)
self.assertAlmostEqual(complex(real=17+23j), 17+23j)
self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j)
self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j)
check(complex(1, 10), 1.0, 10.0)
check(complex(1, 10.0), 1.0, 10.0)
check(complex(1, 4.25), 1.0, 4.25)
check(complex(1.0, 10), 1.0, 10.0)
check(complex(4.25, 10), 4.25, 10.0)
check(complex(1.0, 10.0), 1.0, 10.0)
check(complex(4.25, 0.5), 4.25, 0.5)
check(complex(4.25+0j, 0), 4.25, 0.0)
check(complex(ComplexSubclass(4.25+0j), 0), 4.25, 0.0)
check(complex(WithComplex(4.25+0j), 0), 4.25, 0.0)
check(complex(4.25j, 0), 0.0, 4.25)
check(complex(0j, 4.25), 0.0, 4.25)
check(complex(0, 4.25+0j), 0.0, 4.25)
check(complex(0, ComplexSubclass(4.25+0j)), 0.0, 4.25)
with self.assertRaisesRegex(TypeError,
"second argument must be a number, not 'WithComplex'"):
complex(0, WithComplex(4.25+0j))
check(complex(0.0, 4.25j), -4.25, 0.0)
check(complex(4.25+0j, 0j), 4.25, 0.0)
check(complex(4.25j, 0j), 0.0, 4.25)
check(complex(0j, 4.25+0j), 0.0, 4.25)
check(complex(0j, 4.25j), -4.25, 0.0)
check(complex(real=4.25), 4.25, 0.0)
check(complex(real=4.25+0j), 4.25, 0.0)
check(complex(real=4.25+1.5j), 4.25, 1.5)
check(complex(imag=1.5), 0.0, 1.5)
check(complex(real=4.25, imag=1.5), 4.25, 1.5)
check(complex(4.25, imag=1.5), 4.25, 1.5)
# check that the sign of a zero in the real or imaginary part
# is preserved when constructing from two floats. (These checks
# are harmless on systems without support for signed zeros.)
def split_zeros(x):
"""Function that produces different results for 0. and -0."""
return atan2(x, -1.)
# is preserved when constructing from two floats.
for x in 1.0, -1.0:
for y in 0.0, -0.0:
check(complex(x, y), x, y)
check(complex(y, x), y, x)
self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.))
self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.))
self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.))
self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.))
c = complex(4.25, 1.5)
self.assertIs(complex(c), c)
c2 = ComplexSubclass(c)
self.assertEqual(c2, c)
self.assertIs(type(c2), ComplexSubclass)
del c, c2
c = 3.14 + 1j
self.assertTrue(complex(c) is c)
del c
self.assertRaises(TypeError, complex, "1", "1")
self.assertRaises(TypeError, complex, 1, "1")
# SF bug 543840: complex(string) accepts strings with \0
# Fixed in 2.3.
self.assertRaises(ValueError, complex, '1+1j\0j')
self.assertRaises(TypeError, int, 5+3j)
self.assertRaises(TypeError, int, 5+3j)
self.assertRaises(TypeError, float, 5+3j)
self.assertRaises(ValueError, complex, "")
self.assertRaises(TypeError, complex, None)
self.assertRaisesRegex(TypeError, "not 'NoneType'", complex, None)
self.assertRaises(ValueError, complex, "\0")
self.assertRaises(ValueError, complex, "3\09")
self.assertRaises(TypeError, complex, "1", "2")
self.assertRaises(TypeError, complex, "1", 42)
self.assertRaises(TypeError, complex, 1, "2")
self.assertRaises(ValueError, complex, "1+")
self.assertRaises(ValueError, complex, "1+1j+1j")
self.assertRaises(ValueError, complex, "--")
self.assertRaises(ValueError, complex, "(1+2j")
self.assertRaises(ValueError, complex, "1+2j)")
self.assertRaises(ValueError, complex, "1+(2j)")
self.assertRaises(ValueError, complex, "(1+2j)123")
self.assertRaises(ValueError, complex, "x")
self.assertRaises(ValueError, complex, "1j+2")
self.assertRaises(ValueError, complex, "1e1ej")
self.assertRaises(ValueError, complex, "1e++1ej")
self.assertRaises(ValueError, complex, ")1+2j(")
self.assertRaisesRegex(
TypeError,
self.assertRaisesRegex(TypeError,
"first argument must be a string or a number, not 'dict'",
complex, {1:2}, 1)
self.assertRaisesRegex(
TypeError,
complex, {})
self.assertRaisesRegex(TypeError,
"first argument must be a string or a number, not 'NoneType'",
complex, None)
self.assertRaisesRegex(TypeError,
"first argument must be a string or a number, not 'dict'",
complex, {1:2}, 0)
self.assertRaisesRegex(TypeError,
"can't take second arg if first is a string",
complex, '1', 0)
self.assertRaisesRegex(TypeError,
"second argument must be a number, not 'dict'",
complex, 1, {1:2})
# the following three are accepted by Python 2.6
self.assertRaises(ValueError, complex, "1..1j")
self.assertRaises(ValueError, complex, "1.11.1j")
self.assertRaises(ValueError, complex, "1e1.1j")
complex, 0, {1:2})
self.assertRaisesRegex(TypeError,
"second arg can't be a string",
complex, 0, '1')
# check that complex accepts long unicode strings
self.assertEqual(type(complex("1"*500)), complex)
# check whitespace processing
self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j)
# Invalid unicode string
# See bpo-34087
self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f')
self.assertRaises(TypeError, complex, WithComplex(1.5))
self.assertRaises(TypeError, complex, WithComplex(1))
self.assertRaises(TypeError, complex, WithComplex(None))
self.assertRaises(TypeError, complex, WithComplex(4.25+0j), object())
self.assertRaises(TypeError, complex, WithComplex(1.5), object())
self.assertRaises(TypeError, complex, WithComplex(1), object())
self.assertRaises(TypeError, complex, WithComplex(None), object())
class EvilExc(Exception):
pass
@ -481,33 +455,33 @@ def __complex__(self):
self.assertRaises(EvilExc, complex, evilcomplex())
class float2:
def __init__(self, value):
self.value = value
def __float__(self):
return self.value
check(complex(WithFloat(4.25)), 4.25, 0.0)
check(complex(WithFloat(4.25), 1.5), 4.25, 1.5)
check(complex(1.5, WithFloat(4.25)), 1.5, 4.25)
self.assertRaises(TypeError, complex, WithFloat(42))
self.assertRaises(TypeError, complex, WithFloat(42), 1.5)
self.assertRaises(TypeError, complex, 1.5, WithFloat(42))
self.assertRaises(TypeError, complex, WithFloat(None))
self.assertRaises(TypeError, complex, WithFloat(None), 1.5)
self.assertRaises(TypeError, complex, 1.5, WithFloat(None))
self.assertAlmostEqual(complex(float2(42.)), 42)
self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j)
self.assertRaises(TypeError, complex, float2(None))
class MyIndex:
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
self.assertAlmostEqual(complex(MyIndex(42)), 42.0+0.0j)
self.assertAlmostEqual(complex(123, MyIndex(42)), 123.0+42.0j)
self.assertRaises(OverflowError, complex, MyIndex(2**2000))
self.assertRaises(OverflowError, complex, 123, MyIndex(2**2000))
check(complex(WithIndex(42)), 42.0, 0.0)
check(complex(WithIndex(42), 1.5), 42.0, 1.5)
check(complex(1.5, WithIndex(42)), 1.5, 42.0)
self.assertRaises(OverflowError, complex, WithIndex(2**2000))
self.assertRaises(OverflowError, complex, WithIndex(2**2000), 1.5)
self.assertRaises(OverflowError, complex, 1.5, WithIndex(2**2000))
self.assertRaises(TypeError, complex, WithIndex(None))
self.assertRaises(TypeError, complex, WithIndex(None), 1.5)
self.assertRaises(TypeError, complex, 1.5, WithIndex(None))
class MyInt:
def __int__(self):
return 42
self.assertRaises(TypeError, complex, MyInt())
self.assertRaises(TypeError, complex, 123, MyInt())
self.assertRaises(TypeError, complex, MyInt(), 1.5)
self.assertRaises(TypeError, complex, 1.5, MyInt())
class complex0(complex):
"""Test usage of __complex__() when inheriting from 'complex'"""
@ -527,9 +501,9 @@ class complex2(complex):
def __complex__(self):
return None
self.assertEqual(complex(complex0(1j)), 42j)
check(complex(complex0(1j)), 0.0, 42.0)
with self.assertWarns(DeprecationWarning):
self.assertEqual(complex(complex1(1j)), 2j)
check(complex(complex1(1j)), 0.0, 2.0)
self.assertRaises(TypeError, complex, complex2(1j))
def test___complex__(self):
@ -537,36 +511,93 @@ def test___complex__(self):
self.assertEqual(z.__complex__(), z)
self.assertEqual(type(z.__complex__()), complex)
class complex_subclass(complex):
pass
z = complex_subclass(3 + 4j)
z = ComplexSubclass(3 + 4j)
self.assertEqual(z.__complex__(), 3 + 4j)
self.assertEqual(type(z.__complex__()), complex)
@support.requires_IEEE_754
def test_constructor_special_numbers(self):
class complex2(complex):
pass
for x in 0.0, -0.0, INF, -INF, NAN:
for y in 0.0, -0.0, INF, -INF, NAN:
with self.subTest(x=x, y=y):
z = complex(x, y)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
z = complex2(x, y)
self.assertIs(type(z), complex2)
z = ComplexSubclass(x, y)
self.assertIs(type(z), ComplexSubclass)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
z = complex(complex2(x, y))
z = complex(ComplexSubclass(x, y))
self.assertIs(type(z), complex)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
z = complex2(complex(x, y))
self.assertIs(type(z), complex2)
z = ComplexSubclass(complex(x, y))
self.assertIs(type(z), ComplexSubclass)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
def test_constructor_from_string(self):
def check(z, x, y):
self.assertIs(type(z), complex)
self.assertFloatsAreIdentical(z.real, x)
self.assertFloatsAreIdentical(z.imag, y)
check(complex("1"), 1.0, 0.0)
check(complex("1j"), 0.0, 1.0)
check(complex("-1"), -1.0, 0.0)
check(complex("+1"), 1.0, 0.0)
check(complex("1+2j"), 1.0, 2.0)
check(complex("(1+2j)"), 1.0, 2.0)
check(complex("(1.5+4.25j)"), 1.5, 4.25)
check(complex("4.25+1J"), 4.25, 1.0)
check(complex(" ( +4.25-6J )"), 4.25, -6.0)
check(complex(" ( +4.25-J )"), 4.25, -1.0)
check(complex(" ( +4.25+j )"), 4.25, 1.0)
check(complex("J"), 0.0, 1.0)
check(complex("( j )"), 0.0, 1.0)
check(complex("+J"), 0.0, 1.0)
check(complex("( -j)"), 0.0, -1.0)
check(complex('1-1j'), 1.0, -1.0)
check(complex('1J'), 0.0, 1.0)
check(complex('1e-500'), 0.0, 0.0)
check(complex('-1e-500j'), 0.0, -0.0)
check(complex('1e-500+1e-500j'), 0.0, 0.0)
check(complex('-1e-500+1e-500j'), -0.0, 0.0)
check(complex('1e-500-1e-500j'), 0.0, -0.0)
check(complex('-1e-500-1e-500j'), -0.0, -0.0)
# SF bug 543840: complex(string) accepts strings with \0
# Fixed in 2.3.
self.assertRaises(ValueError, complex, '1+1j\0j')
self.assertRaises(ValueError, complex, "")
self.assertRaises(ValueError, complex, "\0")
self.assertRaises(ValueError, complex, "3\09")
self.assertRaises(ValueError, complex, "1+")
self.assertRaises(ValueError, complex, "1+1j+1j")
self.assertRaises(ValueError, complex, "--")
self.assertRaises(ValueError, complex, "(1+2j")
self.assertRaises(ValueError, complex, "1+2j)")
self.assertRaises(ValueError, complex, "1+(2j)")
self.assertRaises(ValueError, complex, "(1+2j)123")
self.assertRaises(ValueError, complex, "x")
self.assertRaises(ValueError, complex, "1j+2")
self.assertRaises(ValueError, complex, "1e1ej")
self.assertRaises(ValueError, complex, "1e++1ej")
self.assertRaises(ValueError, complex, ")1+2j(")
# the following three are accepted by Python 2.6
self.assertRaises(ValueError, complex, "1..1j")
self.assertRaises(ValueError, complex, "1.11.1j")
self.assertRaises(ValueError, complex, "1e1.1j")
# check that complex accepts long unicode strings
self.assertIs(type(complex("1"*500)), complex)
# check whitespace processing
self.assertEqual(complex('\N{EM SPACE}(\N{EN SPACE}1+1j ) '), 1+1j)
# Invalid unicode string
# See bpo-34087
self.assertRaises(ValueError, complex, '\u3053\u3093\u306b\u3061\u306f')
def test_constructor_negative_nans_from_string(self):
self.assertEqual(copysign(1., complex("-nan").real), -1.)
self.assertEqual(copysign(1., complex("-nanj").imag), -1.)
@ -645,9 +676,6 @@ def test(v, expected, test_fn=self.assertEqual):
test(complex(-0., -0.), "(-0-0j)")
def test_pos(self):
class ComplexSubclass(complex):
pass
self.assertEqual(+(1+6j), 1+6j)
self.assertEqual(+ComplexSubclass(1, 6), 1+6j)
self.assertIs(type(+ComplexSubclass(1, 6)), complex)
@ -667,8 +695,8 @@ def test_getnewargs(self):
def test_plus_minus_0j(self):
# test that -0j and 0j literals are not identified
z1, z2 = 0j, -0j
self.assertEqual(atan2(z1.imag, -1.), atan2(0., -1.))
self.assertEqual(atan2(z2.imag, -1.), atan2(-0., -1.))
self.assertFloatsAreIdentical(z1.imag, 0.0)
self.assertFloatsAreIdentical(z2.imag, -0.0)
@support.requires_IEEE_754
def test_negated_imaginary_literal(self):