Skip to content

Commit b857b07

Browse files
authored
100% test coverage (#32)
1 parent e5396fd commit b857b07

File tree

6 files changed

+278
-16
lines changed

6 files changed

+278
-16
lines changed

.github/workflows/pytest.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
environment:
3434
- smoke
3535
- mindeps
36+
- no-optional
3637
- py39
3738
- py311
3839
- py314

pixi.lock

Lines changed: 235 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ mindeps = { features = ["mindeps", "smoke", "tests"] }
257257
py39 = { features = ["py39", "optional", "smoke", "tests"] }
258258
py311 = { features = ["py311", "optional", "smoke", "tests"] }
259259
py314 = { features = ["py314", "optional", "smoke", "tests"], solve-group = "py314" }
260+
no-optional = { features = ["py314", "smoke", "tests"], solve-group = "py314" }
260261
nogil = { features = ["nogil", "smoke", "tests"], no-default-feature = true }
261262
upstream = { features = ["upstream", "smoke", "tests"], no-default-feature = true }
262263
smoke = { features = ["smoke"] }

recursive_diff/recursive_diff.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ def is_array_like(dtype: str) -> bool:
3535
return dtype in {"int", "float", "complex", "bool", "str", "list", "tuple"}
3636

3737

38+
def is_basic_noncontainer(x: object) -> bool:
39+
return type(x) in {bool, int, float, type(None), str, bytes}
40+
41+
3842
def recursive_diff(
3943
lhs: Any,
4044
rhs: Any,
@@ -174,10 +178,10 @@ def diff(msg: str, print_path: list[object] = path) -> str:
174178
yield diff(f"LHS {msg_lhs}; RHS {msg_rhs}")
175179
return
176180

177-
# Don't add internalized objects
178-
if not isinstance(lhs, (bool, int, float, type(None), str, bytes)):
181+
# Don't add potentially internalized objects
182+
if not is_basic_noncontainer(lhs):
179183
seen_lhs = [*seen_lhs, id(lhs)]
180-
if not isinstance(rhs, (bool, int, float, type(None), str, bytes)):
184+
if not is_basic_noncontainer(rhs):
181185
seen_rhs = [*seen_rhs, id(rhs)]
182186
# End of recursion detection
183187

@@ -410,10 +414,13 @@ def diff(msg: str, print_path: list[object] = path) -> str:
410414
# e.g. bool or str
411415
diffs = lhs.values != rhs.values
412416

417+
# NumPy <1.26:
413418
# Comparison between two non-scalar, incomparable types
414-
# (like strings and numbers) will return True
419+
# (like strings and numbers) returns True
420+
# FutureWarning: elementwise comparison failed; returning scalar
421+
# instead, but in the future will perform elementwise comparison
415422
if diffs is True:
416-
diffs = np.full(lhs.shape, dtype=bool, fill_value=True)
423+
diffs = np.ones(lhs.shape, dtype=bool)
417424

418425
if diffs.ndim > 1 and lhs.dims[-1] == "__stacked__":
419426
# N>0 original dimensions, some (but not all) of which are in
@@ -549,8 +556,9 @@ def _dtype_str(obj: object) -> str:
549556
"""
550557
try:
551558
dtype = type(obj).__name__
552-
except AttributeError:
553-
# Base types don't have __name__
559+
except AttributeError: # pragma: nocover
560+
# FIXME This used to be triggered in Python 2. Is it still possible to get here?
561+
# Maybe some poorly written C extensions?
554562
dtype = str(type(obj))
555563

556564
if isinstance(obj, np.integer):

recursive_diff/tests/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import importlib
44

55
import numpy as np
6+
import pandas as pd
67
import pytest
78
from packaging.version import Version
89

@@ -39,11 +40,12 @@ def _import_or_skip(modname: str) -> tuple:
3940
has_netcdf = has_h5netcdf or has_netcdf4 or has_scipy
4041
requires_netcdf = pytest.mark.skipif(not has_netcdf, reason="No NetCDF engine found")
4142

42-
NP_GE_126 = Version(np.__version__) >= Version("1.26")
43+
NUMPY_GE_126 = Version(np.__version__) >= Version("1.26")
44+
PANDAS_GE_200 = Version(pd.__version__) >= Version("2.0")
4345

4446

4547
def filter_old_numpy_warnings(testfunc):
46-
if NP_GE_126:
48+
if NUMPY_GE_126:
4749
return testfunc
4850
return pytest.mark.filterwarnings(
4951
"ignore:elementwise comparison failed:DeprecationWarning",

recursive_diff/tests/test_recursive_diff.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,21 @@
55
import pandas as pd
66
import pytest
77
import xarray
8-
from packaging.version import Version
98

109
from recursive_diff import cast, recursive_diff
11-
from recursive_diff.tests import filter_old_numpy_warnings, requires_dask
12-
13-
PANDAS_GE_200 = Version(pd.__version__).release >= (2, 0)
10+
from recursive_diff.tests import PANDAS_GE_200, filter_old_numpy_warnings, requires_dask
1411

1512

1613
class Rectangle:
17-
"""Sample class to test custom comparisons"""
14+
"""Sample class to test custom comparisons."""
1815

1916
def __init__(self, w, h):
2017
self.w = w
2118
self.h = h
2219

2320
def __eq__(self, other):
24-
return self.w == other.w and self.h == other.h
21+
# Never invoked thanks to @cast.register
22+
raise AssertionError("__eq__ should not be called") # pragma: nocover
2523

2624
def __repr__(self):
2725
return f"Rectangle({self.w}, {self.h})"
@@ -37,7 +35,8 @@ def __init__(self, w, h):
3735
self.h = h
3836

3937
def __eq__(self, other):
40-
return self.w == other.w and self.h == other.h
38+
# Never invoked thanks to @cast.register
39+
raise AssertionError("__eq__ should not be called") # pragma: nocover
4140

4241

4342
@cast.register(Rectangle)
@@ -309,6 +308,14 @@ def test_numpy():
309308
"[data][0, 1]: 2 != 4 (abs: 2.0e+00, rel: 1.0e+00)",
310309
)
311310

311+
# list vs. numpy
312+
check(
313+
[[1, 4, 3], [4, 5, 6]],
314+
np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64),
315+
"object type differs: list != ndarray<int64>",
316+
"[data][0, 1]: 4 != 2 (abs: -2.0e+00, rel: -5.0e-01)",
317+
)
318+
312319
# numpy vs. other object
313320
check(
314321
np.array([0, 0], dtype=np.int64),
@@ -487,6 +494,14 @@ def test_pandas_rangeindex():
487494
f"object type differs: RangeIndex != {int_index}",
488495
)
489496

497+
# Regular index vs RangeIndex
498+
check(
499+
pd.Index([0, 1, 2]),
500+
pd.RangeIndex(4),
501+
"3 is in RHS only",
502+
f"object type differs: {int_index} != RangeIndex",
503+
)
504+
490505

491506
def test_pandas_multiindex():
492507
lhs = pd.MultiIndex.from_tuples(

0 commit comments

Comments
 (0)