Files
doslang-mirror/src/ferrolang_vm/test_cli.py
T
coolguyandClaude Opus 5 8b7d6a8d09 dev: make DOS build failures diagnosable
Reviewing an M7 branch took six DOSBox-X runs to find three build blockers
that each take a second to explain. The runner threw away everything needed to
see them.

Capture the compiler build's output. Case commands were redirected to
RESULTS\<key>.LOG but `call BUILD.BAT` was not, so the step that fails first
and blocks every case left only BUILD.FAIL containing the string "FAIL". The
twelve wcl invocations inside it were invisible; finding "Unable to open
src\emit_c_m7.c" meant hand-editing build-dos.bat to add a redirect and
re-running the VM.

Record exit codes. The batch collapsed every outcome to `if errorlevel 1`, so
a compiler that aborted and one that exited 1 with a diagnostic were the same
FAIL. RC.BAT now walks a descending errorlevel ladder into RESULTS\<key>.RC
and the host derives pass/fail from it, which immediately separates an
ordinary rejection (1) from a trap (255). Note the space in `echo 0 >FILE`:
without it DOS parses `0>` as a redirect of handle 0.

Stop falling back to CONSOLE.LOG. That is DOSBox-X's own log -- display
enumeration and INT15 chatter -- so a crashed command reported fifty lines of
emulator noise instead of saying it produced no output.

Add tools/tests/test_dos_names.py. An over-long source name reaches the DOS
build as `Unable to open "src\..."`, which reads as a missing file rather than
a name FAT cannot represent, and only after a VM boot and ten object builds.
The check runs on the host in 0.03s and flags emit_c_m7.c (9-character stem)
on the branch that prompted this.

Also pass -k through to pytest so a single case can be re-run without its
whole milestone, and print the resolved ROOT at startup: an editable install
plus a git worktree will otherwise silently build a different checkout than
the one the shell is in.

Verified on master: 155 passed, unchanged. Recorded codes are 0 for success,
1 for rejections, 255 for the three bounds traps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BScg8CF1sAAM2zVHAu5zvW
2026-08-16 23:02:28 +09:00

80 lines
3.6 KiB
Python

"""Developer test entry point backed by a disposable DOSBox-X run."""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
from .dosboxx import DosboxError, setup
from .registry import MAX_MILESTONE, MILESTONES
def main() -> int:
parser = argparse.ArgumentParser(
prog="ferro-test",
description="Fast Ferro development tests in DOSBox-X/Open Watcom.",
)
commands = parser.add_subparsers(dest="command", required=True)
prepare = commands.add_parser("setup", help="download and verify pinned development tools")
prepare.add_argument("--accept-watcom-license", action="store_true",
help="confirm acceptance of the Sybase Open Watcom Public License")
run = commands.add_parser("run", help="build once and run milestone pytest cases")
selection = run.add_mutually_exclusive_group()
selection.add_argument("--through", choices=MILESTONES, default=f"m{MAX_MILESTONE}",
help="run cumulatively through this milestone "
f"(default: m{MAX_MILESTONE})")
selection.add_argument("--only", choices=MILESTONES,
help="run only this milestone's cases")
run.add_argument("-v", "--verbose", action="store_true", help="show every pytest case")
run.add_argument("--keep-failed", action="store_true",
help="keep the disposable DOS filesystem after failures")
run.add_argument("--show-dos", action="store_true",
help="show DOSBox-X and wait for a key before closing")
run.add_argument("--dos-log", action="store_true",
help="print the captured DOS console after the run")
run.add_argument("--trace-dos", action="store_true",
help="do not redirect case command output")
run.add_argument("-k", dest="select", metavar="EXPR",
help="run only cases whose id matches this pytest -k expression")
args, extra = parser.parse_known_args()
try:
if args.command == "setup":
dosbox, watcom = setup(accept_watcom_license=args.accept_watcom_license)
print(f"DOSBox-X: {dosbox}")
print(f"Open Watcom: {watcom}")
return 0
if args.only:
os.environ["FERRO_TEST_ONLY"] = args.only
else:
os.environ["FERRO_TEST_THROUGH"] = args.through
for enabled, name in (
(args.keep_failed, "FERRO_TEST_KEEP_FAILED"),
(args.show_dos, "FERRO_TEST_SHOW_DOS"),
(args.trace_dos, "FERRO_TEST_TRACE_DOS"),
(args.dos_log, "FERRO_TEST_DOS_LOG"),
):
if enabled:
os.environ[name] = "1"
import pytest
from .paths import ROOT
# The package can be imported from a different checkout than the one the
# shell is sitting in -- an editable install plus a git worktree is enough
# to silently build and test the wrong tree. Say which tree this is.
print(f"ferro-test: building {ROOT}", file=sys.stderr)
test_file = os.fspath(ROOT / "tools" / "tests" / "test_milestones_dosboxx.py")
pytest_args = [test_file, "--tb=short", "-v" if args.verbose else "-q"]
if args.select:
pytest_args.extend(["-k", args.select])
if args.dos_log:
pytest_args.append("-s")
pytest_args.extend(extra)
return int(pytest.main(pytest_args))
except (DosboxError, ValueError) as exc:
print(f"ferro-test: {exc}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())