#!/usr/bin/env python3 # Oracle for taicpp/tests/mplex_vt_test.cpp: loads ../tai.py, generates seeded cases for # TuiVtScreen and MplexKeys and writes cases with the Python results next to them. # usage: python3 -I mplex_vt_oracle.py [--show N] import collections import hashlib import os import importlib.util import random import sys tai_path, out_path = sys.argv[1], sys.argv[2] spec = importlib.util.spec_from_file_location("tai_mod", tai_path) tai = importlib.util.module_from_spec(spec) sys.modules["tai_mod"] = tai spec.loader.exec_module(tai) def hx(data): if isinstance(data, str): data = data.encode("utf-8", "surrogatepass") return data.hex() if data else "-" def cell_text(cell): if cell is None: return "-" char, fg, bg, attr = cell return "%x:%s:%s:%d" % (ord(char), fg or "_", bg or "_", attr) def row_text(row): return " ".join(cell_text(c) for c in row) if row else "." def dump(screen): out = [] out.append("size %d %d" % (screen.rows, screen.cols)) out.append("cur %d %d %d" % (screen.cx, screen.cy, 1 if screen.cursor_visible else 0)) out.append("sgr %s %s %d" % (screen.fg or "_", screen.bg or "_", screen.attr)) out.append("region %d %d %d %d %d" % (screen.region_top, screen.region_bottom, 1 if screen.lr_mode else 0, screen.margin_left, screen.margin_right)) out.append("modes %d %d %d" % (screen.app_cursor, screen.bracketed_paste, screen.alt_active)) out.append("title " + hx(screen.title)) out.append("replies " + hx(bytes(screen.replies))) if screen.history is None: out.append("nohist") else: out.append("hist %d %d %d" % (screen.history_pushed, len(screen.history), screen.history.maxlen)) for row in screen.history: out.append("h " + row_text(row)) for row in screen.grid: out.append("g " + row_text(row)) out.append("lines " + "|".join(hx(t) for t in screen.text_lines())) out.append("text " + hx(screen.text())) out.append("text3 " + hx(screen.text(3))) return "\n".join(out) def run_vt(rows, cols, hist, ops): screen = tai.TuiVtScreen(rows, cols) if hist >= 0: screen.history = collections.deque(maxlen=hist) dumps = [] for kind, arg in ops: exc = "" try: if kind == "f": screen.feed(arg) elif kind == "r": screen.resize(arg[0], arg[1]) elif kind == "t": screen.take_replies() except ValueError as err: exc = "\nexc ValueError" dumps.append(dump(screen) + exc) return dumps rnd = random.Random(int(os.environ.get('MPLEX_SEED', '20260101'))) CSI_FINALS = list("ABCDEFGHJKLMPSTXZ@bdfhlmnpqrsuc") PARAMS = ["", "0", "1", "2", "3", "5", "7", "9", "10", "25", "255", "300", "99999", "1;1", "2;3", "5;5", "1;80", "3;1;4", "38;5;12", "48;5;200", "38;2;1;2;3", "48;2;250;251;252", "1;31;44", "0;1", "4;7", "?25", "?1", "?2004", "?69", "?1049", "?47", "?1047", "?25;1", ">", ">0", "0", "$", "?25$", "?69$", "?1049$", "1;3", "2;20", "20;2", "1:2", "", "!", "\"", "= 1", " 5", "5 ", "1 2", "99999999999999999999999", "38;5;300", "38;2;1;2", "48;5", "0;0", "1;0"] ESC_SIMPLE = ["7", "8", "M", "D", "E", "c", "(0", "(B", ")0", "#8", "%G", "=", ">"] TEXT = ["hello", "world ", "x", "abc def", "$ ls\r\n", "\r\n", "\n", "\r", "\t", "\b", "\x07", "\x0e", "\x0f", "\x00", "\x7f", "\x0b", "\x0c", "é", "€", "\U0001f600", "中", "̀", "line one\r\nline two\r\n", "q" * 30, "z" * 100, "lqqqk", "xxx", "\x9b", "\x9d", "\x90", "\x9e", "\x9f"] PART_UTF8 = [b"\xc3", b"\xe2\x82", b"\xf0\x9f\x98", b"\xc3\xa9", b"\xff", b"\x80", b"\xc0\xaf", b"\xe2\x28\xa1", b"\xf0\x28\x8c\xbc", b"\xed\xa0\x80", b"\xf4\x90\x80\x80", b"\xe0\x80\x80", b"\xf0\x80", b"\xc2"] def rand_token(): k = rnd.random() if k < 0.28: return rnd.choice(TEXT).encode("utf-8") if k < 0.62: return b"\x1b[" + rnd.choice(PARAMS).encode() + rnd.choice(CSI_FINALS).encode() if k < 0.70: return b"\x1b" + rnd.choice(ESC_SIMPLE).encode() if k < 0.76: term = rnd.choice([b"\x07", b"\x1b\\", b""]) code = rnd.choice([b"0", b"2", b"1", b"52", b""]) return b"\x1b]" + code + b";" + rnd.choice([b"title", b"t\xc3\xa9", b"x" * 300, b""]) + term if k < 0.80: return rnd.choice([b"\x1bP", b"\x1bX", b"\x1b^", b"\x1b_"]) + b"data" + rnd.choice([b"\x07", b"\x1b\\"]) if k < 0.86: return rnd.choice(PART_UTF8) if k < 0.90: return b"\xc2\x9b" + rnd.choice(PARAMS).encode() + rnd.choice(CSI_FINALS).encode() if k < 0.94: return b"\x1b[" + rnd.choice(PARAMS).encode() return bytes(rnd.randrange(256) for _ in range(rnd.randrange(1, 6))) def rand_stream(): return b"".join(rand_token() for _ in range(rnd.randrange(1, 14))) def chunks(data): out = [] pos = 0 while pos < len(data): n = rnd.choice([1, 2, 3, 5, 8, 40, 1000]) out.append(data[pos:pos + n]) pos += n return out or [b""] cases = [] FIXED = [ b"$ echo hi\r\nhi\r\n$ ", b"\x1b[31mred\x1b[0m \x1b[1;4;7mbold\x1b[22;24;27m \x1b[38;5;200mx\x1b[48;2;1;2;3my\x1b[39;49mz", b"\x1b[2J\x1b[H\x1b[5;10Hx\x1b[3A\x1b[2B\x1b[4C\x1b[3D\x1b[6G\x1b[2d", b"\x1b[3;8r" + b"a\r\n" * 12 + b"\x1b[r", b"\x1b[?1049hALT\x1b[2;3Hxx\x1b[?1049l back", b"\x1b[?47hone\x1b[?47l", b"abcdef\x1b[3D\x1b[2P\x1b[3@\x1b[2X\x1b[0P\x1b[0@", b"\x1b[1;1H" + b"line\r\n" * 30, b"\xe2\x82", b"\xe2\x82\xac", b"\xff\xfe", b"\xf0\x9f\x98\x80", b"\x1b]0;my title\x07\x1b]2;other\x1b\\\x1b]1;no\x07", b"\x1b[5n\x1b[6n\x1b[c\x1b[>c\x1b[0c\x1b[?25$p\x1b[?1$p\x1b[?2004$p\x1b[?69$p\x1b[?1049$p\x1b[4$p\x1b[?7$p", b"\x1b[?69h\x1b[3;10s\x1b[1;3Hab\x1b[1;5r\r\n\r\n\r\n\r\n\r\n\x1b[?69l", b"\x1b(0lqqqk\x1b(Bplain\x1b[3b", b"x" * 85 + b"\r\n" + b"y" * 80 + b"z", b"\x1b7abc\x1b8d\x1b[s\x1b[3;3Hx\x1b[u", b"\x1bM\x1bM\x1bD\x1bE\x1bc\x1b[3L\x1b[2M\x1b[1S\x1b[2T", b"\x1b[1;1H\x1b[0J\x1b[1J\x1b[2J\x1b[3J\x1b[0K\x1b[1K\x1b[2K", b"\x1b[?25l\x1b[?25h\x1b[?1h\x1b[?1l\x1b[?2004h\x1b[?2004l", ] for rows, cols in ((24, 80), (5, 12), (1, 1), (3, 40), (10, 7)): for stream in FIXED: for hist in (-1, 3): cases.append((rows, cols, hist, [("f", stream)], "fixed")) for stream in FIXED: cases.append((6, 20, 5, [("f", stream), ("r", (9, 11)), ("f", stream), ("r", (3, 30)), ("f", stream), ("r", (1, 2)), ("t", None), ("f", stream)], "resize")) for _ in range(int(os.environ.get('MPLEX_FUZZ', '2400'))): rows = rnd.choice([1, 2, 3, 5, 8, 12]) cols = rnd.choice([1, 2, 4, 9, 16, 24]) hist = rnd.choice([-1, 0, 2, 7, 100]) ops = [] for part in chunks(rand_stream()): ops.append(("f", part)) if rnd.random() < 0.08: ops.append(("r", (rnd.randrange(1, 12), rnd.randrange(1, 26)))) if rnd.random() < 0.05: ops.append(("t", None)) cases.append((rows, cols, hist, ops, "fuzz")) # ---- MplexKeys ---------------------------------------------------------------------- K = tai.MplexKeys KEY_SPECS = [" ", "", " ", "a", "A", "C-a", "C-A", "M-a", "C-M-a", "M-C-a", "S-Up", "S-a", "S-A", "C-S-a", "^a", "^A", "C-m", "C-i", "C-[", "C-M-m", "Space", "Enter", "Tab", "BTab", "BSpace", "Escape", "Esc", "Up", "Down", "Left", "Right", "Home", "End", "PPage", "PageUp", "PgUp", "NPage", "PageDown", "PgDn", "IC", "Insert", "DC", "Delete", "Return", "Backspace", "Backtab", "F1", "F5", "F12", "F13", "F0", "f1", "C-F5", "M-F1", "S-F2", "C-M-S-Up", "C-Up", "M-Up", "C-Space", "M-Space", "C-x", "C-\\", "C-]", "C-^", "C-_", "C-@", "C-?", "C-1", "X", "foo", "C-foo", "C-", "M-", "é", "C-é", "S-é", "S-1", "C-S-1", "^", "^^", "^ ", " a ", "C- ", "M-M-a", "C-C-a", "S-S-a", "C-M-S-a", "-", "C--", "M--", "S--", "C-.", "M-<", "M->", "M-{", "ctrl", "C-b", "C-z", "None", "none"] for ch in "!\"#$%&'()*+,-./0123456789:;<=>?@[\\]^_`{|}~": KEY_SPECS += [ch, "C-" + ch, "M-" + ch, "S-" + ch] for name in ("Up", "Down", "Left", "Right", "Home", "End", "PPage", "NPage", "IC", "DC", "Enter", "Tab", "BTab", "BSpace", "Escape", "Space") + tuple("F%d" % n for n in range(1, 13)): for mods in ("", "C-", "M-", "S-", "C-M-", "C-S-", "M-S-", "C-M-S-"): KEY_SPECS.append(mods + name) key_cases = [] def kc(kind, *args): key_cases.append((kind, args)) for s in KEY_SPECS: kc("canon", s) CANON = [] for s in KEY_SPECS: try: CANON.append(K.canonical(s)) except tai.MplexError: pass for n in sorted(set(CANON)): kc("tobytes", n, False) kc("tobytes", n, True) for n in ("Foo", "C-", "", "X-Up", "Up-", "C-F13", "F13", "C-Foo"): kc("tobytes", n, False) for b in range(256): kc("namechar", bytes([b])) for raw in (b"\xc3\xa9", b"\xe2\x82\xac", b"\xf0\x9f\x98\x80", b"\xff\xfe", b"ab", b"\xc3"): kc("namechar", raw) SEQS = [b"\x1b[A", b"\x1b[B", b"\x1b[C", b"\x1b[D", b"\x1b[H", b"\x1b[F", b"\x1b[Z", b"\x1bOA", b"\x1bOB", b"\x1bOC", b"\x1bOD", b"\x1bOH", b"\x1bOF", b"\x1bOP", b"\x1bOQ", b"\x1bOR", b"\x1bOS", b"\x1bOZ", b"\x1b[1~", b"\x1b[2~", b"\x1b[3~", b"\x1b[4~", b"\x1b[5~", b"\x1b[6~", b"\x1b[7~", b"\x1b[8~", b"\x1b[9~", b"\x1b[11~", b"\x1b[12~", b"\x1b[13~", b"\x1b[14~", b"\x1b[15~", b"\x1b[17~", b"\x1b[18~", b"\x1b[19~", b"\x1b[20~", b"\x1b[21~", b"\x1b[23~", b"\x1b[24~", b"\x1b[25~", b"\x1b[1;2A", b"\x1b[1;3A", b"\x1b[1;5A", b"\x1b[1;6B", b"\x1b[1;4C", b"\x1b[1;7D", b"\x1b[1;8H", b"\x1b[1;9F", b"\x1b[1;16A", b"\x1b[1;1A", b"\x1b[1;0A", b"\x1b[1;xA", b"\x1b[5;5~", b"\x1b[3;3~", b"\x1b[15;2~", b"\x1b[24;8~", b"\x1b[99;2~", b"\x1b[1;2Q", b"\x1b[97u", b"\x1b[97;5u", b"\x1b[97;2u", b"\x1b[65;2u", b"\x1b[49;2u", b"\x1b[13u", b"\x1b[13;5u", b"\x1b[9;2u", b"\x1b[27u", b"\x1b[127u", b"\x1b[57417u", b"\x1b[57419;5u", b"\x1b[57441u", b"\x1b[57441;1:3u", b"\x1b[97;1:3u", b"\x1b[97;1:2u", b"\x1b[1u", b"\x1b[26u", b"\x1b[0u", b"\x1b[0;;97u", b"\x1b[57400u", b"\x1b[57344u", b"\x1b[31u", b"\x1b[32u", b"\x1b[97;;228u", b"\x1b[?1u", b"\x1b[u", b"\x1b[97;3u", b"\x1b[97;8u", b"\x1b[0;1;65:66u", b"\x1b[57426;3u", b"\x1b[57443u", b"\x1b[57443;1:2u", b"\x1b[57443;1:3u", b"\x1b[M !!", b"\x1b[M`!!", b"\x1b[Mabc", b"\x1b[M ", b"\x1b[<0;10;5M", b"\x1b[<0;10;5m", b"\x1b[<64;1;1M", b"\x1b[<65;1;1M", b"\x1b[<35;1;1M", b"\x1b[<2;3;4M", b"\x1b[<0;1M", b"\x1b[ 4 and sys.argv[3] == "--show": index = int(sys.argv[4]) # regenerate identically: cases list is deterministic rows, cols, hist, ops, _tag = cases[index] for line in run_vt(rows, cols, hist, ops): print(line) print("-----") sys.exit(0) with open(out_path, "w") as handle: for rows, cols, hist, ops, tag in cases: digests = " ".join(sha(d) for d in run_vt(rows, cols, hist, ops)) handle.write("VT\t%d\t%d\t%d\t%s\t=\t%s\n" % (rows, cols, hist, "\t".join(op_text(o) for o in ops), digests)) for kind, args in key_cases: shown = [] for a in args: if isinstance(a, bool): shown.append("1" if a else "0") else: shown.append(hx(a)) handle.write("KEY\t%s\t%s\t=\t%s\n" % (kind, "\t".join(shown), key_result(kind, args))) print("cases: %d vt, %d key" % (len(cases), len(key_cases)))