@@ -392,9 +392,27 @@ def test_find_verrou_prefers_verrou_home_candidate(tmp_path, monkeypatch):
392392 vbin .write_text ("#!/bin/sh\n " )
393393 vbin .chmod (0o755 )
394394 monkeypatch .setenv ("VERROU_HOME" , str (tmp_path ))
395+ # The candidate must also verify as Verrou-enabled; stub that so the test
396+ # exercises precedence, not a real valgrind invocation.
397+ monkeypatch .setattr (runners , "_has_verrou_tool" , lambda _bin , _env = None : True )
395398 assert runners ._find_verrou () == str (vbin )
396399
397400
401+ def test_find_verrou_rejects_broken_verrou_home_tree (tmp_path , monkeypatch ):
402+ from mfc import fp_stability_runners as runners
403+
404+ # A valgrind exists at $VERROU_HOME but does not actually run the verrou tool
405+ # (broken/stale/non-Verrou): it must read as absent, not be returned.
406+ vbin = tmp_path / "bin" / "valgrind"
407+ vbin .parent .mkdir (parents = True )
408+ vbin .write_text ("#!/bin/sh\n " )
409+ vbin .chmod (0o755 )
410+ monkeypatch .setenv ("VERROU_HOME" , str (tmp_path ))
411+ monkeypatch .setattr (runners , "_has_verrou_tool" , lambda _bin , _env = None : False )
412+ monkeypatch .setattr (runners .shutil , "which" , lambda _name : None )
413+ assert runners ._find_verrou () == ""
414+
415+
398416def test_find_verrou_rejects_non_verrou_path_valgrind (tmp_path , monkeypatch ):
399417 from mfc import fp_stability_runners as runners
400418
@@ -425,3 +443,83 @@ def __init__(self, rc):
425443 assert runners ._has_verrou_tool ("/any/valgrind" ) is True
426444 monkeypatch .setattr (runners .subprocess , "run" , lambda * a , ** k : _R (1 ))
427445 assert runners ._has_verrou_tool ("/any/valgrind" ) is False
446+
447+ def _boom (* a , ** k ):
448+ raise OSError ("not executable" )
449+
450+ monkeypatch .setattr (runners .subprocess , "run" , _boom )
451+ assert runners ._has_verrou_tool ("/stale/valgrind" ) is False
452+
453+
454+ # --- env composition for relocated (prebuilt) Verrou trees ---
455+
456+
457+ def test_verrou_env_sets_valgrind_lib_when_libexec_present (tmp_path , monkeypatch ):
458+ from mfc import fp_stability_runners as runners
459+
460+ (tmp_path / "libexec" / "valgrind" ).mkdir (parents = True )
461+ monkeypatch .delenv ("VALGRIND_LIB" , raising = False )
462+ env = runners ._verrou_env (str (tmp_path / "bin" / "valgrind" ))
463+ assert env ["VALGRIND_LIB" ] == str (tmp_path / "libexec" / "valgrind" )
464+
465+
466+ def test_verrou_env_omits_valgrind_lib_when_libexec_absent (tmp_path , monkeypatch ):
467+ from mfc import fp_stability_runners as runners
468+
469+ monkeypatch .delenv ("VALGRIND_LIB" , raising = False )
470+ env = runners ._verrou_env (str (tmp_path / "bin" / "valgrind" ))
471+ assert "VALGRIND_LIB" not in env
472+
473+
474+ def test_verrou_env_preserves_user_valgrind_lib (tmp_path , monkeypatch ):
475+ from mfc import fp_stability_runners as runners
476+
477+ (tmp_path / "libexec" / "valgrind" ).mkdir (parents = True )
478+ monkeypatch .setenv ("VALGRIND_LIB" , "/user/chosen/lib" )
479+ env = runners ._verrou_env (str (tmp_path / "bin" / "valgrind" ))
480+ assert env ["VALGRIND_LIB" ] == "/user/chosen/lib" # not clobbered
481+
482+
483+ def test_dd_env_prepends_pythonpath_and_inherits_valgrind_lib (tmp_path , monkeypatch ):
484+ from mfc import fp_stability_runners as runners
485+
486+ (tmp_path / "libexec" / "valgrind" ).mkdir (parents = True )
487+ monkeypatch .delenv ("VALGRIND_LIB" , raising = False )
488+ monkeypatch .setenv ("PYTHONPATH" , "/pre/existing" )
489+ monkeypatch .setattr (runners , "_verrou_pythonpath" , lambda _b : "/vg/site-packages/valgrind" )
490+ env = runners ._dd_env (str (tmp_path / "bin" / "valgrind" ))
491+ assert env ["PYTHONPATH" ] == "/vg/site-packages/valgrind:/pre/existing"
492+ assert env ["VALGRIND_LIB" ] == str (tmp_path / "libexec" / "valgrind" )
493+
494+
495+ def test_dd_env_no_leading_colon_when_pythonpath_empty (tmp_path , monkeypatch ):
496+ from mfc import fp_stability_runners as runners
497+
498+ monkeypatch .delenv ("PYTHONPATH" , raising = False )
499+ monkeypatch .setattr (runners , "_verrou_pythonpath" , lambda _b : "/vg/valgrind" )
500+ env = runners ._dd_env (str (tmp_path / "bin" / "valgrind" ))
501+ assert env ["PYTHONPATH" ] == "/vg/valgrind" # no stray leading ':'
502+
503+
504+ # --- auto-install hard-fail guards ---
505+
506+
507+ def test_install_verrou_raises_when_bootstrap_fails (monkeypatch ):
508+ import pytest
509+
510+ from mfc import fp_stability as fps
511+
512+ monkeypatch .setattr (fps .subprocess , "run" , lambda * a , ** k : type ("R" , (), {"returncode" : 1 })())
513+ with pytest .raises (fps .MFCException , match = "Verrou install failed" ):
514+ fps ._install_verrou ()
515+
516+
517+ def test_install_verrou_raises_when_no_binary_appears (monkeypatch ):
518+ import pytest
519+
520+ from mfc import fp_stability as fps
521+
522+ monkeypatch .setattr (fps .subprocess , "run" , lambda * a , ** k : type ("R" , (), {"returncode" : 0 })())
523+ monkeypatch .setattr (fps , "_find_verrou" , lambda : "" )
524+ with pytest .raises (fps .MFCException , match = "no valgrind binary" ):
525+ fps ._install_verrou ()
0 commit comments