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
56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import warnings
|
|
|
|
import pytest
|
|
|
|
from ferrolang_vm.dosboxx import SuiteRun, run_suite
|
|
from ferrolang_vm.registry import MAX_MILESTONE, all_cases, milestone_number
|
|
from ferrolang_vm.suite import Case
|
|
|
|
ONLY = os.environ.get("FERRO_TEST_ONLY")
|
|
CASES = all_cases(
|
|
through=milestone_number(os.environ.get("FERRO_TEST_THROUGH", f"m{MAX_MILESTONE}")),
|
|
only=milestone_number(ONLY) if ONLY else None,
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def suite_run() -> SuiteRun:
|
|
run = run_suite(
|
|
CASES,
|
|
keep=os.environ.get("FERRO_TEST_KEEP_FAILED") == "1",
|
|
show_dos=os.environ.get("FERRO_TEST_SHOW_DOS") == "1",
|
|
trace_dos=os.environ.get("FERRO_TEST_TRACE_DOS") == "1",
|
|
)
|
|
yield run
|
|
if os.environ.get("FERRO_TEST_DOS_LOG") == "1":
|
|
console = run.root / "CONSOLE.LOG"
|
|
if console.is_file():
|
|
print(console.read_text(encoding="utf-8", errors="replace"))
|
|
run.cleanup()
|
|
|
|
|
|
def test_compiler_build(suite_run: SuiteRun) -> None:
|
|
assert suite_run.result() == "PASS", suite_run.log()
|
|
|
|
|
|
@pytest.mark.parametrize("case", CASES, ids=lambda case: case.id)
|
|
def test_milestone_case(case: Case, suite_run: SuiteRun) -> None:
|
|
if suite_run.result() != "PASS":
|
|
pytest.skip("compiler build failed")
|
|
result = suite_run.result(case)
|
|
err = suite_run.err(case)
|
|
if result == "PASS" and err:
|
|
warning_lines = [line for line in err.splitlines() if "warning" in line.lower()]
|
|
if warning_lines:
|
|
warnings.warn("\n".join(warning_lines), stacklevel=1)
|
|
code = suite_run.rc(case)
|
|
assert result == "PASS", (
|
|
f"DOS command: {case.command}\n"
|
|
f"Expected success: {case.expect_success}\n"
|
|
f"Exit code: {'not recorded' if code is None else code}\n"
|
|
f"{suite_run.log(case)}\n{err}"
|
|
)
|