Skip to content

Commit 6a856f5

Browse files
committed
Closes #5255: alignment tests for arkouda.numpy.err
1 parent 9d5b842 commit 6a856f5

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ testpaths =
5151
tests/numpy/dtypes_test.py
5252
tests/numpy/err_test.py
5353
tests/numpy/manipulation_functions_test.py
54+
tests/numpy/alignment_verification/err_alignment.py
5455
tests/numpy/alignment_verification/operators_alignment.py
5556
tests/numpy/numeric_test.py
5657
tests/numpy/numpy_test.py
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
from __future__ import annotations
2+
3+
import io
4+
import sys
5+
import warnings
6+
7+
import pytest
8+
9+
import arkouda as ak
10+
11+
12+
# --- helpers ---------------------------------------------------------------
13+
14+
ERR_KINDS = ("divide", "over", "under", "invalid")
15+
MODES = ("ignore", "warn", "raise", "call", "print", "log")
16+
17+
18+
def _reset_ak_err_to_defaults() -> None:
19+
# Arkouda scaffold defaults mirror NumPy defaults per module docstring.
20+
ak.seterr(divide="ignore", over="ignore", under="ignore", invalid="ignore")
21+
ak.seterrcall(None)
22+
23+
24+
@pytest.fixture(autouse=True)
25+
def _clean_errstate():
26+
_reset_ak_err_to_defaults()
27+
yield
28+
_reset_ak_err_to_defaults()
29+
30+
31+
# --- tests ----------------------------------------------------------------
32+
33+
34+
@pytest.mark.parametrize("kind", ERR_KINDS)
35+
@pytest.mark.parametrize("mode", MODES)
36+
def test_seterr_roundtrip_and_return_previous(kind: str, mode: str) -> None:
37+
"""Seterr returns the previous dict and updates the requested key."""
38+
before = ak.geterr()
39+
prev = ak.seterr(**{kind: mode})
40+
after = ak.geterr()
41+
42+
assert prev == before
43+
assert after[kind] == mode
44+
# other keys unchanged
45+
for k in ERR_KINDS:
46+
if k != kind:
47+
assert after[k] == before[k]
48+
49+
50+
def test_errstate_restores_state() -> None:
51+
ak.seterr(divide="raise")
52+
assert ak.geterr()["divide"] == "raise"
53+
54+
with ak.errstate(divide="warn"):
55+
assert ak.geterr()["divide"] == "warn"
56+
57+
# restored
58+
assert ak.geterr()["divide"] == "raise"
59+
60+
61+
def test_errstate_nested_restores_correctly() -> None:
62+
ak.seterr(divide="ignore")
63+
with ak.errstate(divide="warn"):
64+
assert ak.geterr()["divide"] == "warn"
65+
with ak.errstate(divide="raise"):
66+
assert ak.geterr()["divide"] == "raise"
67+
assert ak.geterr()["divide"] == "warn"
68+
assert ak.geterr()["divide"] == "ignore"
69+
70+
71+
def test_seterrcall_geterrcall_roundtrip() -> None:
72+
def handler(kind: str, msg: str) -> None:
73+
pass
74+
75+
assert ak.geterrcall() is None
76+
prev = ak.seterrcall(handler)
77+
assert prev is None
78+
assert ak.geterrcall() is handler
79+
80+
prev2 = ak.seterrcall(None)
81+
assert prev2 is handler
82+
assert ak.geterrcall() is None
83+
84+
85+
def test_errstate_temporarily_sets_call_handler() -> None:
86+
calls: list[tuple[str, str]] = []
87+
88+
def handler(kind: str, msg: str) -> None:
89+
calls.append((kind, msg))
90+
91+
ak.seterr(divide="call")
92+
assert ak.geterrcall() is None
93+
94+
with ak.errstate(call=handler):
95+
assert ak.geterrcall() is handler
96+
ak.numpy.err.handle("divide", "divide by zero encountered")
97+
98+
assert calls == [("divide", "divide by zero encountered")]
99+
# restored
100+
assert ak.geterrcall() is None
101+
102+
103+
@pytest.mark.parametrize("kind", ERR_KINDS)
104+
def test_handle_unknown_kind_raises_value_error(kind: str) -> None:
105+
# sanity: known kinds do not raise at validation layer
106+
ak.numpy.err.handle(kind, "msg")
107+
108+
109+
def test_handle_rejects_unknown_kind() -> None:
110+
with pytest.raises(ValueError):
111+
ak.numpy.err.handle("bogus", "nope")
112+
113+
114+
def test_mode_ignore_no_side_effect() -> None:
115+
ak.seterr(divide="ignore")
116+
# Should do nothing; just ensure it doesn't raise or warn.
117+
with warnings.catch_warnings(record=True) as w:
118+
warnings.simplefilter("always")
119+
ak.numpy.err.handle("divide", "divide by zero encountered")
120+
assert len(w) == 0
121+
122+
123+
def test_mode_warn_emits_runtimewarning() -> None:
124+
ak.seterr(divide="warn")
125+
with warnings.catch_warnings(record=True) as w:
126+
warnings.simplefilter("always")
127+
ak.numpy.err.handle("divide", "divide by zero encountered")
128+
assert any(issubclass(x.category, RuntimeWarning) for x in w)
129+
130+
131+
def test_mode_raise_raises_floating_point_error() -> None:
132+
ak.seterr(divide="raise")
133+
with pytest.raises(FloatingPointError):
134+
ak.numpy.err.handle("divide", "divide by zero encountered")
135+
136+
137+
def test_mode_call_invokes_handler() -> None:
138+
seen: list[tuple[str, str]] = []
139+
140+
def handler(kind: str, msg: str) -> None:
141+
seen.append((kind, msg))
142+
143+
ak.seterr(divide="call")
144+
ak.seterrcall(handler)
145+
146+
ak.numpy.err.handle("divide", "divide by zero encountered")
147+
assert seen == [("divide", "divide by zero encountered")]
148+
149+
150+
def test_mode_print_writes_to_stdout() -> None:
151+
ak.seterr(divide="print")
152+
buf = io.StringIO()
153+
old = sys.stdout
154+
try:
155+
sys.stdout = buf
156+
ak.numpy.err.handle("divide", "divide by zero encountered")
157+
finally:
158+
sys.stdout = old
159+
assert "divide: divide by zero encountered" in buf.getvalue()
160+
161+
162+
def test_mode_log_does_not_crash() -> None:
163+
# We don't assert logger output here; just that it routes without error.
164+
ak.seterr(divide="log")
165+
ak.numpy.err.handle("divide", "divide by zero encountered")
166+
167+
168+
@pytest.mark.parametrize("kind", ("over", "under", "invalid"))
169+
def test_seterr_warns_for_unimplemented_kinds(kind: str) -> None:
170+
"""
171+
err.py warns that over/under/invalid are not implemented yet.
172+
We assert a warning is emitted when changing those from current value.
173+
"""
174+
with warnings.catch_warnings(record=True) as w:
175+
warnings.simplefilter("always")
176+
ak.seterr(**{kind: "warn"})
177+
assert any("not implemented yet" in str(x.message) for x in w)

0 commit comments

Comments
 (0)