Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions toolchain/bootstrap/precheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,15 @@ fi
) &
PID_PARAM_DOCS=$!

# Example case validation
# Example case validation - validate ALL cases in ONE process (validate accepts multiple files) instead of a
# per-case './mfc.sh validate' loop. Each invocation re-paid the mfc.sh/venv bootstrap (~2.4s), which dominated
# (~90%) the ~6 min serial loop over 155 cases; batching drops it to well under a minute.
(
failed=0
for case in examples/*/case.py; do
[ -f "$case" ] || continue
if ! ./mfc.sh validate "$case" > /dev/null 2>&1; then
failed=$((failed + 1))
fi
done
echo "$failed" > "$TMPDIR_PC/examples_exit"
if ./mfc.sh validate examples/*/case.py > /dev/null 2>&1; then
echo "0" > "$TMPDIR_PC/examples_exit"
else
echo "1" > "$TMPDIR_PC/examples_exit"
fi
) &
PID_EXAMPLES=$!

Expand Down Expand Up @@ -194,7 +193,7 @@ EXAMPLES_FAILED=$(cat "$TMPDIR_PC/examples_exit" 2>/dev/null || echo "1")
if [ "$EXAMPLES_FAILED" = "0" ]; then
ok "All example cases are valid."
else
error "$EXAMPLES_FAILED example case(s) failed validation. Run$MAGENTA ./mfc.sh validate examples/\*/case.py$COLOR_RESET for details."
error "Example case validation failed. Run$MAGENTA ./mfc.sh validate examples/\*/case.py$COLOR_RESET for the failing case(s)."
FAILED=1
fi

Expand Down
5 changes: 4 additions & 1 deletion toolchain/mfc/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ def custom_error(message):
continue

if args.get(e) is not None:
args[e] = os.path.abspath(args[e])
if isinstance(args[e], list): # validate accepts multiple case files (nargs="+")
args[e] = [os.path.abspath(p) for p in args[e]]
else:
args[e] = os.path.abspath(args[e])

return args
3 changes: 2 additions & 1 deletion toolchain/mfc/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,8 @@
positionals=[
Positional(
name="input",
help="Path to case file to validate.",
help="Path(s) to case file(s) to validate. Multiple files validate in one process (used by precheck).",
nargs="+",
completion=Completion(type=CompletionType.FILES_PY),
),
],
Expand Down
53 changes: 51 additions & 2 deletions toolchain/mfc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,58 @@
from .state import ARG


def _validate_batch(inputs):
"""Load + constraint-validate many case files in ONE process. Used by precheck to validate every example
case without re-paying the mfc.sh/venv bootstrap per case (the serial per-case './mfc.sh validate' loop
was ~90% bootstrap overhead). Only a load/parse failure fails the command - constraint issues are warnings -
matching the historical per-file exit behavior (a single './mfc.sh validate' exits non-zero only on load error).
Cases with constraint issues are still surfaced (listed in the summary) so the batched output is not misleading."""
failed = []
with_issues = []
for c in inputs:
if not os.path.isfile(c):
failed.append(c)
continue
try:
case = run_input.load(c, do_print=False)
except MFCException:
failed.append(c)
continue
had_issue = False
for stage in ("pre_process", "simulation", "post_process"):
try:
if CaseValidator(case.params).validate(stage): # non-empty -> warnings
had_issue = True
except CaseConstraintError:
had_issue = True # constraint violation is a warning here, not a load failure
if had_issue:
with_issues.append(c)
n = len(inputs)
if failed:
cons.print(f"[bold red]✗ {len(failed)}/{n} case(s) failed to load:[/bold red]")
for c in failed:
cons.print(f" {c}")
sys.exit(1)
if with_issues:
cons.print(f"[bold green]✓[/bold green] Validated {n} case(s): all loaded " f"([yellow]{len(with_issues)} with constraint issues[/yellow]; " f"run './mfc.sh validate <case>' for details):")
for c in with_issues:
cons.print(f" [yellow]⚠[/yellow] {c}")
else:
cons.print(f"[bold green]✓[/bold green] Validated {n} case(s): all loaded, no constraint issues.")


def validate():
"""Validate a case file without building or running."""
input_file = ARG("input")
"""Validate one or more case files without building or running."""
inputs = ARG("input")
if isinstance(inputs, str):
inputs = [inputs]
if len(inputs) > 1:
if ARG("migrate"): # --migrate rewrites a file in place; only meaningful one case at a time
cons.print("[bold red]Error:[/bold red] --migrate operates on a single case file at a time.")
sys.exit(1)
_validate_batch(inputs)
return
input_file = inputs[0]

if not os.path.isfile(input_file):
cons.print(f"[bold red]Error:[/bold red] File not found: {input_file}")
Expand Down
Loading