diff --git a/Lib/test/test_unittest/test_assertions.py b/Lib/test/test_unittest/test_assertions.py index df95e558949aee..a50f9ed7acafb5 100644 --- a/Lib/test/test_unittest/test_assertions.py +++ b/Lib/test/test_unittest/test_assertions.py @@ -6,6 +6,11 @@ from itertools import product +NAN = float('nan') +INF = float('inf') +NINF = float('-inf') + + class Test_Assertions(unittest.TestCase): def test_AlmostEqual(self): self.assertAlmostEqual(1.00000001, 1.0) @@ -26,11 +31,7 @@ def test_AlmostEqual(self): self.assertRaises(self.failureException, self.assertNotAlmostEqual, 0, .1+.1j, places=0) - self.assertAlmostEqual(float('inf'), float('inf')) - self.assertRaises(self.failureException, self.assertNotAlmostEqual, - float('inf'), float('inf')) - - def test_AmostEqualWithDelta(self): + def test_AlmostEqualWithDelta(self): self.assertAlmostEqual(1.1, 1.0, delta=0.5) self.assertAlmostEqual(1.0, 1.1, delta=0.5) self.assertNotAlmostEqual(1.1, 1.0, delta=0.05) @@ -57,6 +58,55 @@ def test_AmostEqualWithDelta(self): self.assertNotAlmostEqual(first, second, delta=datetime.timedelta(seconds=5)) + def test_AlmostEqualWithRelativeDelta(self): + self.assertAlmostEqual(1.0, 0.6, rel_delta=0.5) + self.assertAlmostEqual(0.6, 1.0, rel_delta=0.5) + with self.assertRaises(self.failureException): + self.assertAlmostEqual(1.0, 0.6, rel_delta=0.05) + with self.assertRaises(self.failureException): + self.assertAlmostEqual(0.6, 1.0, rel_delta=0.05) + + self.assertNotAlmostEqual(1.0, 0.6, rel_delta=0.05) + self.assertNotAlmostEqual(0.6, 1.0, rel_delta=0.05) + with self.assertRaises(self.failureException): + self.assertNotAlmostEqual(1.0, 0.6, rel_delta=0.5) + with self.assertRaises(self.failureException): + self.assertNotAlmostEqual(0.6, 1.0, rel_delta=0.5) + + with self.assertRaises(TypeError): + self.assertAlmostEqual(1.0, 1.1, places=0, rel_delta=0.5) + with self.assertRaises(TypeError): + self.assertAlmostEqual(1.0, 1.1, delta=1, rel_delta=0.5) + with self.assertRaises(TypeError): + self.assertNotAlmostEqual(1.0, 1.1, places=0, rel_delta=0.05) + with self.assertRaises(TypeError): + self.assertNotAlmostEqual(1.0, 1.1, delta=0.05, rel_delta=0.05) + + first_dt = datetime.timedelta(seconds=20) + second_dt = datetime.timedelta(seconds=15) + + self.assertAlmostEqual(first_dt, second_dt, rel_delta=0.5) + self.assertNotAlmostEqual(first_dt, second_dt, rel_delta=0.1) + + def test_AlmostEqual_inf_nan(self): + for criterion in [{"places": 2}, {"delta": 0.5}, {"rel_delta": 0.2}]: + for n1, n2 in [(INF, INF), (NINF, NINF)]: + self.assertAlmostEqual(n1, n2, **criterion) + with self.assertRaises(self.failureException, + msg="%s %s should be equal with %s" % (n1, n2, criterion)): + self.assertNotAlmostEqual(n1, n2, **criterion) + + for n1, n2 in [(INF, NINF), (NINF, INF), + (INF, 1), (1, INF), + (NINF, 1), (1, NINF), + (NAN, NAN), + (NAN, 1), (1, NAN), + (NAN, INF), (INF, NAN), + (NAN, NINF), (NINF, NAN)]: + self.assertNotAlmostEqual(n1, n2, **criterion) + with self.assertRaises(self.failureException): + self.assertAlmostEqual(n1, n2, **criterion) + def test_assertRaises(self): def _raise(e): raise e diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index a392238c85abfa..023e39f3a900c8 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -958,24 +958,33 @@ def assertNotEqual(self, first, second, msg=None): raise self.failureException(msg) def assertAlmostEqual(self, first, second, places=None, msg=None, - delta=None): + delta=None, rel_delta=None): """Fail if the two objects are unequal as determined by their - difference rounded to the given number of decimal places - (default 7) and comparing to zero, or by comparing that the - difference between the two objects is more than the given - delta. + difference by one of the following methods: - Note that decimal places (from zero) are usually not the same - as significant digits (measured from the most significant digit). + 1) Given number of decimal places. This is the default method + (default value 7 digits). Note that decimal places (from + zero) are usually not the same as significant digits + (measured from the most significant digit). + + 2) Comparing that the absolute difference between the two + objects is more than the given delta. + + 3) Comparing that the relative difference between the two + objects is more than the given rel_delta, using the same + algorithm as :meth:`math.isclose`. If the two objects compare equal then they will automatically - compare almost equal. + compare almost equal. Comparison of floating point numbers + (espaicially inf, -inf, and NaN) is applied in the same way as + in :meth:`math.isclose` according to IEEE standard 754. """ if first == second: # shortcut return - if delta is not None and places is not None: - raise TypeError("specify delta or places not both") + if int(delta is not None) + int(places is not None) + \ + int(rel_delta is not None) > 1: + raise TypeError("specify maximally one: delta, places or rel_delta") diff = abs(first - second) if delta is not None: @@ -987,6 +996,15 @@ def assertAlmostEqual(self, first, second, places=None, msg=None, safe_repr(second), safe_repr(delta), safe_repr(diff)) + elif rel_delta is not None: + if (diff < rel_delta*abs(first)) or (diff < rel_delta*abs(second)): + return + + standardMsg = '%s != %s within relatively %s (%s rel. difference)' % ( + safe_repr(first), + safe_repr(second), + safe_repr(rel_delta), + safe_repr(diff/abs(first))) else: if places is None: places = 7 @@ -1003,28 +1021,38 @@ def assertAlmostEqual(self, first, second, places=None, msg=None, raise self.failureException(msg) def assertNotAlmostEqual(self, first, second, places=None, msg=None, - delta=None): + delta=None, rel_delta=None): """Fail if the two objects are equal as determined by their - difference rounded to the given number of decimal places - (default 7) and comparing to zero, or by comparing that the - difference between the two objects is less than the given delta. - - Note that decimal places (from zero) are usually not the same - as significant digits (measured from the most significant digit). + difference by one of the methods analougsly described for + assertAlmostEqual(). Objects that are equal automatically fail. """ - if delta is not None and places is not None: - raise TypeError("specify delta or places not both") + if int(delta is not None) + int(places is not None) + \ + int(rel_delta is not None) > 1: + raise TypeError("specify maximally one: delta, places or rel_delta") + diff = abs(first - second) + if delta is not None: - if not (first == second) and diff > delta: + if not (first == second) and ((diff > delta) or diff != diff): return standardMsg = '%s == %s within %s delta (%s difference)' % ( safe_repr(first), safe_repr(second), safe_repr(delta), safe_repr(diff)) + elif rel_delta is not None: + if not (first == second) and \ + (((diff > rel_delta*abs(first)) and (diff > rel_delta*abs(second))) + or (diff != diff) or (diff/2 == diff)): + return + + standardMsg = '%s == %s within relatively %s (%s rel. difference)' % ( + safe_repr(first), + safe_repr(second), + safe_repr(rel_delta), + safe_repr(diff/abs(first))) else: if places is None: places = 7 diff --git a/Misc/NEWS.d/next/Library/2022-09-16-21-51-41.gh-issue-71385.r5XUzt.rst b/Misc/NEWS.d/next/Library/2022-09-16-21-51-41.gh-issue-71385.r5XUzt.rst new file mode 100644 index 00000000000000..2f9412929ac299 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-09-16-21-51-41.gh-issue-71385.r5XUzt.rst @@ -0,0 +1 @@ +A new parameter ``rel_delta`` was added to the :meth:`unittest.TestCase.assertAlmostEqual` and :meth:`unittest.TestCase.assertNotAlmostEqual`. It allows comparing two objects with a maximum relative difference to each other.