#!/usr/bin/env python3 """ mame_auto_test.py — headless-прогон .exe в MAME без участия человека. Механизм: system.bat на системном диске (пользователь один раз добавил в конец безусловный вызов "A:\\AUTORUN.BAT") запускает файл, который этот скрипт кладёт на дискету A: вместе с тестируемым .exe. Загрузка идёт полностью автоматически, без эмуляции нажатий клавиш — natural keyboard (Lua natkeyboard:post/post_coded, -autoboot_command) И прямая инъекция состояния через ioport.fields[...]:set_value() на этом драйвере/сборке MAME НЕ РАБОТАЮТ (проверено многократно, разными таймингами — см. memory/mame_autotest_autorun). AUTORUN.BAT — пока единственный надёжный способ что-либо запустить без участия человека. Скрипт делает снимки экрана в заданные моменты времени (секунды эмулированного времени от старта машины, НЕ от нажатия клавиш — их нет) и завершает MAME по таймауту (-seconds_to_run эквивалент через Lua manager.machine:exit()). Снимки — единственный способ проверки результата; их читает Claude (Read с картинкой) визуально, точного сравнения строк нет (алгоритм чтения текстового VRAM не реализован). ВАЖНО: имя генерируемого bat-файла на диске жёстко "AUTORUN.BAT" — make_disk.py берёт имя файла НА ДИСКЕ из basename ЛОКАЛЬНОГО пути (обрезая до 8.3), так что локальный файл-стейджер тоже обязан называться ровно AUTORUN.BAT, иначе system.bat его не найдёт (баг, уже наступали — см. memory). Перед каждым запуском проверяется, что не осталось висящих копий MAME (несколько одновременных инстансов дают недостоверный результат — тоже наступали). Использование: python3 toolchain/mame_auto_test.py [--data f1 f2 ...] [--snap t1,t2,...] [--timeout N] Пример: python3 toolchain/mame_auto_test.py tests/rt_test/rt_test.exe \\ --snap 4,6,8,10,12,14 --timeout 15 """ import argparse import os import shutil import subprocess import sys PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MAME_DIR = os.path.join(PROJECT_ROOT, "mame", "v306") IMG_DIR = os.path.join(MAME_DIR, "IMG") MC_IMG = os.path.join(IMG_DIR, "mc.img") SNAP_DIR = os.path.join(MAME_DIR, "snap_auto") LUA_SCRIPT = os.path.join(MAME_DIR, "_auto_test_gen.lua") AUTORUN_DIR = os.path.join(MAME_DIR, "_autorun_stage") AUTORUN_BAT = os.path.join(AUTORUN_DIR, "AUTORUN.BAT") # имя ОБЯЗАНО быть точным COMMON_ARGS = [ "-skip_gameinfo", "-video", "opengl", "-window", "-nofilter", "-beta:wd179x:0", "35hd", "-beta:wd179x:1", "35hd", "-flop1", MC_IMG, "-flop2", os.path.join(IMG_DIR, "dss171u.img"), "-isa0", "zxbus_adapter", "-isa0:zxbus_adapter:card", "neogs", "-hard1", os.path.join(IMG_DIR, "sp_hdd_sys.chd"), "-hard2", os.path.join(IMG_DIR, "sp_hdd_media.chd"), "-ata2:0", "cdrom", "-cdrom", os.path.join(IMG_DIR, "SprinterCD.iso"), "-bios", "v3.06", ] def check_no_stray_mame(): out = subprocess.run(["pgrep", "-fl", "mame"], capture_output=True, text=True).stdout lines = [l for l in out.splitlines() if "mame.arm" in l or "mame.exe" in l or (" mame " in l)] if lines: print("ОШИБКА: обнаружены запущенные копии MAME — закройте их перед автотестом:") print(out) sys.exit(1) def gen_lua(snap_times, timeout): lines = [ "local start_time = nil", "local snaps_done = {}", "local function log(msg) print('[autotest] ' .. msg) end", "emu.add_machine_reset_notifier(function() start_time = manager.machine.time end)", "local snap_times = {" + ",".join(str(t) for t in snap_times) + "}", "emu.register_periodic(function()", " if not start_time then return end", " local elapsed = (manager.machine.time - start_time).seconds", " for _, st in ipairs(snap_times) do", " if not snaps_done[st] and elapsed >= st then", " snaps_done[st] = true", " manager.machine.video:snapshot()", " log('snap at t=' .. elapsed .. ' (target ' .. st .. ')')", " end", " end", f" if elapsed >= {timeout} then", " log('exiting at t=' .. elapsed)", " manager.machine:exit()", " end", "end)", ] with open(LUA_SCRIPT, "w") as f: f.write("\n".join(lines) + "\n") def main(): ap = argparse.ArgumentParser() ap.add_argument("exe", help="путь к .exe теста") ap.add_argument("--data", nargs="*", default=[], help="доп. файлы на дискету") ap.add_argument("--snap", default="4,6,8,10,12", help="секунды эмулированного времени для скриншотов, через запятую") ap.add_argument("--timeout", type=int, default=13, help="сек. эмуляции до выхода") args = ap.parse_args() check_no_stray_mame() exe_path = os.path.abspath(args.exe) if not os.path.isfile(exe_path): print(f"ОШИБКА: файл не найден: {exe_path}") sys.exit(1) exe_name = os.path.basename(exe_path).upper() os.makedirs(AUTORUN_DIR, exist_ok=True) with open(AUTORUN_BAT, "wb") as f: f.write(f"A:\\{exe_name}\r\n".encode("ascii")) snap_times = [float(x) for x in args.snap.split(",")] gen_lua(snap_times, args.timeout) if os.path.exists(MC_IMG): shutil.copy(MC_IMG, MC_IMG + ".bak") disk_files = [exe_path] + [os.path.abspath(p) for p in args.data] + [AUTORUN_BAT] subprocess.run([sys.executable, os.path.join(MAME_DIR, "make_disk.py"), MC_IMG] + disk_files, check=True, cwd=MAME_DIR) snap_sub = os.path.join(SNAP_DIR, "sprinter") if os.path.isdir(snap_sub): shutil.rmtree(snap_sub) cmd = ["timeout", str(args.timeout + 8), "./mame.arm", "sprinter"] + COMMON_ARGS + [ "-snapshot_directory", SNAP_DIR, "-autoboot_script", LUA_SCRIPT, ] print("Запуск:", " ".join(cmd)) result = subprocess.run(cmd, cwd=MAME_DIR, capture_output=True, text=True) print(result.stdout) if result.returncode not in (0, 124): print("STDERR:", result.stderr) if os.path.isdir(snap_sub): shots = sorted(os.listdir(snap_sub)) print("Скриншоты:") for s in shots: print(" ", os.path.join(snap_sub, s)) else: print("Скриншотов не создано.") if __name__ == "__main__": main()