diff --git a/tools/check_wave_corpus.py b/tools/check_wave_corpus.py index 40bec91d..8428fd30 100755 --- a/tools/check_wave_corpus.py +++ b/tools/check_wave_corpus.py @@ -67,27 +67,48 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: return parser.parse_args(argv) +def _resolve_compiler_path(candidate: Path | str) -> Path | None: + raw = Path(candidate) + if not str(raw).strip(): + return None + if raw.is_file(): + return raw + if not raw.is_absolute(): + rel = ROOT / raw + if rel.is_file(): + return rel + which = shutil.which(str(candidate)) + if which: + return Path(which) + return None + + def resolve_wavec(explicit: Path | None) -> Path: - candidates = [] if explicit is not None: - candidates.append(explicit) - if os.environ.get("WAVEC"): - candidates.append(Path(os.environ["WAVEC"])) - candidates.extend( - [ - ROOT / "target" / "release" / "wavec.exe", - ROOT / "target" / "release" / "wavec", - ROOT / "target" / "debug" / "wavec.exe", - ROOT / "target" / "debug" / "wavec", - ROOT / "target" / "x86_64-pc-windows-gnu" / "release" / "wavec.exe", - ROOT / "target" / "x86_64-pc-windows-gnu" / "debug" / "wavec.exe", - ] - ) + resolved = _resolve_compiler_path(explicit) + if resolved is not None: + return resolved + raise FileNotFoundError(f"wavec executable not found at {explicit}") + + env_wavec = os.environ.get("WAVEC") + if env_wavec and env_wavec.strip(): + resolved = _resolve_compiler_path(env_wavec) + if resolved is not None: + return resolved + raise FileNotFoundError(f"wavec executable specified by WAVEC not found: {env_wavec}") + + candidates = [ + ROOT / "target" / "release" / "wavec.exe", + ROOT / "target" / "release" / "wavec", + ROOT / "target" / "debug" / "wavec.exe", + ROOT / "target" / "debug" / "wavec", + ROOT / "target" / "x86_64-pc-windows-gnu" / "release" / "wavec.exe", + ROOT / "target" / "x86_64-pc-windows-gnu" / "debug" / "wavec.exe", + ] for candidate in candidates: - path = candidate if candidate.is_absolute() else ROOT / candidate - if path.is_file(): - return path + if candidate.is_file(): + return candidate raise FileNotFoundError("wavec not found; build it or pass --wavec") diff --git a/tools/test_check_wave_corpus.py b/tools/test_check_wave_corpus.py index 5997a552..c2139bcf 100644 --- a/tools/test_check_wave_corpus.py +++ b/tools/test_check_wave_corpus.py @@ -1,8 +1,12 @@ import io +import os +import tempfile import unittest +from pathlib import Path from unittest.mock import patch -from tools.check_wave_corpus import parse_args, main +from tools.check_wave_corpus import parse_args, resolve_wavec, main +import tools.check_wave_corpus as check_wave_corpus class TestCheckWaveCorpusCLI(unittest.TestCase): @@ -92,5 +96,103 @@ def test_help_flag(self): self.assertIn("per-file timeout in seconds (default: 15)", output) +class TestResolveWavec(unittest.TestCase): + def test_invalid_explicit_path_fails_without_selecting_existing_fallback(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + fallback = root / "target" / "release" / "wavec" + fallback.parent.mkdir(parents=True) + fallback.touch() + + with patch.object(check_wave_corpus, "ROOT", root): + with self.assertRaises(FileNotFoundError) as cm: + resolve_wavec(Path("missing/wavec")) + + self.assertIn("missing/wavec", str(cm.exception)) + + def test_invalid_explicit_wavec_env_fails_without_selecting_fallback(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + fallback = root / "target" / "release" / "wavec" + fallback.parent.mkdir(parents=True) + fallback.touch() + + with patch.object(check_wave_corpus, "ROOT", root): + with patch.dict(os.environ, {"WAVEC": "missing_env_wavec"}, clear=False): + with self.assertRaises(FileNotFoundError) as cm: + resolve_wavec(None) + + self.assertIn("missing_env_wavec", str(cm.exception)) + + def test_valid_explicit_path_selected(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + custom = root / "bin" / "custom_wavec" + custom.parent.mkdir(parents=True) + custom.touch() + + fallback = root / "target" / "release" / "wavec" + fallback.parent.mkdir(parents=True) + fallback.touch() + + with patch.object(check_wave_corpus, "ROOT", root): + self.assertEqual(resolve_wavec(custom), custom) + self.assertEqual( + resolve_wavec(Path("bin/custom_wavec")), + custom, + ) + + def test_valid_wavec_env_selected(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + custom = root / "bin" / "custom_wavec" + custom.parent.mkdir(parents=True) + custom.touch() + + fallback = root / "target" / "release" / "wavec" + fallback.parent.mkdir(parents=True) + fallback.touch() + + with patch.object(check_wave_corpus, "ROOT", root): + with patch.dict(os.environ, {"WAVEC": str(custom)}, clear=False): + self.assertEqual(resolve_wavec(None), custom) + + def test_no_override_falls_back_to_discovery(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + fallback = root / "target" / "release" / "wavec" + fallback.parent.mkdir(parents=True) + fallback.touch() + + with patch.object(check_wave_corpus, "ROOT", root): + env = os.environ.copy() + env.pop("WAVEC", None) + with patch.dict(os.environ, env, clear=True): + self.assertEqual(resolve_wavec(None), fallback) + + def test_empty_wavec_env_falls_back_to_discovery(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + fallback = root / "target" / "release" / "wavec" + fallback.parent.mkdir(parents=True) + fallback.touch() + + with patch.object(check_wave_corpus, "ROOT", root): + with patch.dict(os.environ, {"WAVEC": " "}, clear=False): + self.assertEqual(resolve_wavec(None), fallback) + + def test_no_override_and_no_binary_raises_file_not_found(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + with patch.object(check_wave_corpus, "ROOT", root): + env = os.environ.copy() + env.pop("WAVEC", None) + with patch.dict(os.environ, env, clear=True): + with self.assertRaises(FileNotFoundError) as cm: + resolve_wavec(None) + + self.assertIn("wavec not found; build it or pass --wavec", str(cm.exception)) + + if __name__ == "__main__": unittest.main()