Files
Sprinter-SDCC/docs/mame-autotest.md
T
2026-09-17 10:23:18 +03:00

288 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Автотестирование в 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 \
--mame-home /путь/к/MAME/runtime --snap 12,14 --timeout 16
```
Инструмент сам:
1. соберёт отдельную временную FAT12-дискету с `.exe`;
2. создаст отдельные каталоги MAME для cfg/NVRAM/diff/snapshot;
3. запустит MAME с драйвером `sprinter`;
4. в момент `--launch-at` **напечатает `a:\bgitest.exe` + Enter**
(эмулируя нажатия клавиш); выбирайте время после загрузки DSS;
5. снимет скриншоты в указанные секунды эмулированного времени;
6. завершит сессию по таймауту;
7. выведет пути к PNG-скриншотам.
Скриншоты лежат в `build/mame-autotest/<session>/sprinter/` (`0000.png`,
`0001.png`, …) относительно каталога запуска. Их можно читать визуально — текст с экрана
программно НЕ распознаётся.
---
## 2. Инструмент: `mame_interactive.py`
```
python3 toolchain/mame_interactive.py [exe] [--data f ...] \
[--mame-home DIR] [--mame-bin FILE] [--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` | секунд эмуляции до принудительного выхода. По умолчанию — чуть позже последнего скриншота. |
| `--mame-home DIR` | установленная среда MAME; можно задать переменной `MAME_HOME`. |
| `--mame-bin`, `--mame-rompath`, `--mame-dss-image`, `--mame-system-hdd-image`, `--mame-bios` | выбор отдельных частей профиля; соответствуют `MAME_*` из окружения. |
| `--snapshot-dir DIR` | локальный каталог кадров вместо `build/mame-autotest`. |
**Важно:** все времена — это **секунды эмулированного времени от старта
машины** (не от нажатий, их «нет»). Загрузка 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_HOME` выбирает `sprinter` (новый MAME.HT) либо `mame.arm`
(прежняя установка), `roms/`, DSS-дискету и системный CHD. `MAME_BIN` явно
выбирает бинарник независимо от каталога ресурсов. A: — временная дискета
этого запуска; B: — DSS. При наличии в
установленной среде CD/NeoGS/медиа скрипт подключает их как необязательные
устройства. `-cfg_directory`, `-nvram_directory`, `-diff_directory` и
`-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)
Временный 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` внутри `build/mame-autotest/<session>/sprinter/`.
Каждый запуск создаёт свой каталог и печатает пути к PNG; другие сессии не
затрагиваются.
### 3.5 Завершение сессии
Два рубежа, чтобы MAME гарантированно завершился:
- в Lua: при `elapsed >= timeout``manager.machine:exit()` (чистый
выход);
- снаружи: `subprocess` ожидает с ограничением wall-clock времени, затем
завершает только собственный процесс MAME.
---
## 4. Предпосылки (окружение)
- **MAME**: задайте `MAME_HOME=/путь/к/MAME/runtime` с бинарником,
`roms/`, `IMG/dss171u.img` и `IMG/sp_hdd_sys.chd`. Исходники fork не нужны.
- **Загрузка должна доходить до `C:\>`.** `system.bat` на системном
диске НЕ должен автоматически запускать Flex Navigator или приложение —
иначе мы не попадём на командную строку и ввод уйдёт в чужую программу.
(Это файл на HDD-образе, вне репозитория; правится один раз.)
- **Одновременный запуск нескольких MAME не проверялся.** Автотест создаёт
отдельные A:, state и снимки и при необходимости копирует системный CHD,
но это ещё не доказывает корректность параллельной работы. Особенно не
гарантируется работа нескольких процессов через MCP bridge: выбор процесса,
идентификатор сессии и арбитраж требуют отдельной проверки.
---
## 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** — выбирать `MAME_BIOS` или отдельные
аргументы профиля.
## 10. Архивные тайминги старого MCP-моста
Ниже сохранены измерения прежнего ручного запуска через `run_bridge.sh` и
`bridge_cmd.sh`. Они не задают таймауты нового автотеста и DAP: автотест
использует изолированные носители, а DAP ждёт стабильный prompt DSS в VRAM.
| шаг | пауза ПОСЛЕ шага |
|-----|------------------|
| запустили `run_bridge.sh` | **6 с** → машина поднялась, можно слать `go` |
| `go` (снять с дебаггерного стопа) | **8 с** → DSS догрузился, принимает ввод |
| `keyseq d:{ENTER}` + `keyseq roomtest{ENTER}` | **5 с** → программа уже стартовала |
То есть весь цикл «перезапуск + старт теста» укладывается в ~20 секунд.
Пересобрал HDD-образ (`make hdd`) → MAME **обязан** полный рестарт
(`chdman -f` даёт новый inode; см. memory `mame_hdd_rebuild_restart`):
остановка через `exit` в дебаггере, затем `run_bridge.sh` заново.
## 11. Карта C/asm для отладки
`SRC_DEBUG=1` в app.mk создаёт проверенный пакет исходников и адресов без
изменения EXE в проверенных конфигурациях. Пер-модульный вариант:
`SRC_DEBUG_FILES=helper.c`. Команды карты и результаты живых экспериментов:
[mame-source-debug-status.md](mame-source-debug-status.md); полный план:
[mame-source-debug.md](mame-source-debug.md). Базовый C-attach/where/точки и
чтение простых переменных доступны через `toolchain/sdbg_session.py`.
`sdbg_server.py`, DAP MVP, VS Code launch и development-расширение уже есть;
безопасный restart и MCP C-уровня пока не готовы. `-debugger none`
автоматически продолжает CPU и не подходит для ожидания команд пошаговой
сессии.
Source-debug launcher ждёт до 10-й секунды эмулируемого времени, сохраняет
снимок готового DSS, только затем вооружает entry breakpoint и вводит команду
EXE. Пользовательские точки создаются после проверенного `main`, когда
`_bank_pages` уже заполнена. В обычной конфигурации это даёт 0 попаданий на
адрес entry во время загрузки DSS. Автоматического распознавания приглашения
`C:\\>` пока нет; `launchAt` в VS Code можно увеличить.