Skip to content

Commit 8ef8354

Browse files
authored
gh-121039: add Floats/ComplexesAreIdenticalMixin to test.support.testcase (GH-121071)
1 parent beee91c commit 8ef8354

File tree

6 files changed

+65
-120
lines changed

6 files changed

+65
-120
lines changed

Lib/test/support/testcase.py

+40
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from math import copysign, isnan
2+
3+
14
class ExceptionIsLikeMixin:
25
def assertExceptionIsLike(self, exc, template):
36
"""
@@ -23,3 +26,40 @@ def assertExceptionIsLike(self, exc, template):
2326
self.assertEqual(len(exc.exceptions), len(template.exceptions))
2427
for e, t in zip(exc.exceptions, template.exceptions):
2528
self.assertExceptionIsLike(e, t)
29+
30+
31+
class FloatsAreIdenticalMixin:
32+
def assertFloatsAreIdentical(self, x, y):
33+
"""Fail unless floats x and y are identical, in the sense that:
34+
(1) both x and y are nans, or
35+
(2) both x and y are infinities, with the same sign, or
36+
(3) both x and y are zeros, with the same sign, or
37+
(4) x and y are both finite and nonzero, and x == y
38+
39+
"""
40+
msg = 'floats {!r} and {!r} are not identical'
41+
42+
if isnan(x) or isnan(y):
43+
if isnan(x) and isnan(y):
44+
return
45+
elif x == y:
46+
if x != 0.0:
47+
return
48+
# both zero; check that signs match
49+
elif copysign(1.0, x) == copysign(1.0, y):
50+
return
51+
else:
52+
msg += ': zeros have different signs'
53+
self.fail(msg.format(x, y))
54+
55+
56+
class ComplexesAreIdenticalMixin(FloatsAreIdenticalMixin):
57+
def assertComplexesAreIdentical(self, x, y):
58+
"""Fail unless complex numbers x and y have equal values and signs.
59+
60+
In particular, if x and y both have real (or imaginary) part
61+
zero, but the zeros have different signs, this test will fail.
62+
63+
"""
64+
self.assertFloatsAreIdentical(x.real, y.real)
65+
self.assertFloatsAreIdentical(x.imag, y.imag)

Lib/test/test_capi/test_getargs.py

+12-15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from test.support import import_helper
77
from test.support import script_helper
88
from test.support import warnings_helper
9+
from test.support.testcase import FloatsAreIdenticalMixin
910
# Skip this test if the _testcapi module isn't available.
1011
_testcapi = import_helper.import_module('_testcapi')
1112
from _testcapi import getargs_keywords, getargs_keyword_only
@@ -436,11 +437,7 @@ def test_K(self):
436437
self.assertEqual(VERY_LARGE & ULLONG_MAX, getargs_K(VERY_LARGE))
437438

438439

439-
class Float_TestCase(unittest.TestCase):
440-
def assertEqualWithSign(self, actual, expected):
441-
self.assertEqual(actual, expected)
442-
self.assertEqual(math.copysign(1, actual), math.copysign(1, expected))
443-
440+
class Float_TestCase(unittest.TestCase, FloatsAreIdenticalMixin):
444441
def test_f(self):
445442
from _testcapi import getargs_f
446443
self.assertEqual(getargs_f(4.25), 4.25)
@@ -462,10 +459,10 @@ def test_f(self):
462459
self.assertEqual(getargs_f(DBL_MAX), INF)
463460
self.assertEqual(getargs_f(-DBL_MAX), -INF)
464461
if FLT_MIN > DBL_MIN:
465-
self.assertEqualWithSign(getargs_f(DBL_MIN), 0.0)
466-
self.assertEqualWithSign(getargs_f(-DBL_MIN), -0.0)
467-
self.assertEqualWithSign(getargs_f(0.0), 0.0)
468-
self.assertEqualWithSign(getargs_f(-0.0), -0.0)
462+
self.assertFloatsAreIdentical(getargs_f(DBL_MIN), 0.0)
463+
self.assertFloatsAreIdentical(getargs_f(-DBL_MIN), -0.0)
464+
self.assertFloatsAreIdentical(getargs_f(0.0), 0.0)
465+
self.assertFloatsAreIdentical(getargs_f(-0.0), -0.0)
469466
r = getargs_f(NAN)
470467
self.assertNotEqual(r, r)
471468

@@ -494,8 +491,8 @@ def test_d(self):
494491
self.assertEqual(getargs_d(x), x)
495492
self.assertRaises(OverflowError, getargs_d, 1<<DBL_MAX_EXP)
496493
self.assertRaises(OverflowError, getargs_d, -1<<DBL_MAX_EXP)
497-
self.assertEqualWithSign(getargs_d(0.0), 0.0)
498-
self.assertEqualWithSign(getargs_d(-0.0), -0.0)
494+
self.assertFloatsAreIdentical(getargs_d(0.0), 0.0)
495+
self.assertFloatsAreIdentical(getargs_d(-0.0), -0.0)
499496
r = getargs_d(NAN)
500497
self.assertNotEqual(r, r)
501498

@@ -519,10 +516,10 @@ def test_D(self):
519516
self.assertEqual(getargs_D(c), c)
520517
c = complex(1.0, x)
521518
self.assertEqual(getargs_D(c), c)
522-
self.assertEqualWithSign(getargs_D(complex(0.0, 1.0)).real, 0.0)
523-
self.assertEqualWithSign(getargs_D(complex(-0.0, 1.0)).real, -0.0)
524-
self.assertEqualWithSign(getargs_D(complex(1.0, 0.0)).imag, 0.0)
525-
self.assertEqualWithSign(getargs_D(complex(1.0, -0.0)).imag, -0.0)
519+
self.assertFloatsAreIdentical(getargs_D(complex(0.0, 1.0)).real, 0.0)
520+
self.assertFloatsAreIdentical(getargs_D(complex(-0.0, 1.0)).real, -0.0)
521+
self.assertFloatsAreIdentical(getargs_D(complex(1.0, 0.0)).imag, 0.0)
522+
self.assertFloatsAreIdentical(getargs_D(complex(1.0, -0.0)).imag, -0.0)
526523

527524

528525
class Paradox:

Lib/test/test_cmath.py

+5-37
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from test.support import requires_IEEE_754, cpython_only, import_helper
2+
from test.support.testcase import ComplexesAreIdenticalMixin
23
from test.test_math import parse_testfile, test_file
34
import test.test_math as test_math
45
import unittest
@@ -49,7 +50,7 @@
4950
(INF, NAN)
5051
]]
5152

52-
class CMathTests(unittest.TestCase):
53+
class CMathTests(ComplexesAreIdenticalMixin, unittest.TestCase):
5354
# list of all functions in cmath
5455
test_functions = [getattr(cmath, fname) for fname in [
5556
'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh',
@@ -65,39 +66,6 @@ def setUp(self):
6566
def tearDown(self):
6667
self.test_values.close()
6768

68-
def assertFloatIdentical(self, x, y):
69-
"""Fail unless floats x and y are identical, in the sense that:
70-
(1) both x and y are nans, or
71-
(2) both x and y are infinities, with the same sign, or
72-
(3) both x and y are zeros, with the same sign, or
73-
(4) x and y are both finite and nonzero, and x == y
74-
75-
"""
76-
msg = 'floats {!r} and {!r} are not identical'
77-
78-
if math.isnan(x) or math.isnan(y):
79-
if math.isnan(x) and math.isnan(y):
80-
return
81-
elif x == y:
82-
if x != 0.0:
83-
return
84-
# both zero; check that signs match
85-
elif math.copysign(1.0, x) == math.copysign(1.0, y):
86-
return
87-
else:
88-
msg += ': zeros have different signs'
89-
self.fail(msg.format(x, y))
90-
91-
def assertComplexIdentical(self, x, y):
92-
"""Fail unless complex numbers x and y have equal values and signs.
93-
94-
In particular, if x and y both have real (or imaginary) part
95-
zero, but the zeros have different signs, this test will fail.
96-
97-
"""
98-
self.assertFloatIdentical(x.real, y.real)
99-
self.assertFloatIdentical(x.imag, y.imag)
100-
10169
def rAssertAlmostEqual(self, a, b, rel_err = 2e-15, abs_err = 5e-323,
10270
msg=None):
10371
"""Fail if the two floating-point numbers are not almost equal.
@@ -555,7 +523,7 @@ def test_isinf(self):
555523
@requires_IEEE_754
556524
def testTanhSign(self):
557525
for z in complex_zeros:
558-
self.assertComplexIdentical(cmath.tanh(z), z)
526+
self.assertComplexesAreIdentical(cmath.tanh(z), z)
559527

560528
# The algorithm used for atan and atanh makes use of the system
561529
# log1p function; If that system function doesn't respect the sign
@@ -564,12 +532,12 @@ def testTanhSign(self):
564532
@requires_IEEE_754
565533
def testAtanSign(self):
566534
for z in complex_zeros:
567-
self.assertComplexIdentical(cmath.atan(z), z)
535+
self.assertComplexesAreIdentical(cmath.atan(z), z)
568536

569537
@requires_IEEE_754
570538
def testAtanhSign(self):
571539
for z in complex_zeros:
572-
self.assertComplexIdentical(cmath.atanh(z), z)
540+
self.assertComplexesAreIdentical(cmath.atanh(z), z)
573541

574542

575543
class IsCloseTests(test_math.IsCloseTests):

Lib/test/test_complex.py

+3-30
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
import sys
33
from test import support
4+
from test.support.testcase import ComplexesAreIdenticalMixin
45
from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
56
INVALID_UNDERSCORE_LITERALS)
67

@@ -52,7 +53,7 @@ def __init__(self, value):
5253
def __complex__(self):
5354
return self.value
5455

55-
class ComplexTest(unittest.TestCase):
56+
class ComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
5657

5758
def assertAlmostEqual(self, a, b):
5859
if isinstance(a, complex):
@@ -81,33 +82,6 @@ def assertCloseAbs(self, x, y, eps=1e-9):
8182
# check that relative difference < eps
8283
self.assertTrue(abs((x-y)/y) < eps)
8384

84-
def assertFloatsAreIdentical(self, x, y):
85-
"""assert that floats x and y are identical, in the sense that:
86-
(1) both x and y are nans, or
87-
(2) both x and y are infinities, with the same sign, or
88-
(3) both x and y are zeros, with the same sign, or
89-
(4) x and y are both finite and nonzero, and x == y
90-
91-
"""
92-
msg = 'floats {!r} and {!r} are not identical'
93-
94-
if isnan(x) or isnan(y):
95-
if isnan(x) and isnan(y):
96-
return
97-
elif x == y:
98-
if x != 0.0:
99-
return
100-
# both zero; check that signs match
101-
elif copysign(1.0, x) == copysign(1.0, y):
102-
return
103-
else:
104-
msg += ': zeros have different signs'
105-
self.fail(msg.format(x, y))
106-
107-
def assertComplexesAreIdentical(self, x, y):
108-
self.assertFloatsAreIdentical(x.real, y.real)
109-
self.assertFloatsAreIdentical(x.imag, y.imag)
110-
11185
def assertClose(self, x, y, eps=1e-9):
11286
"""Return true iff complexes x and y "are close"."""
11387
self.assertCloseAbs(x.real, y.real, eps)
@@ -829,8 +803,7 @@ def test_repr_roundtrip(self):
829803
for y in vals:
830804
z = complex(x, y)
831805
roundtrip = complex(repr(z))
832-
self.assertFloatsAreIdentical(z.real, roundtrip.real)
833-
self.assertFloatsAreIdentical(z.imag, roundtrip.imag)
806+
self.assertComplexesAreIdentical(z, roundtrip)
834807

835808
# if we predefine some constants, then eval(repr(z)) should
836809
# also work, except that it might change the sign of zeros

Lib/test/test_ctypes/test_numbers.py

+2-29
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
import sys
55
import unittest
66
from itertools import combinations
7-
from math import copysign, isnan
87
from operator import truth
98
from ctypes import (byref, sizeof, alignment,
109
c_char, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
1110
c_long, c_ulong, c_longlong, c_ulonglong,
1211
c_float, c_double, c_longdouble, c_bool)
12+
from test.support.testcase import ComplexesAreIdenticalMixin
1313

1414

1515
def valid_ranges(*types):
@@ -62,34 +62,7 @@ def __complex__(self):
6262
NAN = float("nan")
6363

6464

65-
class NumberTestCase(unittest.TestCase):
66-
# from Lib/test/test_complex.py
67-
def assertFloatsAreIdentical(self, x, y):
68-
"""assert that floats x and y are identical, in the sense that:
69-
(1) both x and y are nans, or
70-
(2) both x and y are infinities, with the same sign, or
71-
(3) both x and y are zeros, with the same sign, or
72-
(4) x and y are both finite and nonzero, and x == y
73-
74-
"""
75-
msg = 'floats {!r} and {!r} are not identical'
76-
77-
if isnan(x) or isnan(y):
78-
if isnan(x) and isnan(y):
79-
return
80-
elif x == y:
81-
if x != 0.0:
82-
return
83-
# both zero; check that signs match
84-
elif copysign(1.0, x) == copysign(1.0, y):
85-
return
86-
else:
87-
msg += ': zeros have different signs'
88-
self.fail(msg.format(x, y))
89-
90-
def assertComplexesAreIdentical(self, x, y):
91-
self.assertFloatsAreIdentical(x.real, y.real)
92-
self.assertFloatsAreIdentical(x.imag, y.imag)
65+
class NumberTestCase(unittest.TestCase, ComplexesAreIdenticalMixin):
9366

9467
def test_default_init(self):
9568
# default values are set to zero

Lib/test/test_float.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import unittest
99

1010
from test import support
11+
from test.support.testcase import FloatsAreIdenticalMixin
1112
from test.test_grammar import (VALID_UNDERSCORE_LITERALS,
1213
INVALID_UNDERSCORE_LITERALS)
1314
from math import isinf, isnan, copysign, ldexp
@@ -1093,21 +1094,14 @@ def test_nan_signs(self):
10931094

10941095
fromHex = float.fromhex
10951096
toHex = float.hex
1096-
class HexFloatTestCase(unittest.TestCase):
1097+
class HexFloatTestCase(FloatsAreIdenticalMixin, unittest.TestCase):
10971098
MAX = fromHex('0x.fffffffffffff8p+1024') # max normal
10981099
MIN = fromHex('0x1p-1022') # min normal
10991100
TINY = fromHex('0x0.0000000000001p-1022') # min subnormal
11001101
EPS = fromHex('0x0.0000000000001p0') # diff between 1.0 and next float up
11011102

11021103
def identical(self, x, y):
1103-
# check that floats x and y are identical, or that both
1104-
# are NaNs
1105-
if isnan(x) or isnan(y):
1106-
if isnan(x) == isnan(y):
1107-
return
1108-
elif x == y and (x != 0.0 or copysign(1.0, x) == copysign(1.0, y)):
1109-
return
1110-
self.fail('%r not identical to %r' % (x, y))
1104+
self.assertFloatsAreIdentical(x, y)
11111105

11121106
def test_ends(self):
11131107
self.identical(self.MIN, ldexp(1.0, -1022))

0 commit comments

Comments
 (0)