testkit: модульные тесты под ucsim_z80 + планы покрытия
Обвязка для быстрых тестов 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>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* tcheck.h — микро-фреймворк для тестов, исполняемых под ucsim_z80.
|
||||
*
|
||||
* Почему не printf: тестовый бинарь линкуется БЕЗ Sprinter-libc (там ESTEX,
|
||||
* BIOS, файлы — ничего этого в ucsim нет). Поэтому результат складывается в
|
||||
* структуру tc_result в ОЗУ, а раннер (run_ucsim.py) читает её командой
|
||||
* `dump` после остановки по halt и печатает по-человечески.
|
||||
*
|
||||
* Использование:
|
||||
*
|
||||
* #include "tcheck.h"
|
||||
* TC_TEST(имя_теста) { ... TC_EQ(получено, ожидание); ... }
|
||||
* TC_MAIN(имя_теста, другой_тест, ...)
|
||||
*
|
||||
* Значения сравниваются как int16_t — этого хватает на всё, чем оперирует
|
||||
* движок (координаты, тайлы, кадры, счётчики).
|
||||
*/
|
||||
#ifndef TCHECK_H
|
||||
#define TCHECK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TC_LOGSZ 768
|
||||
#define TC_MAGIC 0x5A5Au
|
||||
|
||||
typedef struct {
|
||||
uint16_t magic; /* TC_MAGIC — раннер убеждается, что нашёл нас */
|
||||
uint16_t total; /* сколько проверок выполнено */
|
||||
uint16_t failed; /* сколько провалилось */
|
||||
uint16_t logn; /* занято в log */
|
||||
uint8_t log[TC_LOGSZ]; /* ASCII-отчёт (только про провалы) */
|
||||
} tc_result_t;
|
||||
|
||||
extern tc_result_t tc_result;
|
||||
|
||||
/* Текущий тест — чтобы в отчёте было видно, где именно упало. */
|
||||
extern const char *tc_curr;
|
||||
|
||||
void tc_begin(const char *name);
|
||||
void tc_check(int16_t got, int16_t want, const char *expr, uint16_t line);
|
||||
void tc_checkp(const void *got, const void *want, const char *expr, uint16_t line);
|
||||
|
||||
/* Основные макросы. __LINE__ хватает для локализации: имя теста уже в
|
||||
* отчёте, а выражение печатается текстом. */
|
||||
#define TC_EQ(got, want) tc_check((int16_t)(got), (int16_t)(want), #got, __LINE__)
|
||||
#define TC_TRUE(cond) tc_check((int16_t)((cond) ? 1 : 0), 1, #cond, __LINE__)
|
||||
#define TC_FALSE(cond) tc_check((int16_t)((cond) ? 1 : 0), 0, #cond, __LINE__)
|
||||
#define TC_PEQ(got, want) tc_checkp((got), (want), #got, __LINE__)
|
||||
|
||||
#define TC_TEST(name) static void name(void)
|
||||
|
||||
/* main() тестового бинаря: перечислить функции тестов. Вариадика через
|
||||
* список вызовов — у SDCC нет проблем с таким раскрытием. */
|
||||
#define TC_RUN(fn) do { tc_begin(#fn); fn(); } while (0)
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user