| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- #!/usr/bin/env python3
- """End-to-end UI test: R compiles and RUNS the image, with no file written.
- uitest.py covers the compile error path, comtest.py the artifact path. This
- covers TP3's `R', which produces no artifact at all: the linked image is poked
- into the interpreter's own 64 KB at 0100h and executed inside this process, so
- the only evidence it ever existed is what the program printed.
- 1. W loads a work file (LoadWorkFile ends with a Pause: needs a filler key)
- 2. R compiles, reports the code and data sizes, pokes LinkSize() bytes at
- 0100h, runs them, and reports a status and a step count
- 3. The GUEST'S OWN OUTPUT is compared against the fixture's .out
- Step 3 is the point, and it is deliberately not "the shell said OK". The .out
- files in this project are hand-derived from Pascal's semantics and never
- blessed from a machine's output, so comparing against one checks the
- interpreter against a third thing that the code under test did not produce:
- the compiler chose the bytes, the interpreter executed them, and neither had a
- say in what the answer was supposed to be.
- Step 4-5: the reported status must be a clean INT 21h AH=4Ch with code 0, the
- step count must be non-zero (a run that executed nothing is not a run), ESC
- must get back to the main menu, and the .COM on disk must be untouched - R is
- Memory and .COM alike by design, and "it didn't write a file" is a claim about
- behaviour that only this test can make.
- Usage: runtest.py [fixture.pas] (the fixture must have a .out)
- Passing a fixture whose .out does not match is expected to be RED - that is
- the test being non-vacuous, not a failure of the shell.
- """
- import os
- import re
- import sys
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
- from ptyharness import SHELL_DIR, drain, reap, send, spawn, status_str, visible
- FIXTURE = os.path.abspath(sys.argv[1]) if len(sys.argv) > 1 else os.path.join(
- os.path.dirname(os.path.abspath(__file__)), "fixtures", "t34_arith.pas")
- OUT = os.path.splitext(FIXTURE)[0] + ".out"
- COM = os.path.splitext(FIXTURE)[0] + ".COM"
- if not os.path.exists(OUT):
- sys.exit("runtest.py: %s has no .out, so there would be nothing to "
- "compare the guest's output against" % os.path.basename(OUT))
- # Hand-derived expectation, read as the raw CRLF bytes the runtime emits.
- EXPECTED = open(OUT, "rb").read()
- def steps_of(text):
- m = re.search(r"steps executed: (\d+)", text)
- return int(m.group(1)) if m else -1
- def main():
- com_before = os.stat(COM).st_mtime_ns if os.path.exists(COM) else None
- pid, fd = spawn()
- checks = []
- run = ""
- after = ""
- try:
- drain(fd, quiet=0.6)
- send(fd, b"w")
- send(fd, FIXTURE.encode())
- send(fd, b"\r", quiet=0.8)
- send(fd, b" ", quiet=0.5) # clears LoadWorkFile's Pause
- run = visible(send(fd, b"r", quiet=2.5))
- checks.append(("R reported a successful compile",
- "Compiled OK - code" in run))
- checks.append(("R reported poking the image at 0100h",
- "bytes at 0100h" in run))
- # The status line is what Run86 answered: 0 is the guest's own
- # INT 21h AH=4Ch, 1 an interpreter fault, 2 the step limit. Only 0
- # means the program finished because it decided to.
- checks.append(("guest ran to a clean AH=4Ch exit with code 0",
- "program terminated (INT 21h AH=4Ch, code 0)" in run))
- checks.append(("R reported a non-zero step count", steps_of(run) > 0))
- checks.append(("guest output matches the hand-derived .out",
- EXPECTED.decode("latin-1") in run))
- after = visible(send(fd, b"\x1b", quiet=0.8))
- checks.append(("ESC after the run returned to the main menu",
- "Main file" in after))
- send(fd, b"q", quiet=0.8)
- finally:
- status = reap(fd, pid)
- checks.append(("shell exited cleanly (status 0)", os.WIFEXITED(status)
- and os.WEXITSTATUS(status) == 0))
- com_after = os.stat(COM).st_mtime_ns if os.path.exists(COM) else None
- checks.append(("R wrote no .COM (it runs the image where it already is)",
- com_before == com_after))
- print("UI TEST (R): %s" % os.path.basename(FIXTURE))
- print("-" * 60)
- ok = True
- for name, passed in checks:
- print("%-58s %s" % (name[:58], "PASS" if passed else "FAIL"))
- ok = ok and passed
- print("-" * 60)
- if not ok:
- # Only when something failed, and only what is needed to say why: the
- # captured run and the bytes wanted, so a red line is readable without
- # re-running it by hand.
- print("wanted: %r" % EXPECTED)
- print("captured after R:")
- for line in run.splitlines():
- if line.strip():
- print(" | %s" % line)
- print("steps: %d" % steps_of(run))
- print("child: %s" % status_str(status))
- print("RESULT: %s" % ("ALL PASS" if ok else "FAILURES PRESENT"))
- return 0 if ok else 1
- if __name__ == "__main__":
- sys.exit(main())
|