toolchain: автотест .exe в MAME без участия человека
AUTORUN.BAT chainload из system.bat (правится пользователем один раз) + mame_auto_test.py: кладёт .exe и сгенерированный AUTORUN.BAT на дискету, гоняет MAME с Lua-таймингом (register_periodic + manager.machine.time) для скриншотов и выхода по таймауту. Natural keyboard (Lua natkeyboard:post/post_coded, -autoboot_command) и прямая инъекция через ioport.fields[...]:set_value() не работают на этом драйвере — перепробовано разными способами; AUTORUN.BAT chainload оказался единственным надёжным путём запуска без участия человека. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
#!/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 <path-to-exe> [--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()
|
||||
Reference in New Issue
Block a user