From ed9fda84d30850f71b7d7d5c831c9ab1aaf3b2c8 Mon Sep 17 00:00:00 2001 From: AnjoMan Date: Thu, 24 Oct 2019 21:07:36 -0400 Subject: [PATCH] Add tolerance to complex numbers --- changelog/6057.feature.rst | 3 +++ src/_pytest/python_api.py | 12 +++++------- testing/python/approx.py | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 changelog/6057.feature.rst diff --git a/changelog/6057.feature.rst b/changelog/6057.feature.rst new file mode 100644 index 000000000..b7334e7fe --- /dev/null +++ b/changelog/6057.feature.rst @@ -0,0 +1,3 @@ +Add tolerances to complex values when printing ``pytest.approx``. + +For example, ``repr(pytest.approx(3+4j))`` returns ``(3+4j) ± 5e-06 ∠ ±180°``. This is polar notation indicating a circle around the expected value, with a radius of 5e-06. For ``approx`` comparisons to return ``True``, the actual value should fall within this circle. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index f03d45ab7..025a46076 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -223,26 +223,24 @@ class ApproxScalar(ApproxBase): def __repr__(self): """ Return a string communicating both the expected value and the tolerance - for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode - plus/minus symbol if this is python3 (it's too hard to get right for - python2). + for the comparison being made, e.g. '1.0 ± 1e-6', '(3+4j) ± 5e-6 ∠ ±180°'. """ - if isinstance(self.expected, complex): - return str(self.expected) # Infinities aren't compared using tolerances, so don't show a # tolerance. - if math.isinf(self.expected): + if math.isinf(abs(self.expected)): return str(self.expected) # If a sensible tolerance can't be calculated, self.tolerance will # raise a ValueError. In this case, display '???'. try: vetted_tolerance = "{:.1e}".format(self.tolerance) + if isinstance(self.expected, complex) and not math.isinf(self.tolerance): + vetted_tolerance += " ∠ ±180°" except ValueError: vetted_tolerance = "???" - return "{} \u00b1 {}".format(self.expected, vetted_tolerance) + return "{} ± {}".format(self.expected, vetted_tolerance) def __eq__(self, actual): """ diff --git a/testing/python/approx.py b/testing/python/approx.py index 0575557ae..5900dee28 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -46,7 +46,6 @@ class TestApprox: assert repr(approx(1.0, rel=inf)) == "1.0 {pm} {infr}".format( pm=plus_minus, infr=infr ) - assert repr(approx(1.0j, rel=inf)) == "1j" # Dictionaries aren't ordered, so we need to check both orders. assert repr(approx({"a": 1.0, "b": 2.0})) in ( @@ -58,6 +57,21 @@ class TestApprox: ), ) + def test_repr_complex_numbers(self): + assert repr(approx(inf + 1j)) == "(inf+1j)" + assert repr(approx(1.0j, rel=inf)) == "1j ± inf" + + # can't compute a sensible tolerance + assert repr(approx(nan + 1j)) == "(nan+1j) ± ???" + + assert repr(approx(1.0j)) == "1j ± 1.0e-06 ∠ ±180°" + + # relative tolerance is scaled to |3+4j| = 5 + assert repr(approx(3 + 4 * 1j)) == "(3+4j) ± 5.0e-06 ∠ ±180°" + + # absolute tolerance is not scaled + assert repr(approx(3.3 + 4.4 * 1j, abs=0.02)) == "(3.3+4.4j) ± 2.0e-02 ∠ ±180°" + @pytest.mark.parametrize( "value, repr_string", [