1from math import copysign, isnan 2 3 4class ExceptionIsLikeMixin: 5 def assertExceptionIsLike(self, exc, template): 6 """ 7 Passes when the provided `exc` matches the structure of `template`. 8 Individual exceptions don't have to be the same objects or even pass 9 an equality test: they only need to be the same type and contain equal 10 `exc_obj.args`. 11 """ 12 if exc is None and template is None: 13 return 14 15 if template is None: 16 self.fail(f"unexpected exception: {exc}") 17 18 if exc is None: 19 self.fail(f"expected an exception like {template!r}, got None") 20 21 if not isinstance(exc, ExceptionGroup): 22 self.assertEqual(exc.__class__, template.__class__) 23 self.assertEqual(exc.args[0], template.args[0]) 24 else: 25 self.assertEqual(exc.message, template.message) 26 self.assertEqual(len(exc.exceptions), len(template.exceptions)) 27 for e, t in zip(exc.exceptions, template.exceptions): 28 self.assertExceptionIsLike(e, t) 29 30 31class 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 56class 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) 66