Files
Sprinter-SDCC/docs/mame-autotest.md
T
2026-09-16 10:01:43 +03:00

18 KiB
Raw Blame History

Автотестирование в 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

Инструмент сам:

  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 с.

Типовые рецепты

# 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 выбирает mame.arm, roms/, DSS-дискету и системный CHD. 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 >= timeoutmanager.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 можно увеличить.