- fail closed on MCP input and compile database policy - add shared scan catalogs, finding queries, and tiered gates
161 lines
5.5 KiB
Python
Executable file
161 lines
5.5 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Run Rust and Swift frontend golden benchmarks through Fold."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib.util
|
|
import os
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
RUNNER_COMMON_PATH = REPO_ROOT / "scripts" / "benchmarks" / "runner_common.py"
|
|
_spec = importlib.util.spec_from_file_location("runner_common", RUNNER_COMMON_PATH)
|
|
if _spec is None or _spec.loader is None:
|
|
raise ImportError(f"failed to load {RUNNER_COMMON_PATH}")
|
|
common = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(common)
|
|
|
|
DEFAULT_SPEC_DIRS = [
|
|
REPO_ROOT / "tests" / "benchmarks" / "rust",
|
|
REPO_ROOT / "tests" / "benchmarks" / "swift",
|
|
]
|
|
COMPARE = REPO_ROOT / "scripts" / "benchmarks" / "compare_findings.py"
|
|
|
|
|
|
def locate_rust_frontend() -> Path | None:
|
|
env = os.environ.get("FOLD_RUST_BIN")
|
|
if env and Path(env).exists():
|
|
return Path(env).resolve()
|
|
candidate = REPO_ROOT / "tools" / "fold-rust" / "target" / "release" / "fold-rust"
|
|
if candidate.exists():
|
|
return candidate.resolve()
|
|
path = shutil.which("fold-rust")
|
|
return Path(path).resolve() if path else None
|
|
|
|
|
|
def locate_swift_frontend(build: Path) -> Path | None:
|
|
env = os.environ.get("FOLD_SWIFT_BIN")
|
|
if env and Path(env).exists():
|
|
return Path(env).resolve()
|
|
candidate = build / "tools" / "fold-swift" / "fold-swift"
|
|
if candidate.exists():
|
|
return candidate.resolve()
|
|
path = shutil.which("fold-swift")
|
|
return Path(path).resolve() if path else None
|
|
|
|
|
|
def frontend_env(mode: str, build: Path) -> dict[str, str]:
|
|
env = dict(os.environ)
|
|
if mode == "rust":
|
|
frontend = locate_rust_frontend()
|
|
if frontend is None:
|
|
raise FileNotFoundError("missing fold-rust frontend")
|
|
env["FOLD_RUST_BIN"] = str(frontend)
|
|
return env
|
|
if mode == "swift":
|
|
frontend = locate_swift_frontend(build)
|
|
if frontend is None:
|
|
raise FileNotFoundError("missing fold-swift frontend")
|
|
if shutil.which("swiftc") is None:
|
|
raise FileNotFoundError("missing swiftc")
|
|
env["FOLD_SWIFT_BIN"] = str(frontend)
|
|
return env
|
|
raise ValueError(f"unsupported frontend mode: {mode}")
|
|
|
|
|
|
def prepare_paths(spec: dict[str, Any], spec_path: Path) -> list[Path]:
|
|
paths = [
|
|
common.resolve_path(str(p), spec_path.parent) for p in spec.get("paths", [])
|
|
]
|
|
if not paths:
|
|
raise ValueError(
|
|
f"frontend spec must contain non-empty paths list: {spec_path}"
|
|
)
|
|
missing = [str(p) for p in paths if not p.exists()]
|
|
if missing:
|
|
raise FileNotFoundError(", ".join(missing))
|
|
return paths
|
|
|
|
|
|
def run_one(
|
|
spec_path: Path, fold_bin: Path, build: Path, output_dir: Path
|
|
) -> tuple[bool, dict[str, Any]]:
|
|
spec = common.load_json(spec_path)
|
|
name = str(spec.get("name", spec_path.stem))
|
|
mode = str(spec.get("mode", spec_path.parent.name))
|
|
skip_if_missing = bool(spec.get("skip_if_missing_tools", True))
|
|
try:
|
|
paths = prepare_paths(spec, spec_path)
|
|
env = frontend_env(mode, build)
|
|
except FileNotFoundError as exc:
|
|
if skip_if_missing:
|
|
return True, {"name": name, "status": "skipped", "reason": str(exc)}
|
|
return False, {"name": name, "status": "failed", "reason": str(exc)}
|
|
except Exception as exc: # noqa: BLE001 - benchmark should report setup failure.
|
|
return False, {"name": name, "status": "failed", "reason": str(exc)}
|
|
|
|
report = output_dir / f"{mode}_{spec_path.stem}.json"
|
|
cmd = [str(fold_bin), "scan", "--mode", mode, *map(str, paths)]
|
|
if spec.get("detectors"):
|
|
cmd.extend(["--detectors", str(spec["detectors"])])
|
|
cmd.extend(["--format", "sarif", "--output", str(report)])
|
|
scan_args = spec.get("scan_args", [])
|
|
if not isinstance(scan_args, list) or not all(
|
|
isinstance(arg, str) for arg in scan_args
|
|
):
|
|
return False, {
|
|
"name": name,
|
|
"status": "failed",
|
|
"reason": "scan_args must be a list of strings",
|
|
}
|
|
cmd.extend(scan_args)
|
|
return common.scan_and_compare(
|
|
cmd=cmd,
|
|
report=report,
|
|
spec=spec,
|
|
spec_path=spec_path,
|
|
name=name,
|
|
repo_root=REPO_ROOT,
|
|
compare_script=COMPARE,
|
|
env=env,
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--build", default="build/release", type=Path, help="Meson build directory"
|
|
)
|
|
parser.add_argument(
|
|
"--fold", type=Path, help="Path to fold binary (default: BUILD/src/cli/fold)"
|
|
)
|
|
parser.add_argument(
|
|
"--output-dir", default=Path("/tmp/fold-rust-swift-goldens"), type=Path
|
|
)
|
|
parser.add_argument(
|
|
"specs", nargs="*", type=Path, help="Specific benchmark spec files"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
build = args.build if args.build.is_absolute() else (REPO_ROOT / args.build)
|
|
fold_bin = common.find_fold_binary(REPO_ROOT, build, args.fold)
|
|
if fold_bin is None:
|
|
common.print_missing_fold(args.fold or (build / "src" / "cli" / "fold"))
|
|
return 2
|
|
|
|
args.output_dir.mkdir(parents=True, exist_ok=True)
|
|
specs = common.specs_from_args(args.specs, DEFAULT_SPEC_DIRS)
|
|
return common.run_suite(
|
|
suite_name="rust_swift_goldens",
|
|
specs=specs,
|
|
run_one=lambda spec_path: run_one(
|
|
spec_path, fold_bin, build.resolve(), args.output_dir.resolve()
|
|
),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|