Skip to content

Commit 0613913

Browse files
committed
fp-stability: auto-install Verrou on first use (download prebuilt), hard-fail if it can't
Running ./mfc.sh fp-stability with no Verrou present now installs it via the bootstrap (downloads the pinned prebuilt from verrou-dist; source build as fallback) and proceeds, instead of SKIP+exit-0; a failed install is now a hard error. _find_verrou no longer accepts a bare system valgrind on PATH (it has no 'verrou' tool and would only fail at run time) — that case reads as 'Verrou absent' so it gets installed. CI drops the separate Install/Verify Verrou steps; the run does it. Tests added for the discovery logic.
1 parent a4cfa79 commit 0613913

5 files changed

Lines changed: 87 additions & 18 deletions

File tree

.github/workflows/fp-stability.yml

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,9 @@ jobs:
7878
build-essential automake python3 python3-numpy libc6-dbg \
7979
cmake gfortran zstd
8080
81-
- name: Install Verrou (prebuilt artifact, or source build as fallback)
82-
if: steps.cache-verrou.outputs.cache-hit != 'true'
83-
run: bash toolchain/bootstrap/verrou.sh
84-
85-
- name: Verify Verrou
86-
# Source env.sh first: a prebuilt (relocated) tree needs VALGRIND_LIB; a
87-
# source build works either way. (fp-stability sets this itself at runtime.)
88-
run: |
89-
[ -f ~/.local/verrou/env.sh ] && . ~/.local/verrou/env.sh
90-
~/.local/verrou/bin/valgrind --tool=verrou --version
81+
# Verrou is installed by `fp-stability` itself on first use (downloads the
82+
# prebuilt artifact; aborts if that fails). The cache above restores it across
83+
# runs so the download only happens on a cache miss.
9184

9285
- name: Build MFC (debug, serial)
9386
# FFLAGS=-fno-inline prevents gfortran from inlining small functions into

toolchain/mfc/cli/commands.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -908,9 +908,11 @@
908908
"(~30x slower, and run many times), so it must be a small, short proxy — large "
909909
"grids or long runs are rejected with guidance; serial .dat I/O is forced. "
910910
"Example: ./mfc.sh fp-stability my_case.py\n\n"
911-
"Requires a Verrou-enabled Valgrind at $VERROU_HOME/bin/valgrind "
912-
"(defaults to $HOME/.local/verrou). The simulation and pre_process "
913-
"binaries must be serial (no-MPI, no-GPU) debug builds.\n\n"
911+
"Uses a Verrou-enabled Valgrind at $VERROU_HOME/bin/valgrind (defaults to "
912+
"$HOME/.local/verrou); if absent it is installed automatically (a pinned, "
913+
"hash-verified prebuilt is downloaded, with a source build as fallback) — "
914+
"aborts if that install fails. The simulation and pre_process binaries must "
915+
"be serial (no-MPI, no-GPU) debug builds.\n\n"
914916
"Analysis passes (skip with --no-* flags):\n"
915917
" float proxy One run with --rounding-mode=float (single-precision sensitivity)\n"
916918
" vprec sweep Runs at mantissa bits [52, 23, 16, 10] (precision floor curve)\n"

toolchain/mfc/fp_stability.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import math
7474
import os
7575
import shutil
76+
import subprocess
7677
import sys
7778
import tempfile
7879
import time
@@ -658,13 +659,26 @@ def _load_user_case(input_path: str) -> dict:
658659
}
659660

660661

662+
def _install_verrou() -> str:
663+
"""Verrou is absent: install it via the bootstrap (downloads a pinned, hash-verified
664+
prebuilt; source build as fallback) and return the valgrind path. Aborts on failure —
665+
fp-stability cannot run without Verrou, so this is a hard error, not a skip."""
666+
script = os.path.join(MFC_ROOT_DIR, "toolchain", "bootstrap", "verrou.sh")
667+
cons.print("[bold]Verrou not found — installing it (downloads a prebuilt artifact, ~seconds; source build as fallback)...[/bold]")
668+
if subprocess.run(["bash", script], check=False).returncode != 0:
669+
raise MFCException("Verrou install failed (see output above). Fix the issue and re-run, install manually with `bash toolchain/bootstrap/verrou.sh`, or pass --verrou-binary PATH.")
670+
verrou_bin = _find_verrou()
671+
if not verrou_bin or not os.path.isfile(verrou_bin):
672+
raise MFCException("Verrou install reported success but no valgrind binary was found under $VERROU_HOME.")
673+
return verrou_bin
674+
675+
661676
def fp_stability():
662677
verrou_bin = ARG("verrou_binary") or _find_verrou()
663678
if not verrou_bin or not os.path.isfile(verrou_bin):
664-
cons.print("[bold yellow]SKIP[/bold yellow]: Verrou not found (it is a compiled Valgrind tool, not a pip package).")
665-
cons.print(" Install it (Linux; ~20 min source build) with: [bold]bash toolchain/bootstrap/verrou.sh[/bold]")
666-
cons.print(" Or point at an existing build with --verrou-binary PATH or $VERROU_HOME.")
667-
sys.exit(0)
679+
if ARG("verrou_binary"):
680+
raise MFCException(f"--verrou-binary {ARG('verrou_binary')!r} not found or not executable.")
681+
verrou_bin = _install_verrou()
668682

669683
sim_bin = ARG("sim_binary") or _find_binary("simulation")
670684
if not sim_bin or not os.path.isfile(sim_bin):

toolchain/mfc/fp_stability_runners.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,26 @@
3232
from .printer import cons
3333

3434

35+
def _has_verrou_tool(valgrind_bin: str) -> bool:
36+
"""True if this valgrind actually provides the 'verrou' tool. A plain system
37+
valgrind does not — accepting one would only fail later at run time."""
38+
try:
39+
return subprocess.run([valgrind_bin, "--tool=verrou", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False).returncode == 0
40+
except OSError:
41+
return False
42+
43+
3544
def _find_verrou() -> str:
3645
verrou_home = os.environ.get("VERROU_HOME", os.path.join(os.path.expanduser("~"), ".local", "verrou"))
3746
candidate = os.path.join(verrou_home, "bin", "valgrind")
3847
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
3948
return candidate
40-
return shutil.which("valgrind") or ""
49+
# Fall back to a valgrind on PATH only if it is Verrou-enabled; a bare system
50+
# valgrind must read as "Verrou absent" so it gets installed, not misused.
51+
path_vg = shutil.which("valgrind")
52+
if path_vg and _has_verrou_tool(path_vg):
53+
return path_vg
54+
return ""
4155

4256

4357
def _find_binary(name: str) -> str:

toolchain/mfc/test_fp_stability.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,49 @@ def test_emit_annotations_downgrade_unconfirmed(tmp_path, monkeypatch, capsys):
379379
report._emit_github_annotations([r])
380380
out = capsys.readouterr().out
381381
assert "::notice" in out and "::warning" not in out # unconfirmed -> notice, not warning
382+
383+
384+
# --- Verrou discovery: a bare system valgrind must read as "Verrou absent" ---
385+
386+
387+
def test_find_verrou_prefers_verrou_home_candidate(tmp_path, monkeypatch):
388+
from mfc import fp_stability_runners as runners
389+
390+
vbin = tmp_path / "bin" / "valgrind"
391+
vbin.parent.mkdir(parents=True)
392+
vbin.write_text("#!/bin/sh\n")
393+
vbin.chmod(0o755)
394+
monkeypatch.setenv("VERROU_HOME", str(tmp_path))
395+
assert runners._find_verrou() == str(vbin)
396+
397+
398+
def test_find_verrou_rejects_non_verrou_path_valgrind(tmp_path, monkeypatch):
399+
from mfc import fp_stability_runners as runners
400+
401+
# VERROU_HOME has no valgrind; a plain valgrind is on PATH but lacks the tool.
402+
monkeypatch.setenv("VERROU_HOME", str(tmp_path))
403+
monkeypatch.setattr(runners.shutil, "which", lambda _name: "/usr/bin/valgrind")
404+
monkeypatch.setattr(runners, "_has_verrou_tool", lambda _bin: False)
405+
assert runners._find_verrou() == ""
406+
407+
408+
def test_find_verrou_accepts_verrou_enabled_path_valgrind(tmp_path, monkeypatch):
409+
from mfc import fp_stability_runners as runners
410+
411+
monkeypatch.setenv("VERROU_HOME", str(tmp_path))
412+
monkeypatch.setattr(runners.shutil, "which", lambda _name: "/opt/verrou/bin/valgrind")
413+
monkeypatch.setattr(runners, "_has_verrou_tool", lambda _bin: True)
414+
assert runners._find_verrou() == "/opt/verrou/bin/valgrind"
415+
416+
417+
def test_has_verrou_tool_reflects_exit_code(monkeypatch):
418+
from mfc import fp_stability_runners as runners
419+
420+
class _R:
421+
def __init__(self, rc):
422+
self.returncode = rc
423+
424+
monkeypatch.setattr(runners.subprocess, "run", lambda *a, **k: _R(0))
425+
assert runners._has_verrou_tool("/any/valgrind") is True
426+
monkeypatch.setattr(runners.subprocess, "run", lambda *a, **k: _R(1))
427+
assert runners._has_verrou_tool("/any/valgrind") is False

0 commit comments

Comments
 (0)