Skip to content

Commit d809997

Browse files
committed
fp-stability: prune unit tests to the high-value contracts (33 -> 17)
Kept the behavioral contracts (verrou-absent/broken detection — the case a real bug hit, scale-free pass/fail invariant + zero-scale safety, cancellation severity, output autodetect, blank-result emitter KeyError guard, fypp-ambiguity annotation, VALGRIND_LIB relocation incl. don't-clobber, install no-binary hard-fail) and the subtle edges. Dropped redundant enumerations (5 of 8 macro-context micro-cases, 3 of 5 sig-bits math cases) and trivial-math/empty-input/constant assertions (digits_left, min_sig_bits==24, *_empty, omits-when-absent, the obvious bootstrap-returncode guard). -125 lines; the dropped paths are covered by the kept tests' shared code or the end-to-end CI job.
1 parent 39a1b0f commit d809997

1 file changed

Lines changed: 11 additions & 136 deletions

File tree

toolchain/mfc/test_fp_stability.py

Lines changed: 11 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
1-
"""Unit tests for the pure helpers behind the FP-stability cancellation pass and
2-
its fypp macro-expansion flagging.
1+
"""Unit tests for the pure helpers behind the FP-stability cancellation pass, its
2+
fypp macro-expansion flagging, scale-free pass/fail, and Verrou discovery/install.
33
44
The Verrou subprocess machinery is exercised by the ./mfc.sh fp-stability CI job;
55
here we test only the pure functions that decide what to instrument and how to
6-
label results, so they can run without Verrou or built binaries.
6+
label results, so they can run without Verrou or built binaries. We keep the tests
7+
that pin a real behavioral contract or a subtle edge, not every micro-variation.
78
"""
89

910
from mfc.fp_stability_metrics import (
10-
MIN_SIG_BITS,
1111
_autodetect_compare,
1212
_cancellation_severity,
13-
_digits_left,
1413
_macro_context_in_lines,
1514
_sig_bits,
1615
)
1716

18-
# --- #2: fypp macro-expansion context detection ---
19-
20-
21-
def test_macro_context_none_outside_any_block():
22-
lines = [
23-
"subroutine s_foo()\n",
24-
" a = b - c\n",
25-
"end subroutine\n",
26-
]
27-
assert _macro_context_in_lines(lines, 2) is None
17+
# --- fypp macro-expansion context detection (a #:for/#:def line maps to N instances) ---
2818

2919

3020
def test_macro_context_inside_for_loop_body():
@@ -37,6 +27,7 @@ def test_macro_context_inside_for_loop_body():
3727

3828

3929
def test_macro_context_if_block_is_not_duplicating():
30+
# #:if selects code but does not duplicate it, so it must NOT be flagged.
4031
lines = [
4132
"#:if FOO\n",
4233
" a = b - c\n",
@@ -45,50 +36,12 @@ def test_macro_context_if_block_is_not_duplicating():
4536
assert _macro_context_in_lines(lines, 2) is None
4637

4738

48-
def test_macro_context_reports_innermost_duplicating_block():
49-
lines = [
50-
"#:def MACRO(x)\n",
51-
" #:if cond\n",
52-
" #:for j in range(3)\n",
53-
" y = ${x}$ - z\n",
54-
" #:endfor\n",
55-
" #:endif\n",
56-
"#:enddef\n",
57-
]
58-
assert _macro_context_in_lines(lines, 4) == "#:for"
59-
60-
61-
def test_macro_context_balances_closers():
62-
lines = [
63-
"#:for i in [1, 2]\n",
64-
" a = b - c\n",
65-
"#:endfor\n",
66-
"d = e - f\n",
67-
]
68-
# line 4 is after the loop closed -> not in any duplicating block
69-
assert _macro_context_in_lines(lines, 4) is None
70-
71-
72-
def test_macro_context_def_body_when_no_inner_loop():
73-
lines = [
74-
"#:def GEOM(n)\n",
75-
" r = x - y\n",
76-
"#:enddef\n",
77-
]
78-
assert _macro_context_in_lines(lines, 2) == "#:def"
79-
80-
81-
def test_macro_context_block_and_call_are_duplicating():
82-
assert _macro_context_in_lines(["#:block B\n", " a = b - c\n", "#:endblock\n"], 2) == "#:block"
83-
assert _macro_context_in_lines(["#:call M()\n", " a = b - c\n", "#:endcall\n"], 2) == "#:call"
84-
85-
8639
def test_macro_context_unbalanced_close_is_safe():
8740
# a stray #:endfor with an empty stack must not crash or misreport
8841
assert _macro_context_in_lines(["#:endfor\n", " a = b - c\n"], 2) is None
8942

9043

91-
# --- per-site cancellation severity (bits lost), from a threshold sweep ---
44+
# --- per-site cancellation severity (highest bit-threshold a site survives) ---
9245

9346

9447
def test_cancellation_severity_takes_highest_surviving_threshold():
@@ -101,10 +54,6 @@ def test_cancellation_severity_takes_highest_surviving_threshold():
10154
assert _cancellation_severity(level_sites) == {("a.fpp", 1): 30, ("b.fpp", 2): 10}
10255

10356

104-
def test_cancellation_severity_empty():
105-
assert _cancellation_severity([]) == {}
106-
107-
10857
# --- auto-detect which output files to compare (for a user case) ---
10958

11059

@@ -123,47 +72,20 @@ def test_autodetect_compare_falls_back_to_prim_when_no_cons():
12372
assert _autodetect_compare(fns) == ["prim.1.00.000010.dat", "prim.3.00.000010.dat"]
12473

12574

126-
def test_autodetect_compare_empty_when_no_field_output():
127-
assert _autodetect_compare(["indices.dat", "pre_time_data.dat", "foo.txt"]) == []
128-
129-
13075
# --- scale-free pass/fail: significant bits retained ---
13176

13277

133-
def test_sig_bits_relative_deviation():
134-
# max_dev/ref_scale = 1e-14 -> ~46.5 retained bits
135-
assert 46 < _sig_bits(1e-14, 1.0) < 47
136-
137-
13878
def test_sig_bits_is_scale_free():
13979
# same relative deviation -> same bits regardless of absolute magnitude
14080
assert abs(_sig_bits(1e-9, 1.0) - _sig_bits(1e-4, 1e5)) < 1e-9
14181

14282

143-
def test_sig_bits_zero_deviation_is_full_precision():
144-
assert _sig_bits(0.0, 1.0) == 53.0
145-
146-
14783
def test_sig_bits_zero_scale_is_safe():
84+
# a zero/degenerate field scale must not divide-by-zero; report full precision
14885
assert _sig_bits(1e-12, 0.0) == 53.0
14986

15087

151-
def test_sig_bits_deviation_at_scale_is_unstable():
152-
# deviation as large as the field -> <= 0 retained bits
153-
assert _sig_bits(1.0, 1.0) <= 0.0
154-
155-
156-
def test_min_sig_bits_is_single_precision_floor():
157-
assert MIN_SIG_BITS == 24
158-
159-
160-
def test_digits_left_full_and_clamped():
161-
assert 15.5 < _digits_left(0) < 16.0 # full double ~ 16 sig digits
162-
assert _digits_left(53) == 0.0
163-
assert _digits_left(60) == 0.0 # clamp: never negative
164-
165-
166-
# --- report emitters: must survive blank and populated result dicts (CI-only path) ---
88+
# --- report emitters: must survive the CI-only path without KeyError / regressions ---
16789

16890

16991
def _emit_to_tmp(results, tmp_path, monkeypatch):
@@ -185,26 +107,6 @@ def test_emit_summary_survives_blank_result(tmp_path, monkeypatch):
185107
assert "0 passed, 1 failed" in text
186108

187109

188-
def test_emit_summary_populated_result(tmp_path, monkeypatch):
189-
from mfc.fp_stability import _blank_result
190-
191-
r = _blank_result("demo")
192-
r.update(
193-
passed=False,
194-
max_dev=1e-9,
195-
sig_bits=30.0,
196-
float_proxy=1e-6,
197-
vprec=[(52, 1e-14), (23, float("inf"))], # exercises the "crash" branch
198-
cancellation_locs=[("src/x/m_a.fpp", 5)],
199-
cancellation_bits={("src/x/m_a.fpp", 5): 40},
200-
cancellation_macro={("src/x/m_a.fpp", 5): "#:for"},
201-
float_max_locs=[("m_a.fpp", 9)],
202-
)
203-
text = _emit_to_tmp([r], tmp_path, monkeypatch)
204-
assert "💥 crash" in text and "digits lost" in text
205-
assert "may represent multiple instances" in text # fypp-ambiguous marker
206-
207-
208110
def test_emit_annotations_cancellation_notes_fypp_ambiguity(tmp_path, monkeypatch, capsys):
209111
from mfc import fp_stability_report as report
210112
from mfc.fp_stability import _blank_result
@@ -222,7 +124,7 @@ def test_emit_annotations_cancellation_notes_fypp_ambiguity(tmp_path, monkeypatc
222124
assert "multiple instances" in out # fypp-expanded cancellation site flagged
223125

224126

225-
# --- Verrou discovery: a bare system valgrind must read as "Verrou absent" ---
127+
# --- Verrou discovery: a bare/broken valgrind must read as "Verrou absent" ---
226128

227129

228130
def test_find_verrou_prefers_verrou_home_candidate(tmp_path, monkeypatch):
@@ -264,15 +166,6 @@ def test_find_verrou_rejects_non_verrou_path_valgrind(tmp_path, monkeypatch):
264166
assert runners._find_verrou() == ""
265167

266168

267-
def test_find_verrou_accepts_verrou_enabled_path_valgrind(tmp_path, monkeypatch):
268-
from mfc import fp_stability_runners as runners
269-
270-
monkeypatch.setenv("VERROU_HOME", str(tmp_path))
271-
monkeypatch.setattr(runners.shutil, "which", lambda _name: "/opt/verrou/bin/valgrind")
272-
monkeypatch.setattr(runners, "_has_verrou_tool", lambda _bin, _env=None: True)
273-
assert runners._find_verrou() == "/opt/verrou/bin/valgrind"
274-
275-
276169
def test_has_verrou_tool_reflects_exit_code(monkeypatch):
277170
from mfc import fp_stability_runners as runners
278171

@@ -304,14 +197,6 @@ def test_verrou_env_sets_valgrind_lib_when_libexec_present(tmp_path, monkeypatch
304197
assert env["VALGRIND_LIB"] == str(tmp_path / "libexec" / "valgrind")
305198

306199

307-
def test_verrou_env_omits_valgrind_lib_when_libexec_absent(tmp_path, monkeypatch):
308-
from mfc import fp_stability_runners as runners
309-
310-
monkeypatch.delenv("VALGRIND_LIB", raising=False)
311-
env = runners._verrou_env(str(tmp_path / "bin" / "valgrind"))
312-
assert "VALGRIND_LIB" not in env
313-
314-
315200
def test_verrou_env_preserves_user_valgrind_lib(tmp_path, monkeypatch):
316201
from mfc import fp_stability_runners as runners
317202

@@ -321,17 +206,7 @@ def test_verrou_env_preserves_user_valgrind_lib(tmp_path, monkeypatch):
321206
assert env["VALGRIND_LIB"] == "/user/chosen/lib" # not clobbered
322207

323208

324-
# --- auto-install hard-fail guards ---
325-
326-
327-
def test_install_verrou_raises_when_bootstrap_fails(monkeypatch):
328-
import pytest
329-
330-
from mfc import fp_stability as fps
331-
332-
monkeypatch.setattr(fps.subprocess, "run", lambda *a, **k: type("R", (), {"returncode": 1})())
333-
with pytest.raises(fps.MFCException, match="Verrou install failed"):
334-
fps._install_verrou()
209+
# --- auto-install hard-fail guard (a green bootstrap that produced no binary) ---
335210

336211

337212
def test_install_verrou_raises_when_no_binary_appears(monkeypatch):

0 commit comments

Comments
 (0)