test: unify registry and capture Watcom diagnostics

This commit is contained in:
2026-08-16 21:54:22 +09:00
parent 3a7d7de0d3
commit 0dada80e65
6 changed files with 149 additions and 131 deletions
+18 -4
View File
@@ -140,19 +140,29 @@ def _batch(cases: list[Case], *, show_dos: bool, trace_dos: bool) -> str:
for index, case in enumerate(cases): for index, case in enumerate(cases):
key = f"C{index:03d}" key = f"C{index:03d}"
command = case.command command = case.command
# Watcom writes diagnostics into the current directory. Isolate each
# case so a later pytest item never sees stale diagnostics.
lines.extend([
"if exist *.ERR del *.ERR > NUL",
f"if exist RESULTS\\{key}.ERR del RESULTS\\{key}.ERR > NUL",
])
if not trace_dos: if not trace_dos:
command += f" > RESULTS\\{key}.LOG" command += f" > RESULTS\\{key}.LOG"
lines.append(command) lines.append(command)
if case.expect_success: if case.expect_success:
lines.extend([ lines.extend([
f"if errorlevel 1 goto {key}F", f"echo PASS>RESULTS\\{key}.RES", f"if errorlevel 1 goto {key}E", f"echo PASS>RESULTS\\{key}.RES",
f"goto {key}D", f":{key}F", f"echo FAIL>RESULTS\\{key}.RES", f":{key}D", f"goto {key}C", f":{key}E", f"echo FAIL>RESULTS\\{key}.RES",
]) ])
else: else:
lines.extend([ lines.extend([
f"if errorlevel 1 goto {key}P", f"echo FAIL>RESULTS\\{key}.RES", f"if errorlevel 1 goto {key}E", f"echo FAIL>RESULTS\\{key}.RES",
f"goto {key}D", f":{key}P", f"echo PASS>RESULTS\\{key}.RES", f":{key}D", f"goto {key}C", f":{key}E", f"echo PASS>RESULTS\\{key}.RES",
]) ])
lines.extend([
f":{key}C", f"if exist *.ERR type *.ERR > RESULTS\\{key}.ERR",
f"if not exist RESULTS\\{key}.ERR type NUL > RESULTS\\{key}.ERR",
])
lines.extend([ lines.extend([
"goto FINISH", ":BUILDFAIL", "echo FAIL>RESULTS\\BUILD.RES", ":FINISH", "goto FINISH", ":BUILDFAIL", "echo FAIL>RESULTS\\BUILD.RES", ":FINISH",
"echo DONE>RUN.OK", *(["pause"] if show_dos else []), "exit", "", "echo DONE>RUN.OK", *(["pause"] if show_dos else []), "exit", "",
@@ -190,6 +200,10 @@ class SuiteRun:
console = self.root / "CONSOLE.LOG" console = self.root / "CONSOLE.LOG"
return console.read_text(encoding="utf-8", errors="replace") if console.is_file() else "" return console.read_text(encoding="utf-8", errors="replace") if console.is_file() else ""
def err(self, case: Case) -> str:
path = self.fec / "RESULTS" / f"{self._key(case)}.ERR"
return path.read_text(encoding="utf-8", errors="replace") if path.is_file() else ""
def cleanup(self) -> None: def cleanup(self) -> None:
if not self.keep: if not self.keep:
shutil.rmtree(self.root, ignore_errors=True) shutil.rmtree(self.root, ignore_errors=True)
+120
View File
@@ -0,0 +1,120 @@
"""Explicit M1--M3 commands and expectations from TEST-DOS.BAT."""
from __future__ import annotations
from .suite import Case
def _c(milestone: int, name: str, command: str, ok: bool = True) -> Case:
return Case(f"m{milestone}-{name}", milestone, command, ok)
CASES: list[Case] = []
for _name in ("basic", "literals", "keybuilt", "v012form"):
CASES.append(_c(1, f"{_name}-parse", f"FEC.EXE --dump-ast TESTS\\PASS\\{_name.upper()}.FE"))
for _name in ("core", "fmt", "io", "list", "map", "mem", "str", "sys"):
CASES.append(_c(1, f"std-{_name}-parse", f"FEC.EXE --dump-ast STD\\{_name.upper()}.FE"))
for _name in ("misssemi", "unclcomm", "logical"):
CASES.append(_c(1, f"{_name}-reject", f"FEC.EXE --dump-ast TESTS\\FAIL\\{_name.upper()}.FE", False))
for _name in ("hello", "scopes"):
_upper = _name.upper()
CASES.extend([
_c(2, f"{_name}-emit", f"FEC.EXE --target=bits32 --emit-c TESTS\\M2\\{_upper}.FE -o TESTS\\M2\\{_upper}.C"),
_c(2, f"{_name}-build", f"WCL386 -q -za -bt=dos -fe=TESTS\\M2\\{_upper}.EXE TESTS\\M2\\{_upper}.C"),
_c(2, f"{_name}-run", f"TESTS\\M2\\{_upper}.EXE"),
])
CASES.extend([
_c(2, "castwhil-emit", "FEC.EXE --target=bits16 --emit-c TESTS\\M2\\CASTWHIL.FE -o TESTS\\M2\\CAST16.C"),
_c(2, "castwhil-build", "WCL -q -za -bt=dos -fe=TESTS\\M2\\CAST16.EXE TESTS\\M2\\CAST16.C"),
_c(2, "castwhil-run", "TESTS\\M2\\CAST16.EXE"),
])
_m2_outputs = {
"bad-cond": "BAD-CO", "bad-cast": "BAD-CA", "bad-asgn": "BAD-AS",
"bad-unk": "BAD-UN", "bad-ari": "BAD-AR", "bad-type": "BAD-TY",
"bad-ret": "BAD-RE", "bad-unit": "BAD-UI", "bad-void": "BAD-VO",
}
for _name, _output in _m2_outputs.items():
CASES.append(_c(2, f"{_name}-reject", "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M2\\{_name.upper()}.FE -o TESTS\\M2\\{_output}.C", False))
def _m3_runtime(name: str) -> list[Case]:
upper = name.upper()
return [
_c(3, f"{name}-emit", f"FEC.EXE --target=bits32 --emit-c TESTS\\M3\\{upper}.FE -o TESTS\\M3\\{upper}.C"),
_c(3, f"{name}-build", f"WCL386 -q -za -bt=dos -fe=TESTS\\M3\\{upper}.EXE TESTS\\M3\\{upper}.C"),
_c(3, f"{name}-run", f"TESTS\\M3\\{upper}.EXE"),
]
for _name in ("struct", "enum", "array", "mutable"):
CASES.extend(_m3_runtime(_name))
for _name in ("bad-mlet", "bad-shwr"):
CASES.append(_c(3, f"{_name}-reject", "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M3\\{_name.upper()}.FE -o TESTS\\M3\\{_name.upper()}.C", False))
for _name in ("str", "for", "nested", "char", "arrayctx"):
CASES.extend(_m3_runtime(_name))
for _name in ("bounds", "slcbound"):
_upper = _name.upper()
CASES.extend([
_c(3, f"{_name}-emit", f"FEC.EXE --target=bits32 --emit-c TESTS\\M3\\{_upper}.FE -o TESTS\\M3\\{_upper}.C"),
_c(3, f"{_name}-build", f"WCL386 -q -za -bt=dos -fe=TESTS\\M3\\{_upper}.EXE TESTS\\M3\\{_upper}.C"),
_c(3, f"{_name}-trap", f"TESTS\\M3\\{_upper}.EXE", False),
])
CASES.extend([
_c(3, "bounds-no-checks-emit", "FEC.EXE --target=bits32 --no-checks --emit-c TESTS\\M3\\BOUNDS.FE -o TESTS\\M3\\BOUNDS-N.C"),
_c(3, "bounds-no-checks-build", "WCL386 -q -za -bt=dos -fe=TESTS\\M3\\BOUNDS-N.EXE TESTS\\M3\\BOUNDS-N.C"),
])
for _name in ("badfld", "badmat", "badarr", "badcycle", "badstr", "badchar", "badfield", "badindex"):
CASES.append(_c(3, f"{_name}-reject", "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M3\\{_name.upper()}.FE -o TESTS\\M3\\{_name.upper()}.C", False))
"""Explicit M4--M5 cases from TEST-DOS.BAT and M6 fixture expectations.
Only commands and their expected status live here; the ``.fe`` fixtures stay
under ``fec/tests`` and are copied by the runner.
"""
def _c(milestone: int, name: str, command: str, ok: bool) -> Case:
return Case(f"m{milestone}-{name}", milestone, command, ok)
CASES.extend([
_c(4, "format", "FEC.EXE --target=bits32 --emit-c TESTS\\M4\\FORMAT.FE -o TESTS\\M4\\FORMAT.C", True),
_c(4, "format-build", "WCL386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\\M4\\FORMAT.EXE TESTS\\M4\\FORMAT.C", True),
_c(4, "format-run", "TESTS\\M4\\FORMAT.EXE", True),
_c(4, "try-fpr", "FEC.EXE --target=bits32 --emit-c TESTS\\M4\\TRY-FPR.FE -o TESTS\\M4\\TRY-FPR.C", True),
_c(4, "try-fpr-build", "WCL386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\\M4\\TRY-FPR.EXE TESTS\\M4\\TRY-FPR.C", True),
_c(4, "try-fpr-run", "TESTS\\M4\\TRY-FPR.EXE", True),
_c(4, "prop", "FEC.EXE --target=bits32 --emit-c TESTS\\M4\\PROP.FE -o TESTS\\M4\\PROP.C", True),
_c(4, "prop-build", "WCL386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\\M4\\PROP.EXE TESTS\\M4\\PROPTEST.C", True),
_c(4, "prop-run", "TESTS\\M4\\PROP.EXE", True),
])
_m4_outputs = {"bad-type": "BAD-TYP", "bad-writ": "BAD-WRI"}
for _name in ("bad-ari", "bad-verb", "bad-run", "bad-type", "bad-try", "bad-writ", "bad-bufw", "bad-many", "bad-open", "bad-cls"):
_output = _m4_outputs.get(_name, _name.upper())
CASES.append(_c(4, _name, "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M4\\{_name.upper()}.FE -o TESTS\\M4\\{_output}.C", False))
for _name in ("defer", "owned"):
CASES.append(_c(5, _name, "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M5\\{_name.upper()}.FE -o TESTS\\M5\\{_name.upper()}.C", True))
for _name in ("bad-move", "bad-dest", "bad-drop", "bad-dbl", "bad-cond", "bad-proj", "bad-clos", "bad-loop"):
_output = "BAD-DES" if _name == "bad-dest" else _name.upper()
CASES.append(_c(5, _name, "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M5\\{_name.upper()}.FE -o TESTS\\M5\\{_output}.C", False))
CASES += [
_c(5, "runtime", "FEC.EXE --target=bits32 --emit-c TESTS\\M5\\RUNTIME.FE -o TESTS\\M5\\RUNT-G.C", True),
_c(5, "runtime-build", "WCL386 -q -za -bt=dos -dmalloc=m5_malloc -dfree=m5_free -fe=TESTS\\M5\\RUNTIME.EXE TESTS\\M5\\RUNT-G.C TESTS\\M5\\RUNTIME.C", True),
_c(5, "runtime-run", "TESTS\\M5\\RUNTIME.EXE", True),
]
for _name in ("badarg", "badbinit", "badbrmov", "baddefer", "badfld", "badglob", "badgmut", "badinv", "badlocsl", "badloop", "badmove", "badmut", "badmut2", "badptr", "badret", "badrfld", "badridx", "badscop", "badself", "badshwr", "badslfld", "badtwo", "badup", "badweak"):
CASES.append(_c(6, _name, f"FEC.EXE --check TESTS\\M6\\{_name.upper()}.FE", False))
for _name in ("okbranch", "okdefer", "okglobcp", "oklast", "okr8free", "okr8join", "okr8meth", "okr8stat", "okrebor", "okrtlast", "okshare", "okslreb", "okstatic", "oktemp", "oktrim", "okwcall"):
CASES.append(_c(6, _name, f"FEC.EXE --target=bits32 --emit-c -o OUT\\{_name.upper()}.C TESTS\\M6\\{_name.upper()}.FE", True))
def all_cases(*, through: int = 6, only: int | None = None) -> list[Case]:
if only is not None:
return [case for case in CASES if case.milestone == only]
return [case for case in CASES if case.milestone <= through]
-69
View File
@@ -1,69 +0,0 @@
"""Explicit M1--M3 commands and expectations from TEST-DOS.BAT."""
from __future__ import annotations
from .suite import Case
def _c(milestone: int, name: str, command: str, ok: bool = True) -> Case:
return Case(f"m{milestone}-{name}", milestone, command, ok)
M1_M3_CASES: list[Case] = []
for _name in ("basic", "literals", "keybuilt", "v012form"):
M1_M3_CASES.append(_c(1, f"{_name}-parse", f"FEC.EXE --dump-ast TESTS\\PASS\\{_name.upper()}.FE"))
for _name in ("core", "fmt", "io", "list", "map", "mem", "str", "sys"):
M1_M3_CASES.append(_c(1, f"std-{_name}-parse", f"FEC.EXE --dump-ast STD\\{_name.upper()}.FE"))
for _name in ("misssemi", "unclcomm", "logical"):
M1_M3_CASES.append(_c(1, f"{_name}-reject", f"FEC.EXE --dump-ast TESTS\\FAIL\\{_name.upper()}.FE", False))
for _name in ("hello", "scopes"):
_upper = _name.upper()
M1_M3_CASES.extend([
_c(2, f"{_name}-emit", f"FEC.EXE --target=bits32 --emit-c TESTS\\M2\\{_upper}.FE -o TESTS\\M2\\{_upper}.C"),
_c(2, f"{_name}-build", f"WCL386 -q -za -bt=dos -fe=TESTS\\M2\\{_upper}.EXE TESTS\\M2\\{_upper}.C"),
_c(2, f"{_name}-run", f"TESTS\\M2\\{_upper}.EXE"),
])
M1_M3_CASES.extend([
_c(2, "castwhil-emit", "FEC.EXE --target=bits16 --emit-c TESTS\\M2\\CASTWHIL.FE -o TESTS\\M2\\CAST16.C"),
_c(2, "castwhil-build", "WCL -q -za -bt=dos -fe=TESTS\\M2\\CAST16.EXE TESTS\\M2\\CAST16.C"),
_c(2, "castwhil-run", "TESTS\\M2\\CAST16.EXE"),
])
_m2_outputs = {
"bad-cond": "BAD-CO", "bad-cast": "BAD-CA", "bad-asgn": "BAD-AS",
"bad-unk": "BAD-UN", "bad-ari": "BAD-AR", "bad-type": "BAD-TY",
"bad-ret": "BAD-RE", "bad-unit": "BAD-UI", "bad-void": "BAD-VO",
}
for _name, _output in _m2_outputs.items():
M1_M3_CASES.append(_c(2, f"{_name}-reject", "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M2\\{_name.upper()}.FE -o TESTS\\M2\\{_output}.C", False))
def _m3_runtime(name: str) -> list[Case]:
upper = name.upper()
return [
_c(3, f"{name}-emit", f"FEC.EXE --target=bits32 --emit-c TESTS\\M3\\{upper}.FE -o TESTS\\M3\\{upper}.C"),
_c(3, f"{name}-build", f"WCL386 -q -za -bt=dos -fe=TESTS\\M3\\{upper}.EXE TESTS\\M3\\{upper}.C"),
_c(3, f"{name}-run", f"TESTS\\M3\\{upper}.EXE"),
]
for _name in ("struct", "enum", "array", "mutable"):
M1_M3_CASES.extend(_m3_runtime(_name))
for _name in ("bad-mlet", "bad-shwr"):
M1_M3_CASES.append(_c(3, f"{_name}-reject", "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M3\\{_name.upper()}.FE -o TESTS\\M3\\{_name.upper()}.C", False))
for _name in ("str", "for", "nested", "char", "arrayctx"):
M1_M3_CASES.extend(_m3_runtime(_name))
for _name in ("bounds", "slcbound"):
_upper = _name.upper()
M1_M3_CASES.extend([
_c(3, f"{_name}-emit", f"FEC.EXE --target=bits32 --emit-c TESTS\\M3\\{_upper}.FE -o TESTS\\M3\\{_upper}.C"),
_c(3, f"{_name}-build", f"WCL386 -q -za -bt=dos -fe=TESTS\\M3\\{_upper}.EXE TESTS\\M3\\{_upper}.C"),
_c(3, f"{_name}-trap", f"TESTS\\M3\\{_upper}.EXE", False),
])
M1_M3_CASES.extend([
_c(3, "bounds-no-checks-emit", "FEC.EXE --target=bits32 --no-checks --emit-c TESTS\\M3\\BOUNDS.FE -o TESTS\\M3\\BOUNDS-N.C"),
_c(3, "bounds-no-checks-build", "WCL386 -q -za -bt=dos -fe=TESTS\\M3\\BOUNDS-N.EXE TESTS\\M3\\BOUNDS-N.C"),
])
for _name in ("badfld", "badmat", "badarr", "badcycle", "badstr", "badchar", "badfield", "badindex"):
M1_M3_CASES.append(_c(3, f"{_name}-reject", "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M3\\{_name.upper()}.FE -o TESTS\\M3\\{_name.upper()}.C", False))
-49
View File
@@ -1,49 +0,0 @@
"""Explicit M4--M5 cases from TEST-DOS.BAT and M6 fixture expectations.
Only commands and their expected status live here; the ``.fe`` fixtures stay
under ``fec/tests`` and are copied by the runner.
"""
from __future__ import annotations
from .suite import Case
def _c(milestone: int, name: str, command: str, ok: bool) -> Case:
return Case(f"m{milestone}-{name}", milestone, command, ok)
M4_M6_CASES: list[Case] = [
_c(4, "format", "FEC.EXE --target=bits32 --emit-c TESTS\\M4\\FORMAT.FE -o TESTS\\M4\\FORMAT.C", True),
_c(4, "format-build", "WCL386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\\M4\\FORMAT.EXE TESTS\\M4\\FORMAT.C", True),
_c(4, "format-run", "TESTS\\M4\\FORMAT.EXE", True),
_c(4, "try-fpr", "FEC.EXE --target=bits32 --emit-c TESTS\\M4\\TRY-FPR.FE -o TESTS\\M4\\TRY-FPR.C", True),
_c(4, "try-fpr-build", "WCL386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\\M4\\TRY-FPR.EXE TESTS\\M4\\TRY-FPR.C", True),
_c(4, "try-fpr-run", "TESTS\\M4\\TRY-FPR.EXE", True),
_c(4, "prop", "FEC.EXE --target=bits32 --emit-c TESTS\\M4\\PROP.FE -o TESTS\\M4\\PROP.C", True),
_c(4, "prop-build", "WCL386 -q -za -wx -wcd=202 -bt=dos -fe=TESTS\\M4\\PROP.EXE TESTS\\M4\\PROPTEST.C", True),
_c(4, "prop-run", "TESTS\\M4\\PROP.EXE", True),
]
_m4_outputs = {"bad-type": "BAD-TYP", "bad-writ": "BAD-WRI"}
for _name in ("bad-ari", "bad-verb", "bad-run", "bad-type", "bad-try", "bad-writ", "bad-bufw", "bad-many", "bad-open", "bad-cls"):
_output = _m4_outputs.get(_name, _name.upper())
M4_M6_CASES.append(_c(4, _name, "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M4\\{_name.upper()}.FE -o TESTS\\M4\\{_output}.C", False))
for _name in ("defer", "owned"):
M4_M6_CASES.append(_c(5, _name, "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M5\\{_name.upper()}.FE -o TESTS\\M5\\{_name.upper()}.C", True))
for _name in ("bad-move", "bad-dest", "bad-drop", "bad-dbl", "bad-cond", "bad-proj", "bad-clos", "bad-loop"):
_output = "BAD-DES" if _name == "bad-dest" else _name.upper()
M4_M6_CASES.append(_c(5, _name, "FEC.EXE --target=bits32 --emit-c "
f"TESTS\\M5\\{_name.upper()}.FE -o TESTS\\M5\\{_output}.C", False))
M4_M6_CASES += [
_c(5, "runtime", "FEC.EXE --target=bits32 --emit-c TESTS\\M5\\RUNTIME.FE -o TESTS\\M5\\RUNT-G.C", True),
_c(5, "runtime-build", "WCL386 -q -za -bt=dos -dmalloc=m5_malloc -dfree=m5_free -fe=TESTS\\M5\\RUNTIME.EXE TESTS\\M5\\RUNT-G.C TESTS\\M5\\RUNTIME.C", True),
_c(5, "runtime-run", "TESTS\\M5\\RUNTIME.EXE", True),
]
for _name in ("badarg", "badbinit", "badbrmov", "baddefer", "badfld", "badglob", "badgmut", "badinv", "badlocsl", "badloop", "badmove", "badmut", "badmut2", "badptr", "badret", "badrfld", "badridx", "badscop", "badself", "badshwr", "badslfld", "badtwo", "badup", "badweak"):
M4_M6_CASES.append(_c(6, _name, f"FEC.EXE --check TESTS\\M6\\{_name.upper()}.FE", False))
for _name in ("okbranch", "okdefer", "okglobcp", "oklast", "okr8free", "okr8join", "okr8meth", "okr8stat", "okrebor", "okrtlast", "okshare", "okslreb", "okstatic", "oktemp", "oktrim", "okwcall"):
M4_M6_CASES.append(_c(6, _name, f"FEC.EXE --target=bits32 --emit-c -o OUT\\{_name.upper()}.C TESTS\\M6\\{_name.upper()}.FE", True))
+2 -7
View File
@@ -13,10 +13,5 @@ class Case:
def all_cases(*, through: int = 6, only: int | None = None) -> list[Case]: def all_cases(*, through: int = 6, only: int | None = None) -> list[Case]:
from .registry_m1_m3 import M1_M3_CASES from .registry import all_cases as _all_cases
from .registry_m4_m6 import M4_M6_CASES return _all_cases(through=through, only=only)
cases = [*M1_M3_CASES, *M4_M6_CASES]
if only is not None:
return [case for case in cases if case.milestone == only]
return [case for case in cases if case.milestone <= through]
+9 -2
View File
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import os import os
import warnings
import pytest import pytest
@@ -48,7 +49,13 @@ def test_compiler_build(suite_run: SuiteRun) -> None:
def test_milestone_case(case: Case, suite_run: SuiteRun) -> None: def test_milestone_case(case: Case, suite_run: SuiteRun) -> None:
if suite_run.result() != "PASS": if suite_run.result() != "PASS":
pytest.skip("compiler build failed") pytest.skip("compiler build failed")
assert suite_run.result(case) == "PASS", ( 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)
assert result == "PASS", (
f"DOS command: {case.command}\nExpected success: {case.expect_success}\n" f"DOS command: {case.command}\nExpected success: {case.expect_success}\n"
f"{suite_run.log(case)}" f"{suite_run.log(case)}\n{err}"
) )