18 KiB
Автотестирование в MAME
Единый справочник: как запускать программы Sprinter в эмуляторе MAME без участия человека, вводить команды, снимать скриншоты, завершать сессию и анализировать результат. Если нужно что-то про автотесты в MAME — смотреть сюда.
Весь механизм собран в одном инструменте: toolchain/mame_interactive.py.
1. TL;DR
# собрать .exe (пример)
make -C tests/bgitest
# запустить в MAME, снять экран, выйти по таймауту
python3 toolchain/mame_interactive.py tests/bgitest/bgitest.exe \
--mame-home /путь/к/MAME/runtime --snap 12,14 --timeout 16
Инструмент сам:
- соберёт отдельную временную FAT12-дискету с
.exe; - создаст отдельные каталоги MAME для cfg/NVRAM/diff/snapshot;
- запустит MAME с драйвером
sprinter; - в момент
--launch-atнапечатаетa:\bgitest.exe+ Enter (эмулируя нажатия клавиш); выбирайте время после загрузки DSS; - снимет скриншоты в указанные секунды эмулированного времени;
- завершит сессию по таймауту;
- выведет пути к 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 с.
Типовые рецепты
# 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:
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:
-- дамп всех полей клавиатуры в лог
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.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 можно увеличить.