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:
@@ -0,0 +1,242 @@
|
||||
# Автотестирование в MAME
|
||||
|
||||
Единый справочник: как запускать программы Sprinter в эмуляторе MAME
|
||||
**без участия человека**, вводить команды, снимать скриншоты, завершать
|
||||
сессию и анализировать результат. Если нужно что-то про автотесты в
|
||||
MAME — смотреть сюда.
|
||||
|
||||
Весь механизм собран в одном инструменте: **`toolchain/mame_interactive.py`**.
|
||||
|
||||
---
|
||||
|
||||
## 1. TL;DR
|
||||
|
||||
```bash
|
||||
# собрать .exe (пример)
|
||||
make -C tests/bgitest
|
||||
|
||||
# запустить в MAME, снять экран, выйти по таймауту
|
||||
python3 toolchain/mame_interactive.py tests/bgitest/bgitest.exe \
|
||||
--snap 12,14 --timeout 16
|
||||
```
|
||||
|
||||
Инструмент сам:
|
||||
1. проверит, что нет висящих копий MAME;
|
||||
2. положит `.exe` на дискету A: (`mame/v306/IMG/mc.img`);
|
||||
3. запустит MAME с драйвером `sprinter`;
|
||||
4. дождётся приглашения `C:\>` и **напечатает `a:\bgitest.exe` + Enter**
|
||||
(эмулируя нажатия клавиш);
|
||||
5. снимет скриншоты в указанные секунды эмулированного времени;
|
||||
6. завершит сессию по таймауту;
|
||||
7. выведет пути к PNG-скриншотам.
|
||||
|
||||
Скриншоты лежат в `mame/v306/snap_auto/sprinter/` (`0000.png`, `0001.png`,
|
||||
…). Их читает Claude визуально (инструментом Read) — текст с экрана
|
||||
программно НЕ распознаётся.
|
||||
|
||||
---
|
||||
|
||||
## 2. Инструмент: `mame_interactive.py`
|
||||
|
||||
```
|
||||
python3 toolchain/mame_interactive.py [exe] [--data f ...] \
|
||||
[--launch-at T] [--step "T:TEXT" ...] [--snap t1,t2,...] [--timeout N]
|
||||
```
|
||||
|
||||
| Аргумент | Назначение |
|
||||
|----------|-----------|
|
||||
| `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 t1,t2,...` | секунды эмуляции для скриншотов. По умолчанию: `launch_at+4` и `+6` (для `exe`), либо сразу после последнего ввода. |
|
||||
| `--timeout N` | секунд эмуляции до принудительного выхода. По умолчанию — чуть позже последнего скриншота. |
|
||||
|
||||
**Важно:** все времена — это **секунды эмулированного времени от старта
|
||||
машины** (не от нажатий, их «нет»). Загрузка DSS до `C:\>` занимает
|
||||
~7 секунд, поэтому `--launch-at 8` и скриншоты с ~12 с.
|
||||
|
||||
### Типовые рецепты
|
||||
|
||||
```bash
|
||||
# 1. Запустить тест и снять результат (самый частый случай)
|
||||
python3 toolchain/mame_interactive.py tests/rt_test/rt_test.exe \
|
||||
--snap 12,14 --timeout 16
|
||||
|
||||
# 2. Набрать команду на голой командной строке (без exe)
|
||||
python3 toolchain/mame_interactive.py --step "8:dir\n" \
|
||||
--snap 10,11 --timeout 12
|
||||
|
||||
# 3. Запустить программу и ответить на её ввод (например, выбор пункта меню)
|
||||
python3 toolchain/mame_interactive.py tests/menu/menu.exe \
|
||||
--step "13:2\n" --snap 15 --timeout 17
|
||||
|
||||
# 4. Тест с файлом-данными на дискете
|
||||
python3 toolchain/mame_interactive.py examples/mdview2/mdview2.exe \
|
||||
--data doc.md --step "13:mdview2 doc.md\n" --snap 16 --timeout 18
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Как это работает внутри
|
||||
|
||||
### 3.1 Запуск MAME
|
||||
|
||||
Нативный `mame.arm` (arm64) в `mame/v306/`, драйвер `sprinter`, BIOS
|
||||
v3.06. Полный набор аргументов зашит в `COMMON_ARGS` внутри скрипта:
|
||||
две дискеты (A: наш `mc.img`, B: DSS 1.71u), два HDD-образа (система +
|
||||
медиа), CD-ROM, ZX-Bus карта NeoGS, `-video opengl -window`,
|
||||
`-snapshot_directory`, `-autoboot_script <сгенерированный .lua>`.
|
||||
|
||||
### 3.2 Ввод с клавиатуры — ключевой момент
|
||||
|
||||
У Sprinter в MAME **две** клавиатуры:
|
||||
|
||||
- `IO_LINE0..7` — легаси ZX-Spectrum-матрица (порт `0xFE`). DSS её для
|
||||
командной строки **НЕ читает**.
|
||||
- `root:kbd:ms_naturl` — **настоящая AT/PS-2 клавиатура**, подключённая
|
||||
последовательно к SIO Z84C015 (`sprinter.cpp:2037`). Именно её DSS
|
||||
читает как поток scancode'ов.
|
||||
|
||||
Поэтому **не работают** (проверено многократно): `natkeyboard:post`,
|
||||
`-autoboot_command`, а также `set_value` по полям `:IO_LINE*`. Всё это
|
||||
бьёт в ZX-матрицу, которую DSS игнорирует.
|
||||
|
||||
**Работает** — прямое управление полями AT-клавиатуры из Lua:
|
||||
```lua
|
||||
manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(1) -- нажать
|
||||
... подождать ~0.06с ...
|
||||
manager.machine.ioport.ports[":kbd:ms_naturl:P1.4"].fields["D"]:set_value(0) -- отпустить
|
||||
```
|
||||
`at_keyboard` сам сгенерит make/break scancode'ы → SIO → DSS.
|
||||
|
||||
Скрипт хранит раскладку `char → (порт, битовая маска)` (словарь `PHYS` +
|
||||
`SHIFTED` для Shift-символов) и разворачивает строку в список
|
||||
timed-событий `(время, порт, маска, значение)`. Backslash `\` в
|
||||
AT-клавиатуре есть (поле `P2.1`/0x4) — путь `a:\name.exe` вводится
|
||||
полностью.
|
||||
|
||||
### 3.3 Тайминг (Lua)
|
||||
|
||||
Генерируется `_interactive_gen.lua`, который через
|
||||
`emu.register_periodic` на каждом кадре сверяет **эмулированное время** и
|
||||
проигрывает события ввода, снимает скриншоты и завершает сессию.
|
||||
|
||||
Время берётся как `t.seconds + t.attoseconds/1e18`, потому что
|
||||
`attotime.seconds` — **целое** (дробную часть отбрасывает); если считать
|
||||
по нему, все события схлопнутся в 1-секундную сетку.
|
||||
|
||||
Старт отсчёта — `emu.add_machine_reset_notifier` (НЕ `emu.register_start`
|
||||
— он deprecated).
|
||||
|
||||
### 3.4 Скриншоты
|
||||
|
||||
`manager.machine.video:snapshot()` пишет PNG в каталог из
|
||||
`-snapshot_directory` (`mame/v306/snap_auto/sprinter/`). Скрипт перед
|
||||
запуском чистит этот каталог, потом печатает пути к готовым файлам.
|
||||
Claude открывает их инструментом Read (визуальный анализ).
|
||||
|
||||
### 3.5 Завершение сессии
|
||||
|
||||
Два рубежа, чтобы MAME гарантированно не «завис»:
|
||||
- в Lua: при `elapsed >= timeout` → `manager.machine:exit()` (чистый
|
||||
выход);
|
||||
- снаружи: shell-обёртка `timeout <N+8> ./mame.arm …` как страховка.
|
||||
|
||||
---
|
||||
|
||||
## 4. Предпосылки (окружение)
|
||||
|
||||
- **MAME**: `mame/v306/mame.arm` + образы в `mame/v306/IMG/`
|
||||
(`mc.img` — наш перезаписываемый A:, `dss171u.img`, HDD `.chd`, CD
|
||||
`.iso`). Каталог `mame/` целиком в `.gitignore` — поэтому инструмент
|
||||
живёт в `toolchain/`, а не рядом с MAME.
|
||||
- **Загрузка должна доходить до `C:\>`.** `system.bat` на системном
|
||||
диске НЕ должен автоматически запускать Flex Navigator или приложение —
|
||||
иначе мы не попадём на командную строку и ввод уйдёт в чужую программу.
|
||||
(Это файл на HDD-образе, вне репозитория; правится один раз.)
|
||||
- **Нет висящих копий MAME.** Несколько одновременных инстансов пишут в
|
||||
один `mc.img` и дают недостоверный результат. Скрипт проверяет это сам
|
||||
(`pgrep`), но при ручных запусках MAME — проверяйте `ps aux | grep mame`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Как выбирать времена
|
||||
|
||||
- **Загрузка до `C:\>`:** ~7 секунд → `--launch-at 8` безопасно.
|
||||
- **Набор пути `a:\name.exe`:** ~13 символов × 0.14с ≈ 1.8с → команда
|
||||
уходит около 9.8с, программа стартует ~10с.
|
||||
- **Скриншот:** давайте программе дорисоваться. Быстрая программа —
|
||||
снимать с ~12с; если рисует долго/по частям, снимайте несколько кадров
|
||||
(`--snap 12,16,20`) и смотрите, где картинка «дособралась».
|
||||
- **Диалог с программой (`--step`):** времена шагов ставьте ПОСЛЕ старта
|
||||
программы (например, запуск на 8с, ответ на ввод на 13–15с).
|
||||
|
||||
---
|
||||
|
||||
## 6. Анализ результата
|
||||
|
||||
- Скриншоты — **единственный** способ проверки: программного чтения
|
||||
текстового/графического VRAM нет, OCR нет. Claude читает PNG глазами
|
||||
(инструмент Read с картинкой).
|
||||
- Лог MAME фильтруется по строкам `[interactive]` (моменты снимков и
|
||||
выхода) — видно, в какие секунды сделаны кадры.
|
||||
- Если картинка «не дособралась» — снять более поздний кадр (увеличить
|
||||
`--snap`/`--timeout`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Раскладка клавиатуры (справочно)
|
||||
|
||||
Раскладка снята дампом ioport-полей `:kbd:ms_naturl:*` живой машины.
|
||||
Она зашита в `PHYS`/`SHIFTED` внутри `mame_interactive.py`. Поддержаны:
|
||||
буквы (a–z, A–Z через Shift), цифры, пробел, Enter (`\n`), Tab (`\t`),
|
||||
и символы ``- = [ ] \ ; ' , . / ` `` плюс их Shift-версии
|
||||
`! @ # $ % ^ & * ( ) _ + { } | : " < > ? ~`.
|
||||
|
||||
Если понадобится клавиша вне списка — снять её поле дампом (пример
|
||||
Lua-пробы ниже) и добавить в `PHYS`:
|
||||
|
||||
```lua
|
||||
-- дамп всех полей клавиатуры в лог
|
||||
for tag, port in pairs(manager.machine.ioport.ports) do
|
||||
if tostring(tag):find("kbd") then
|
||||
for fname, field in pairs(port.fields) do
|
||||
print(string.format("%s mask=0x%x %q", tag, field.mask, fname))
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Квирки и грабли (все, на которые уже наступали)
|
||||
|
||||
- **`attotime.seconds` — целое.** Субсекундный тайминг только через
|
||||
`seconds + attoseconds/1e18`.
|
||||
- **Автоповтор (typematic).** Клавишу держать коротко (~0.06с). Если
|
||||
держать ~1с — `d` превратится в `dddddd`.
|
||||
- **Слипание scancode'ов.** Между символами ~0.14с.
|
||||
- **Не та клавиатура.** Ввод — только в `:kbd:ms_naturl`, НЕ в
|
||||
`:IO_LINE*`, НЕ через `natkeyboard`/`-autoboot_command`.
|
||||
- **Загрузка мимо `C:\>`.** Если `system.bat` что-то автозапускает —
|
||||
ввод уходит в чужую программу; вернуть чистую командную строку.
|
||||
- **Висящие копии MAME.** Всегда проверять перед запуском.
|
||||
- **macOS-специфика (справочно):** известный баг MAME
|
||||
(mamedev/mame#10612 — потеря ввода в fullscreen при движении мыши на
|
||||
старте) к нам НЕ относится: работаем в `-window`, ввод скриптовый.
|
||||
|
||||
---
|
||||
|
||||
## 9. На будущее (заметки, ещё не в инструменте)
|
||||
|
||||
- **Быстрый накопитель.** Тестам, которым важна скорость диска (напр.
|
||||
потоковое чтение), имеет смысл копировать файлы с медленной дискеты A:
|
||||
на HDD `C:\TEMP` перед запуском.
|
||||
- **Видео+звук.** MAME умеет писать AVI (`-aviwrite`) — для тестов с
|
||||
анимацией/звуком, где скриншотов мало. Пока не подключено к скрипту.
|
||||
- **Ручная отладка ввода.** Запуск MAME с `-console` даёт интерактивный
|
||||
Lua-REPL — удобно нащупывать поля/тайминги вживую перед скриптованием.
|
||||
- **Второй видеорежим/варианты BIOS** — при необходимости менять
|
||||
`COMMON_ARGS`.
|
||||
```
|
||||
Reference in New Issue
Block a user