61d4255091
Обвязка для быстрых тестов plain-C логики: секунды вместо прогона в MAME, без образа диска. ucsim_z80 идёт в комплекте нашего SDCC — новых зависимостей нет. ПОЧЕМУ ПОД Z80, А НЕ ХОСТОВЫМ GCC. У SDCC z80 int 16 бит, у хоста 32, и расходится это НЕ в объявлениях, а в выражениях: integer promotion повышает операнды до int независимо от того, объявлены они как uint8_t или uint16_t. Перевод кода на фиксированные типы разницу не убирает — убирает только исполнение с z80-семантикой. Побочно проверяется кодогенерация SDCC и модули с inline-asm, которых хостовая сборка не видит в принципе. Устройство: crt0_ucsim.s (SP, зануление, main, halt), tcheck.* (итог в структуру в ОЗУ), run_ucsim.py (гоняет ucsim, дампит tc_result, печатает отчёт), host-tests.mk (общие правила). Вывода через printf нет: тестовый бинарь линкуется без Sprinter-libc. Через ucsim-simif не идём — номера его команд плавают между версиями, halt + dump работают везде. Наборы лежат РЯДОМ с проверяемым кодом, обвязка общая: testkit/t_selftest.c — самопроверка (sizeof(int)==2) applications/PoP/roomtest/tests-host/ — движок PoP Первый содержательный набор — t_geom: сверяет рукописный asm-LCG из pop_geom.c с наивной 32-битной формулой на 128 шагах. Заявка «бит-в-бит как в SDLPoP» до сих пор держалась на комментарии. Тест проверен мутацией: порча эталонной константы даёт красный. Планы дальнейшего покрытия: docs/host-tests-plan.md — libc и libbgi (не начато) applications/PoP/docs/host_tests_plan.md — движок PoP Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
130 lines
8.2 KiB
Markdown
130 lines
8.2 KiB
Markdown
# План: модульные тесты libc и libbgi под ucsim_z80
|
||
|
||
Статус: **не начато**, задача на будущее. Обвязка уже готова и обкатана —
|
||
`testkit/` (см. `testkit/README.md`); первый потребитель —
|
||
`applications/PoP/roomtest/tests-host/`. Этот документ — про то, как накрыть
|
||
тем же способом основной продукт репозитория.
|
||
|
||
## Что это НЕ заменяет
|
||
|
||
В `tests/` уже лежат 62 каталога — это **интеграционные** тесты: одна фича =
|
||
одна программа, которая пакуется на дискету и гоняется в MAME
|
||
(`docs/mame-autotest.md`). Они проверяют, что API работает на живой машине:
|
||
ESTEX, BIOS, диск, экран, тайминги.
|
||
|
||
Модульные тесты их не отменяют, а дополняют с другой стороны:
|
||
|
||
| | `tests/` (MAME) | `tests-host` (ucsim) |
|
||
|---|---|---|
|
||
| что проверяет | работает ли на машине | верна ли логика |
|
||
| граничные случаи | 1–2 на фичу | десятки, дёшево |
|
||
| время прогона | десятки секунд | миллисекунды |
|
||
| ловит | железо, тайминги, банки | арифметику, краевые условия, регрессии |
|
||
|
||
Правило разделения то же, что уже записано для PoP: **что можно проверить
|
||
без железа — проверять в ucsim, MAME оставить железу.**
|
||
|
||
## Три группы модулей
|
||
|
||
### 1. Чистая логика — тестируется как есть, швов не нужно
|
||
|
||
Здесь можно начинать в тот же день, когда задачу возьмут в работу.
|
||
|
||
**libc:**
|
||
|
||
- `time/` — `_tm_is_leap`, `_tm_mdays`, `_tm_month_days`, `_tm_year_days`,
|
||
`mktime`, `gmtime`, `localtime`, `asctime`, `ctime`. Классическая
|
||
календарная арифметика: високосные годы, границы месяцев, переходы через
|
||
год, круговой прогон `mktime(gmtime(t)) == t`. Идеальный первый набор —
|
||
много краевых случаев и ноль зависимостей.
|
||
- `stdio/` — `dec_print`, `hex8/16/32`, `_scanf_core`, `sscanf`. Формат и
|
||
разбор: ширина, знак, переполнение, мусор на входе.
|
||
- `string/`, `stdlib/` — `strlwr`, `strupr`, `max`, `min`.
|
||
|
||
**libbgi/common** (115 модулей, почти всё mode-agnostic):
|
||
|
||
- `_bgi_isqrt`, `_bgi_trig` — сверить с эталонной формулой на всём
|
||
диапазоне, ровно как сделано для LCG в `t_geom` у PoP;
|
||
- `_bgi_lineseg`, `_bgi_styled_line`, `_bgi_poly_edge`, `_bgi_arc_draw` —
|
||
геометрия и отсечение: линия целиком вне окна, по диагонали через угол,
|
||
вырожденная в точку;
|
||
- `_spr_ysort` — порядок сортировки спрайтов;
|
||
- `_bgi_hspan`, `_bgi_fill_span` — заливка: краевые span'ы, нулевая ширина.
|
||
|
||
### 2. Нужны швы — но швы дешёвые
|
||
|
||
**Фейковый ESTEX и BIOS прямо в тестовом crt0.** Оба вызываются через
|
||
`rst #0x10` и `rst #0x08`, то есть через фиксированные векторы в первых
|
||
байтах памяти. В тестовом бинаре эти адреса наши: можно положить туда
|
||
обработчик, который эмулирует крошечную файловую систему в ОЗУ и текстовый
|
||
экран в буфере. Это открывает:
|
||
|
||
- `file/` (32 модуля) и `io/` (21) — `fopen`/`fread`/`fwrite`/`fseek`,
|
||
буферизация (`docs/file-buffering-design.md`), поведение на EOF и
|
||
ошибках, `errno`;
|
||
- `conio/` (38) — вывод в буфер вместо экрана, проверка атрибутов,
|
||
скроллинга, границ окна.
|
||
|
||
Отдельно ценно: **гарды**, которые обязаны быть в обеих сборках. `_fd_guard`
|
||
(девятый `OPEN` вешает DSS) — это ровно тот случай, где нужен тест, а не
|
||
вера в комментарий: открыть восемь, убедиться, что девятый вернул ошибку и
|
||
не дошёл до ESTEX.
|
||
|
||
**Кадровый буфер в ОЗУ для libbgi.** Если рисующие ядра умеют писать в
|
||
обычный буфер, а не только в видеопамять, растеризацию можно проверять
|
||
снимком: нарисовать фигуру, сравнить с эталонным массивом. Начинать с
|
||
маленьких (8×8, 16×16) — эталон читаемый прямо в исходнике.
|
||
|
||
### 3. Только MAME
|
||
|
||
Банки и W-окна, EMM, реальный ESTEX/DSS, клавиатурный трамплин и IM2,
|
||
CBL-звук (`cbl/`), мышь (`mouse/`), `mem/` (это банки и страницы, а не
|
||
куча), тайминги и бюджет кадра, ускоритель.
|
||
|
||
## Обе сборки: fast и safe
|
||
|
||
Библиотеки собираются в двух вариантах (`-D*_NOCHECK` вырезает
|
||
валидацию параметров). Наборы стоит гонять **против обоих**:
|
||
|
||
- в safe — что валидация ловит мусорные аргументы и ставит `errno`;
|
||
- в fast — что вырезание валидации не поменяло поведение на корректных
|
||
входах.
|
||
|
||
Это дешёвая параметризация Makefile (тот же набор, два `OBJS_*`), и она
|
||
пресекает целый класс расхождений между вариантами.
|
||
|
||
## Фазы
|
||
|
||
1. **Календарь и формат.** `time/`, `stdio/`. Нулевые швы, максимальная
|
||
плотность краевых случаев. Цель — обкатать поток работы на libc.
|
||
2. **Геометрия libbgi.** `common/`: isqrt, тригонометрия, отсечение линий,
|
||
рёбра полигонов. Тоже без швов.
|
||
3. **Фейковые ESTEX/BIOS в crt0.** Открывает `file/`, `io/`, `conio/` и
|
||
гарды. Самый крупный кусок работы и самая большая отдача.
|
||
4. **Растр libbgi по снимкам** — если окажется, что ядра можно нацелить на
|
||
буфер в ОЗУ без правок продукта; иначе отложить.
|
||
5. **Параметризация fast/safe** — после того, как наборов станет заметно.
|
||
|
||
## Критерии
|
||
|
||
- Тест не считается написанным, пока не проверен мутацией: сломать
|
||
проверяемое место, убедиться, что набор краснеет. Пустые тесты хуже
|
||
отсутствующих.
|
||
- Каждый закрытый баг libc/libbgi получает регрессионный кейс, если он
|
||
ловится без железа.
|
||
- `make host-tests` остаётся быстрым: если суммарно перевалит за несколько
|
||
секунд, делить на быстрый и полный прогон.
|
||
|
||
## Организация
|
||
|
||
Наборы кладутся рядом с кодом, обвязка общая:
|
||
|
||
```
|
||
libc/tests-host/ наборы по областям: t_time.c, t_stdio.c, …
|
||
libbgi/tests-host/ t_geom.c, t_raster.c, …
|
||
```
|
||
|
||
Каждому — `Makefile` на пять строк (`TESTKIT`, `ENGINE_DIR`, `OBJS_*`,
|
||
`include`), и добавить каталог в `HOST_TEST_DIRS` корневого `Makefile`.
|
||
Подробности — `testkit/README.md`.
|