Skip to content

Commit 2c20396

Browse files
authored
Merge pull request #200 from wavebyrd/fix-float-precision
Use approximate float comparison for JSON test records
2 parents 4cca791 + c2e5f4f commit 2c20396

File tree

1 file changed

+53
-8
lines changed

1 file changed

+53
-8
lines changed

tests/conftest.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ruff: noqa
22

33
import json
4+
import math
45
import os
56
import pathlib
67
from typing import Any
@@ -34,6 +35,40 @@
3435

3536

3637
class Record:
38+
@staticmethod
39+
def _values_approx_equal(a: Any, b: Any, rel_tol: float = 1e-9) -> bool:
40+
"""Recursively compare two JSON-parsed values using approximate
41+
comparison for floats. Everything else is compared by equality."""
42+
if isinstance(a, float) and isinstance(b, float):
43+
return math.isclose(a, b, rel_tol=rel_tol)
44+
if isinstance(a, list) and isinstance(b, list):
45+
if len(a) != len(b):
46+
return False
47+
return all(
48+
Record._values_approx_equal(x, y, rel_tol=rel_tol)
49+
for x, y in zip(a, b)
50+
)
51+
if isinstance(a, dict) and isinstance(b, dict):
52+
if a.keys() != b.keys():
53+
return False
54+
return all(
55+
Record._values_approx_equal(a[k], b[k], rel_tol=rel_tol)
56+
for k in a
57+
)
58+
return a == b
59+
60+
@staticmethod
61+
def _json_strings_approx_equal(
62+
recorded: str, captured: str, rel_tol: float = 1e-9
63+
) -> bool:
64+
"""Parse two JSON strings and compare with float tolerance."""
65+
try:
66+
return Record._values_approx_equal(
67+
json.loads(recorded), json.loads(captured), rel_tol=rel_tol
68+
)
69+
except (json.JSONDecodeError, ValueError):
70+
return recorded == captured
71+
3772
@staticmethod
3873
def extract_string(data: Any, **kwargs) -> str:
3974
if isinstance(data, tuple(EXTENSIONS_MATCHING["txt"])):
@@ -74,15 +109,25 @@ def strip(self) -> bool:
74109
@property
75110
def record_changed(self) -> bool:
76111
if self.__recorded is None:
77-
changed = True
78-
elif self.__strip and self.__recorded.strip() != self.__captured.strip():
79-
changed = True
80-
elif not self.__strip and self.__recorded != self.__captured:
81-
changed = True
82-
else:
83-
changed = False
112+
return True
113+
114+
recorded = self.__recorded
115+
captured = self.__captured
116+
117+
if self.__strip:
118+
recorded = recorded.strip()
119+
captured = captured.strip()
120+
121+
if recorded == captured:
122+
return False
123+
124+
# For JSON records, fall back to approximate float comparison so that
125+
# differences in float string representation (e.g. 0.011710000000000002
126+
# vs 0.01171) don't cause spurious failures across Python versions.
127+
if self.__record_path.endswith(".json"):
128+
return not self._json_strings_approx_equal(recorded, captured)
84129

85-
return changed
130+
return True
86131

87132
@property
88133
def record_exists(self) -> bool:

0 commit comments

Comments
 (0)