docs: единый справочник по автотестам в MAME; убрать метод AUTORUN.BAT
- docs/mame-autotest.md — исчерпывающий документ: запуск, ввод команд, скриншоты, завершение сессий, анализ, раскладка клавиатуры, все квирки. Одного этого документа достаточно, чтобы работать с MAME в режиме автотестирования. - mame_interactive.py теперь единственный инструмент: авто-запускает exe вводом пути (a:\<exe>+Enter), --step опционален (доп. ввод в программу), умные дефолты снимков/таймаута. - удалён mame_auto_test.py (старый метод через AUTORUN.BAT chainload) и все упоминания AUTORUN.BAT в доках; интерактивный ввод его заменил. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,53 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
mame_interactive.py — скриптовый ИНТЕРАКТИВНЫЙ ввод в DSS через MAME.
|
||||
r"""
|
||||
mame_interactive.py — автотест .exe в MAME без участия человека.
|
||||
|
||||
В отличие от mame_auto_test.py (который запускает .exe через AUTORUN.BAT
|
||||
chainload и НЕ умеет вводить с клавиатуры), этот инструмент печатает
|
||||
произвольный текст в командную строку DSS, эмулируя нажатия клавиш.
|
||||
Полный справочник по автотестированию в MAME: docs/mame-autotest.md.
|
||||
|
||||
КАК это работает (важное открытие 2026-07-08, см.
|
||||
memory/mame_autotest_autorun):
|
||||
у Sprinter в MAME ДВЕ клавиатуры. `IO_LINE0..7` — легаси ZX-матрица
|
||||
(порт 0xFE), которую DSS для командной строки НЕ читает — именно в неё
|
||||
безрезультатно били natkeyboard/-autoboot_command/set_value раньше.
|
||||
Настоящая клавиатура DSS — AT/PS-2 устройство `:kbd:ms_naturl`,
|
||||
подключённое последовательно к SIO Z84C015. Если через Lua
|
||||
`ioport.ports[":kbd:ms_naturl:PX.Y"].fields[...]:set_value(1/0)`
|
||||
нажимать/отпускать поля ЭТОГО устройства, at_keyboard сам генерит
|
||||
scancode'ы → SIO → DSS. Проверено: `dir`<Enter> выполняется.
|
||||
Инструмент запускает Sprinter в MAME, вводит команды в DSS эмуляцией
|
||||
нажатий клавиш, снимает скриншоты в заданные моменты и завершает сессию
|
||||
по таймауту. Ввод идёт в НАСТОЯЩУЮ клавиатуру DSS — AT/PS-2 устройство
|
||||
`:kbd:ms_naturl` (подключено последовательно к SIO Z84C015): через Lua
|
||||
`ioport.ports[":kbd:ms_naturl:PX.Y"].fields[...]:set_value(1/0)` жмём/
|
||||
отпускаем поля, at_keyboard сам генерит scancode'ы → SIO → DSS.
|
||||
(ZX-матрица `IO_LINE0..7` и natkeyboard/-autoboot_command командную
|
||||
строку DSS НЕ достают — не использовать.)
|
||||
|
||||
Тонкости, на которых уже наступали:
|
||||
- `attotime.seconds` — ЦЕЛОЕ (отбрасывает дробь); для субсекундного
|
||||
тайминга берём `seconds + attoseconds/1e18`.
|
||||
- клавишу держать коротко (~0.06с) — иначе срабатывает автоповтор
|
||||
(typematic) и вместо `d` получается `dddddd`.
|
||||
- между символами ~0.14с, чтобы scancode'ы не сливались.
|
||||
- дискета БЕЗ AUTORUN.BAT → загрузка встаёт на приглашении `C:\>`
|
||||
(с AUTORUN.BAT сразу ушла бы в автозапуск .exe).
|
||||
Тонкости (детали — в docs/mame-autotest.md):
|
||||
- attotime.seconds — ЦЕЛОЕ; субсекундный тайминг = seconds +
|
||||
attoseconds/1e18.
|
||||
- клавишу держать коротко (~0.06с), между символами ~0.14с — иначе
|
||||
автоповтор (typematic) и слипание scancode'ов.
|
||||
- загрузка должна встать на приглашении C:\> (system.bat не должен
|
||||
автозапускать Flex Navigator/приложение).
|
||||
- перед запуском проверяем, что нет висящих копий MAME.
|
||||
|
||||
Использование:
|
||||
python3 toolchain/mame_interactive.py [exe] [--data f ...] \\
|
||||
--step "T:TEXT" [--step "T:TEXT" ...] \\
|
||||
[--snap t1,t2,...] [--timeout N]
|
||||
python3 toolchain/mame_interactive.py [exe] [--data f ...] \
|
||||
[--launch-at T] [--step "T:TEXT" ...] [--snap t1,t2,...] [--timeout N]
|
||||
|
||||
exe / --data — файлы, кладущиеся на дискету A: (опционально;
|
||||
чтобы можно было напечатать, напр., a:\\name.exe).
|
||||
--step "T:TEXT" — в момент T секунд (эмуляции) напечатать TEXT.
|
||||
Можно указывать несколько раз (диалог с программой).
|
||||
В TEXT поддержаны \\n (Enter) и \\t (Tab); символы
|
||||
с Shift (заглавные, ! @ : \\ и т.п.) — автоматически.
|
||||
--snap — секунды для скриншотов (по умолчанию — авто).
|
||||
--timeout — секунд эмуляции до выхода.
|
||||
exe — .exe, который кладётся на дискету A: и АВТОМАТИЧЕСКИ
|
||||
запускается (печатается a:\<EXE> + Enter в момент
|
||||
--launch-at). Без exe работаем на голой строке.
|
||||
--data f ... — доп. файлы на дискету A:.
|
||||
--launch-at T — секунда, когда печатается запуск exe (по умолч. 8).
|
||||
--step "T:TEXT" — в момент T сек напечатать TEXT (можно много раз —
|
||||
диалог с уже запущенной программой). В TEXT: \n =
|
||||
Enter, \t = Tab; заглавные/символы с Shift — авто.
|
||||
--snap — секунды для скриншотов (по умолчанию — авто).
|
||||
--timeout — секунд эмуляции до выхода.
|
||||
|
||||
Пример (набрать DIR на приглашении и снять экран):
|
||||
python3 toolchain/mame_interactive.py --step "8:dir\\n" \\
|
||||
Примеры:
|
||||
# запустить тест и снять экран
|
||||
python3 toolchain/mame_interactive.py tests/rt_test/rt_test.exe \
|
||||
--snap 12,14 --timeout 16
|
||||
|
||||
# набрать команду на голой командной строке
|
||||
python3 toolchain/mame_interactive.py --step "8:dir\n" \
|
||||
--snap 10,11 --timeout 12
|
||||
|
||||
Пример (запустить .exe вводом пути и дождаться вывода):
|
||||
python3 toolchain/mame_interactive.py tests/rt_test/rt_test.exe \\
|
||||
--step "8:a:\\rt_test.exe\\n" --snap 12,14 --timeout 16
|
||||
# запустить программу и ответить на её ввод
|
||||
python3 toolchain/mame_interactive.py tests/menu/menu.exe \
|
||||
--step "13:2\n" --snap 15 --timeout 17
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -75,7 +76,7 @@ COMMON_ARGS = [
|
||||
]
|
||||
|
||||
# --- Раскладка AT-клавиатуры :kbd:ms_naturl (tag, mask) ---
|
||||
# Снято дампом ioport-полей в MAME (см. _probe_kbd.lua).
|
||||
# Снято дампом ioport-полей в MAME (см. docs/mame-autotest.md).
|
||||
KB = ":kbd:ms_naturl:"
|
||||
PHYS = {
|
||||
"a": (KB+"P1.6", 0x04), "b": (KB+"P1.5", 0x10), "c": (KB+"P1.4", 0x08),
|
||||
@@ -217,46 +218,56 @@ def parse_step(s):
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("exe", nargs="?", help="опц. .exe на дискету A:")
|
||||
ap.add_argument("exe", nargs="?", help=".exe на дискету A: (авто-запуск)")
|
||||
ap.add_argument("--data", nargs="*", default=[], help="доп. файлы на дискету")
|
||||
ap.add_argument("--step", action="append", type=parse_step, required=True,
|
||||
help="'T:TEXT' — в момент T сек напечатать TEXT (можно несколько)")
|
||||
ap.add_argument("--launch-at", type=float, default=8.0,
|
||||
help="сек, когда печатается запуск exe (по умолч. 8)")
|
||||
ap.add_argument("--step", action="append", default=[], type=parse_step,
|
||||
help="'T:TEXT' — в момент T сек напечатать TEXT (можно много)")
|
||||
ap.add_argument("--snap", default=None, help="секунды для скриншотов, через запятую")
|
||||
ap.add_argument("--timeout", type=float, default=None, help="сек эмуляции до выхода")
|
||||
args = ap.parse_args()
|
||||
|
||||
check_no_stray_mame()
|
||||
|
||||
events = build_events(args.step)
|
||||
last_ev_t = events[-1][0] if events else max(t for t, _ in args.step)
|
||||
# Собираем шаги ввода: авто-запуск exe (печать пути) + пользовательские.
|
||||
steps = list(args.step)
|
||||
exe_path = None
|
||||
if args.exe:
|
||||
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).lower()
|
||||
steps.append((args.launch_at, f"a:\\{exe_name}\n"))
|
||||
if not steps:
|
||||
print("ОШИБКА: укажите exe для запуска или хотя бы один --step")
|
||||
sys.exit(1)
|
||||
|
||||
events = build_events(steps)
|
||||
last_ev_t = events[-1][0] if events else max(t for t, _ in steps)
|
||||
|
||||
if args.snap:
|
||||
snap_times = [float(x) for x in args.snap.split(",")]
|
||||
else:
|
||||
snap_times = [last_ev_t + 1.5, last_ev_t + 3.0]
|
||||
# exe: даём программе доработать (снимаем через 4 и 6 с после ввода);
|
||||
# чистый ввод: сразу после последнего шага.
|
||||
pad = 4.0 if exe_path else 1.5
|
||||
snap_times = [last_ev_t + pad, last_ev_t + pad + 2.0]
|
||||
timeout = args.timeout if args.timeout is not None else (max(snap_times) + 1.5)
|
||||
|
||||
gen_lua(events, snap_times, timeout)
|
||||
|
||||
# Дискета БЕЗ AUTORUN.BAT → приглашение C:\>
|
||||
# Дискета БЕЗ авто-запускаемого bat → приглашение C:\>.
|
||||
disk_files = []
|
||||
if args.exe:
|
||||
p = os.path.abspath(args.exe)
|
||||
if not os.path.isfile(p):
|
||||
print(f"ОШИБКА: не найден {p}")
|
||||
sys.exit(1)
|
||||
disk_files.append(p)
|
||||
if exe_path:
|
||||
disk_files.append(exe_path)
|
||||
disk_files += [os.path.abspath(p) for p in args.data]
|
||||
|
||||
if os.path.exists(MC_IMG):
|
||||
shutil.copy(MC_IMG, MC_IMG + ".bak")
|
||||
if disk_files:
|
||||
subprocess.run([sys.executable, os.path.join(MAME_DIR, "make_disk.py"), MC_IMG]
|
||||
+ disk_files, check=True, cwd=MAME_DIR)
|
||||
else:
|
||||
# пустая дискета без AUTORUN.BAT
|
||||
subprocess.run([sys.executable, os.path.join(MAME_DIR, "make_disk.py"), MC_IMG],
|
||||
check=True, cwd=MAME_DIR)
|
||||
make_disk_cmd = [sys.executable, os.path.join(MAME_DIR, "make_disk.py"), MC_IMG]
|
||||
subprocess.run(make_disk_cmd + disk_files, check=True, cwd=MAME_DIR)
|
||||
|
||||
snap_sub = os.path.join(SNAP_DIR, "sprinter")
|
||||
if os.path.isdir(snap_sub):
|
||||
@@ -266,8 +277,8 @@ def main():
|
||||
"-snapshot_directory", SNAP_DIR,
|
||||
"-autoboot_script", LUA_SCRIPT,
|
||||
]
|
||||
print("Ввод по шагам:", [(t, repr(txt)) for t, txt in args.step])
|
||||
print("Запуск:", " ".join(cmd))
|
||||
print("Ввод по шагам:", [(round(t, 2), repr(txt)) for t, txt in steps])
|
||||
print("Скриншоты (сек):", snap_times, " таймаут:", timeout)
|
||||
result = subprocess.run(cmd, cwd=MAME_DIR, capture_output=True, text=True)
|
||||
for line in result.stdout.splitlines():
|
||||
if "[interactive]" in line:
|
||||
|
||||
Reference in New Issue
Block a user