Skip to content

Commit b9df9a4

Browse files
authored
Merge pull request #6058 from AnjoMan/6057-tolerance-on-complex-approx
6057 tolerance on complex approx
2 parents 39066d5 + 3c7fbe2 commit b9df9a4

File tree

3 files changed

+40
-44
lines changed

3 files changed

+40
-44
lines changed

changelog/6057.feature.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add tolerances to complex values when printing ``pytest.approx``.
2+
3+
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.

src/_pytest/python_api.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,26 +223,24 @@ class ApproxScalar(ApproxBase):
223223
def __repr__(self):
224224
"""
225225
Return a string communicating both the expected value and the tolerance
226-
for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode
227-
plus/minus symbol if this is python3 (it's too hard to get right for
228-
python2).
226+
for the comparison being made, e.g. '1.0 ± 1e-6', '(3+4j) ± 5e-6 ∠ ±180°'.
229227
"""
230-
if isinstance(self.expected, complex):
231-
return str(self.expected)
232228

233229
# Infinities aren't compared using tolerances, so don't show a
234-
# tolerance.
235-
if math.isinf(self.expected):
230+
# tolerance. Need to call abs to handle complex numbers, e.g. (inf + 1j)
231+
if math.isinf(abs(self.expected)):
236232
return str(self.expected)
237233

238234
# If a sensible tolerance can't be calculated, self.tolerance will
239235
# raise a ValueError. In this case, display '???'.
240236
try:
241237
vetted_tolerance = "{:.1e}".format(self.tolerance)
238+
if isinstance(self.expected, complex) and not math.isinf(self.tolerance):
239+
vetted_tolerance += " ∠ ±180°"
242240
except ValueError:
243241
vetted_tolerance = "???"
244242

245-
return "{} \u00b1 {}".format(self.expected, vetted_tolerance)
243+
return "{} ± {}".format(self.expected, vetted_tolerance)
246244

247245
def __eq__(self, actual):
248246
"""

testing/python/approx.py

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,55 +24,50 @@ def report_failure(self, out, test, example, got):
2424

2525

2626
class TestApprox:
27-
@pytest.fixture
28-
def plus_minus(self):
29-
return "\u00b1"
30-
31-
def test_repr_string(self, plus_minus):
32-
tol1, tol2, infr = "1.0e-06", "2.0e-06", "inf"
33-
assert repr(approx(1.0)) == "1.0 {pm} {tol1}".format(pm=plus_minus, tol1=tol1)
34-
assert repr(
35-
approx([1.0, 2.0])
36-
) == "approx([1.0 {pm} {tol1}, 2.0 {pm} {tol2}])".format(
37-
pm=plus_minus, tol1=tol1, tol2=tol2
38-
)
39-
assert repr(
40-
approx((1.0, 2.0))
41-
) == "approx((1.0 {pm} {tol1}, 2.0 {pm} {tol2}))".format(
42-
pm=plus_minus, tol1=tol1, tol2=tol2
43-
)
27+
def test_repr_string(self):
28+
assert repr(approx(1.0)) == "1.0 ± 1.0e-06"
29+
assert repr(approx([1.0, 2.0])) == "approx([1.0 ± 1.0e-06, 2.0 ± 2.0e-06])"
30+
assert repr(approx((1.0, 2.0))) == "approx((1.0 ± 1.0e-06, 2.0 ± 2.0e-06))"
4431
assert repr(approx(inf)) == "inf"
45-
assert repr(approx(1.0, rel=nan)) == "1.0 {pm} ???".format(pm=plus_minus)
46-
assert repr(approx(1.0, rel=inf)) == "1.0 {pm} {infr}".format(
47-
pm=plus_minus, infr=infr
48-
)
49-
assert repr(approx(1.0j, rel=inf)) == "1j"
32+
assert repr(approx(1.0, rel=nan)) == "1.0 ± ???"
33+
assert repr(approx(1.0, rel=inf)) == "1.0 ± inf"
5034

5135
# Dictionaries aren't ordered, so we need to check both orders.
5236
assert repr(approx({"a": 1.0, "b": 2.0})) in (
53-
"approx({{'a': 1.0 {pm} {tol1}, 'b': 2.0 {pm} {tol2}}})".format(
54-
pm=plus_minus, tol1=tol1, tol2=tol2
55-
),
56-
"approx({{'b': 2.0 {pm} {tol2}, 'a': 1.0 {pm} {tol1}}})".format(
57-
pm=plus_minus, tol1=tol1, tol2=tol2
58-
),
37+
"approx({'a': 1.0 ± 1.0e-06, 'b': 2.0 ± 2.0e-06})",
38+
"approx({'b': 2.0 ± 2.0e-06, 'a': 1.0 ± 1.0e-06})",
5939
)
6040

41+
def test_repr_complex_numbers(self):
42+
assert repr(approx(inf + 1j)) == "(inf+1j)"
43+
assert repr(approx(1.0j, rel=inf)) == "1j ± inf"
44+
45+
# can't compute a sensible tolerance
46+
assert repr(approx(nan + 1j)) == "(nan+1j) ± ???"
47+
48+
assert repr(approx(1.0j)) == "1j ± 1.0e-06 ∠ ±180°"
49+
50+
# relative tolerance is scaled to |3+4j| = 5
51+
assert repr(approx(3 + 4 * 1j)) == "(3+4j) ± 5.0e-06 ∠ ±180°"
52+
53+
# absolute tolerance is not scaled
54+
assert repr(approx(3.3 + 4.4 * 1j, abs=0.02)) == "(3.3+4.4j) ± 2.0e-02 ∠ ±180°"
55+
6156
@pytest.mark.parametrize(
62-
"value, repr_string",
57+
"value, expected_repr_string",
6358
[
64-
(5.0, "approx(5.0 {pm} 5.0e-06)"),
65-
([5.0], "approx([5.0 {pm} 5.0e-06])"),
66-
([[5.0]], "approx([[5.0 {pm} 5.0e-06]])"),
67-
([[5.0, 6.0]], "approx([[5.0 {pm} 5.0e-06, 6.0 {pm} 6.0e-06]])"),
68-
([[5.0], [6.0]], "approx([[5.0 {pm} 5.0e-06], [6.0 {pm} 6.0e-06]])"),
59+
(5.0, "approx(5.0 ± 5.0e-06)"),
60+
([5.0], "approx([5.0 ± 5.0e-06])"),
61+
([[5.0]], "approx([[5.0 ± 5.0e-06]])"),
62+
([[5.0, 6.0]], "approx([[5.0 ± 5.0e-06, 6.0 ± 6.0e-06]])"),
63+
([[5.0], [6.0]], "approx([[5.0 ± 5.0e-06], [6.0 ± 6.0e-06]])"),
6964
],
7065
)
71-
def test_repr_nd_array(self, plus_minus, value, repr_string):
66+
def test_repr_nd_array(self, value, expected_repr_string):
7267
"""Make sure that arrays of all different dimensions are repr'd correctly."""
7368
np = pytest.importorskip("numpy")
7469
np_array = np.array(value)
75-
assert repr(approx(np_array)) == repr_string.format(pm=plus_minus)
70+
assert repr(approx(np_array)) == expected_repr_string
7671

7772
def test_operator_overloading(self):
7873
assert 1 == approx(1, rel=1e-6, abs=1e-12)

0 commit comments

Comments
 (0)