#!/usr/bin/env python3 """Assertions that pin every claim the article makes about the captured output.""" import pathlib, re, sys OUT = pathlib.Path(__file__).resolve().parent.parent / "output" fails = [] def rd(n): return (OUT / n).read_text() def check(name, cond): print(("PASS " if cond else "FAIL ") + name) if not cond: fails.append(name) lst = rd("01-opcodes.lst") check("int3 assembles to the single byte CC", re.search(r" CC\s+int3", lst) is not None) check("NASM 'int 3' assembles to CD 03", re.search(r" CD03\s+int 3", lst) is not None) check("int 21h assembles to CD 21", re.search(r" CD21\s+int 21h", lst) is not None) check("into assembles to CE", re.search(r" CE\s+into", lst) is not None) t = rd("02-hello21.txt") check("hello21 prints its string", "Hello from INT 21h, AH=09h" in t) check("AH=4Ch return code 7 visible as ERRORLEVEL", "ERRORLEVEL is at least 7" in t) t = rd("03-fn-tour.txt") check("AH=30h reports a version", re.search(r"AH=30h DOS version\s+: \d+\.\d+", t) is not None) check("AH=2Ah/2Ch ranges ok", "okok" not in t and re.search(r"ranges : ok ok", t) is not None) check("AH=02h+06h write AB", re.search(r"AH=06h\s+: AB", t) is not None) check("AH=01h returned 5A ('Z') and AH=0Ah counted 5 chars 'Ankur'", "AH=01h got 0x5A ; AH=0Ah count = 5, text = Ankur" in t) check("AH=25h/35h round trip", re.search(r"vector 60h\s+: ok", t) is not None) check("AH=48h/49h alloc+free ok", re.search(r"alloc\+free 4 KB\s+: ok", t) is not None) check("AH=48h 1 MB request fails CF=1 AX=0008", "(fails) : CF=1 AX=0008" in t) check("file I/O: 12 bytes read, LSEEK says 12", "12 bytes read ; LSEEK(end) says size = 12" in t) check("findfirst sees U.TXT size 12", "U.TXT size = 12" in t) check("reopening deleted file: CF=1 AX=0002", "CF=1 AX=0002" in t) check("mkdir/rmdir ok", "mkdir+rmdir : ok" in t) check("child exit code 7, termination type 0 via AH=4Dh", "AH=4Dh return code = 7 type = 0" in t) t = rd("04-int3-handler.txt") check("CC: return IP = trap + 1", "CC (INT3): return IP = trap address + 1" in t) check("CD 03: return IP = trap + 2", "CD 03 (INT 3): return IP = trap address + 2" in t) check("IF and TF are 0 inside the handler, IF back to 1 after IRET", t.count("IF in handler = 0, TF in handler = 0, IF after IRET = 1") == 2) check("IF pushed on the stack was 1", t.count("IF pushed = 1") == 2) t = rd("05-int3-default.txt") check("INT 3 with no handler: program survives under DOSBox", "after INT3 (program survived)" in t) if (OUT / "06-debug-cc.txt").exists(): t = rd("06-debug-cc.txt") check("DEBUG + CC: 'Unexpected breakpoint interrupt' and IP=0104", "Unexpected breakpoint interrupt" in t and "IP=0104" in t) check("DEBUG + CC: next instruction after trap is MOV AX,2222", "0104 B82222 MOV AX,2222" in t) check("DEBUG + CC: single-step then AX=2222", "AX=2222" in t) t = rd("07-debug-cd03.txt") check("DEBUG + CD 03: no breakpoint message", "Unexpected breakpoint interrupt" not in t) check("DEBUG + CD 03: stops at IP=0104 inside the INT 03 instruction", "IP=0104" in t and "03B82222 ADD DI,[BX+SI+2222]" in t) check("DEBUG + CD 03: MOV AX,2222 never runs (AX stays 1111 after stepping)", "AX=2222" not in t) else: print("SKIP DEBUG checks (run scripts/fetch-debug.sh first)") t = rd("08-bp-experiment.txt") check("Unicorn: CC patch trap return IP = site+1, resume gives CX=3", "CC patch=CC trap: vector=3 return IP=7C03 (site=7C02, +1)" in t and "CX=3 (expected 3)" in t) check("Unicorn: CD 03 patch trap return IP = site+2 and resume goes wrong", "return IP=7C04 (site=7C02, +2)" in t and "CX=0 (expected 3)" in t) sys.exit(1 if fails else 0)