From 855eabfb32eb4c11fdd866b83acac27baed04b79 Mon Sep 17 00:00:00 2001 From: deexsed <95432880+deexsed@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:57:36 +0300 Subject: [PATCH] test: Harden host checks and CI codegen sync Replace assert with hostFail under -UNDEBUG, cover dirty/settings clamp rules, validate all boards, and fail CI when i18n/pack drift. --- .github/workflows/host-tests.yml | 17 +++++++++++- CONTRIBUTING.md | 5 ++-- components/flint/app/app.cpp | 2 +- components/flint/config/config.h | 2 +- components/flint/day_mode/day_mode.cpp | 4 --- components/flint/day_mode/day_mode.h | 3 +- components/flint/faces/FaceContext.h | 2 +- components/flint/settings/settings.cpp | 18 +++++++----- components/flint/settings/settings_clamp.h | 32 ++++++++++++++++++++++ components/flint/shell/shell_settings.h | 4 +-- components/flint/ui/ui.cpp | 20 ++++++-------- components/flint/ui/ui_dirty_rules.h | 22 +++++++++++++++ docs/ARCHITECTURE.md | 24 +++++++++++++--- docs/MODULES.md | 19 ++++++++----- tests/host/Makefile | 16 +++++++++-- tests/host/host_check.h | 17 ++++++++++++ tests/host/test_date_fmt.cpp | 10 +++---- tests/host/test_day_mode.cpp | 18 ++++++------ tests/host/test_settings_clamp.cpp | 28 +++++++++++++++++++ tests/host/test_ui_dirty.cpp | 25 +++++++++++++++++ tools/tests/test_gen_tools.py | 26 +++++++++++------- 21 files changed, 243 insertions(+), 71 deletions(-) create mode 100644 components/flint/settings/settings_clamp.h create mode 100644 components/flint/ui/ui_dirty_rules.h create mode 100644 tests/host/host_check.h create mode 100644 tests/host/test_settings_clamp.cpp create mode 100644 tests/host/test_ui_dirty.cpp diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml index 94c5031..e2bf850 100644 --- a/.github/workflows/host-tests.yml +++ b/.github/workflows/host-tests.yml @@ -5,6 +5,9 @@ on: push: branches: [main] +permissions: + contents: read + concurrency: group: host-tests-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -16,8 +19,20 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Host C++ (date_fmt, day mode) + - name: Host C++ (date_fmt, day mode, dirty, settings) + env: + CXX: g++ run: make -C tests/host run - name: Tools gen_* smoke run: python3 -m unittest discover -s tools/tests -v + + - name: Codegen artifacts in sync + run: | + set -euo pipefail + python3 tools/gen_i18n_catalog.py + git diff --exit-code -- components/flint/i18n/i18n_catalog.inc + python3 tools/gen_asset_pack.py + git diff --exit-code -- assets/fs_root/core.flintpack + # Board outputs are gitignored; regen must succeed for the default board. + python3 tools/gen_board_catalog.py --quiet diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ae40e97..4e46b9f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,14 +47,15 @@ Interactive picker: `./flint_tools`. Full flags and examples: [`tools/README.md` Pure contracts (no ESP-IDF / device). From repo root: ```bash -# date_fmt + dayModeFromHour / WAITING profile (asserts + g++) +# date_fmt / day-mode / dirty rules / settings clamp (explicit fail, -UNDEBUG) make -C tests/host run # tools/gen_* schema / id smoke (stdlib unittest; pytest also works if installed) python3 -m unittest discover -s tools/tests -v ``` -CI runs the same commands on every PR via `.github/workflows/host-tests.yml`. +CI runs the same commands on every PR via `.github/workflows/host-tests.yml`, plus +regen of i18n/pack with `git diff --exit-code` and a quiet board catalog codegen. Do not add on-target Unity or heavy CI matrices unless agreed separately. diff --git a/components/flint/app/app.cpp b/components/flint/app/app.cpp index 6bd1203..ed42c11 100644 --- a/components/flint/app/app.cpp +++ b/components/flint/app/app.cpp @@ -138,7 +138,7 @@ static void debugHeartbeat(const struct tm* tiOpt) { const char* modeName = "-"; if (ti) { snprintf(tbuf, sizeof(tbuf), "%02d:%02d:%02d", ti->tm_hour, ti->tm_min, ti->tm_sec); - modeName = dayModeNameRu(dayModeFromHour(ti->tm_hour)); + modeName = dayModeName(dayModeFromHour(ti->tm_hour)); } LOGI("hb", "wifi=%d time=%s%s mode=%s heap=%u dirty=0x%x", diff --git a/components/flint/config/config.h b/components/flint/config/config.h index 302d631..2fddeb1 100644 --- a/components/flint/config/config.h +++ b/components/flint/config/config.h @@ -33,5 +33,5 @@ static const uint32_t NTP_RESYNC_MS = 24UL * 60UL * 60UL * 1000UL; static const uint32_t WIFI_RETRY_MS = 15000UL; static const uint32_t WIFI_CONNECT_TIMEOUT_MS = 20000UL; -// Factory default when NVS has no active_face yet (picker / settings override). +// Factory default when ui.cfg has no active_face yet (picker / settings override). inline constexpr const char* ACTIVE_FACE_ID = "digital"; diff --git a/components/flint/day_mode/day_mode.cpp b/components/flint/day_mode/day_mode.cpp index 5755566..3e31e16 100644 --- a/components/flint/day_mode/day_mode.cpp +++ b/components/flint/day_mode/day_mode.cpp @@ -6,10 +6,6 @@ const char* dayModeName(DayMode mode) { return flintTrDayMode(static_cast(mode)); } -const char* dayModeNameRu(DayMode mode) { - return dayModeName(mode); -} - static void fillChromeRoles(UiPalette& p) { p.divider = colorLerp(p.bgTop, p.accent, 70); p.selectionBg = colorLerp(p.bgTop, p.panel, 200); diff --git a/components/flint/day_mode/day_mode.h b/components/flint/day_mode/day_mode.h index dcfb15d..4175221 100644 --- a/components/flint/day_mode/day_mode.h +++ b/components/flint/day_mode/day_mode.h @@ -5,8 +5,7 @@ #include -const char* dayModeNameRu(DayMode mode); // legacy alias → flintTrDayMode -const char* dayModeName(DayMode mode); // localized +const char* dayModeName(DayMode mode); // localized UiPalette dayModeBuildPalette(DayMode mode); diff --git a/components/flint/faces/FaceContext.h b/components/flint/faces/FaceContext.h index e228bfa..a83cf55 100644 --- a/components/flint/faces/FaceContext.h +++ b/components/flint/faces/FaceContext.h @@ -17,7 +17,7 @@ struct FaceContext { bool wifiConnected; uint8_t wifiBars; // 0 = off, 1–3 = RSSI strength while connected - // Resolved prefs (shell/settings) — faces must not read NVS themselves. + // Resolved prefs (shell/settings → /cfg/ui.cfg) — faces must not read cfg/NVS. bool showSeconds; bool hour12; // false = 24h, true = 12h + AM/PM uint8_t dateFormat; // SettingsDateFormat diff --git a/components/flint/settings/settings.cpp b/components/flint/settings/settings.cpp index e9f2bae..77e6fd6 100644 --- a/components/flint/settings/settings.cpp +++ b/components/flint/settings/settings.cpp @@ -3,6 +3,7 @@ #include "config/debug.h" #include "i18n/i18n.h" #include "settings/cfg_fs.h" +#include "settings/settings_clamp.h" #include "time/tz_db.h" #include @@ -28,6 +29,11 @@ static void settingsApplyDefaults() { } static void settingsApplyAfterLoad() { + gSettings.showSeconds = settingsClampShowSeconds(gSettings.showSeconds); + gSettings.lang = settingsClampLang(gSettings.lang); + gSettings.hourFormat = settingsClampHourFormat(gSettings.hourFormat); + gSettings.dateFormat = settingsClampDateFormat(gSettings.dateFormat); + gSettings.dateSep = settingsClampDateSep(gSettings.dateSep); flintI18nSetLang(gSettings.lang); clockTimeApplyTz(gSettings.tzId); } @@ -253,30 +259,28 @@ bool settingsSetActiveFace(const char* id) { } void settingsSetShowSeconds(uint8_t mode) { - gSettings.showSeconds = mode; + gSettings.showSeconds = settingsClampShowSeconds(mode); settingsSave(); } void settingsSetLang(uint8_t lang) { - gSettings.lang = (lang == SETTINGS_LANG_EN) ? SETTINGS_LANG_EN : SETTINGS_LANG_RU; + gSettings.lang = settingsClampLang(lang); flintI18nSetLang(gSettings.lang); settingsSave(); } void settingsSetHourFormat(uint8_t fmt) { - gSettings.hourFormat = (fmt == SETTINGS_HOUR_12) ? SETTINGS_HOUR_12 : SETTINGS_HOUR_24; + gSettings.hourFormat = settingsClampHourFormat(fmt); settingsSave(); } void settingsSetDateFormat(uint8_t fmt) { - gSettings.dateFormat = - (fmt < SETTINGS_DATE_FORMAT_COUNT) ? fmt : static_cast(SETTINGS_DATE_DMY4); + gSettings.dateFormat = settingsClampDateFormat(fmt); settingsSave(); } void settingsSetDateSep(uint8_t sep) { - gSettings.dateSep = - (sep < SETTINGS_DATE_SEP_COUNT) ? sep : static_cast(SETTINGS_DATE_SEP_DOT); + gSettings.dateSep = settingsClampDateSep(sep); settingsSave(); } diff --git a/components/flint/settings/settings_clamp.h b/components/flint/settings/settings_clamp.h new file mode 100644 index 0000000..525afb8 --- /dev/null +++ b/components/flint/settings/settings_clamp.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "settings/settings.h" + +// Pure clamp helpers for prefs enums (host-testable; used by settings setters). + +inline uint8_t settingsClampHourFormat(uint8_t fmt) { + return (fmt == SETTINGS_HOUR_12) ? SETTINGS_HOUR_12 : SETTINGS_HOUR_24; +} + +inline uint8_t settingsClampDateFormat(uint8_t fmt) { + return (fmt < SETTINGS_DATE_FORMAT_COUNT) ? fmt + : static_cast(SETTINGS_DATE_DMY4); +} + +inline uint8_t settingsClampDateSep(uint8_t sep) { + return (sep < SETTINGS_DATE_SEP_COUNT) ? sep + : static_cast(SETTINGS_DATE_SEP_DOT); +} + +inline uint8_t settingsClampLang(uint8_t lang) { + return (lang == SETTINGS_LANG_EN) ? SETTINGS_LANG_EN : SETTINGS_LANG_RU; +} + +inline uint8_t settingsClampShowSeconds(uint8_t mode) { + if (mode == SETTINGS_SEC_ON || mode == SETTINGS_SEC_OFF || mode == SETTINGS_SEC_AUTO) { + return mode; + } + return SETTINGS_SEC_AUTO; +} diff --git a/components/flint/shell/shell_settings.h b/components/flint/shell/shell_settings.h index 8bc9283..7d153cd 100644 --- a/components/flint/shell/shell_settings.h +++ b/components/flint/shell/shell_settings.h @@ -3,8 +3,8 @@ #include // Settings overlay — owns row selection, paint, and value adjusters. -// Keep this file focused: new setting rows/adjusters → helpers beside it -// (formatters, cycles), not a return to a monolithic shell god-file. +// Do not grow this file with more rows/cycles inline: extract formatters / +// cycle helpers (or paint) beside it. New settings → helper first, not god-file. void shellSettingsEnter(); void shellSettingsNavPrev(); diff --git a/components/flint/ui/ui.cpp b/components/flint/ui/ui.cpp index 715ab03..722c5ae 100644 --- a/components/flint/ui/ui.cpp +++ b/components/flint/ui/ui.cpp @@ -1,5 +1,6 @@ #include "ui.h" #include "ui/ui_dirty.h" +#include "ui/ui_dirty_rules.h" #include "config/config.h" #include "config/debug.h" #include "display/display.h" @@ -99,7 +100,7 @@ void uiFaceTick() { } // Colon pulse only: reuse last context (skip palette / clock / format rebuild). - if ((dirty & ~UI_DIRTY_MOTION) == 0 && gHaveLastCtx) { + if (uiDirtyIsMotionOnly(dirty) && gHaveLastCtx) { gLastCtx.dirty = dirty; face->onTick(gLastCtx); return; @@ -107,20 +108,17 @@ void uiFaceTick() { FaceContext ctx = uiBuildContext(); ctx.dirty = dirty; - if (!ctx.showSeconds) { - dirty &= ~UI_DIRTY_SECOND; - ctx.dirty = dirty; - if (!dirty) { - gLastCtx = ctx; - gHaveLastCtx = true; - return; - } + dirty = uiDirtyFilterSeconds(dirty, ctx.showSeconds); + ctx.dirty = dirty; + if (!dirty) { + gLastCtx = ctx; + gHaveLastCtx = true; + return; } // FORCE / DAY_MODE / TIME_VALID → onForceRedraw. // SETTINGS stays soft → onTick (faces rebuild layout if prefs changed). - const bool force = (dirty & (UI_DIRTY_FORCE | UI_DIRTY_DAY_MODE | UI_DIRTY_TIME_VALID)) != 0 - || ctx.dayMode != gLastMode; + const bool force = uiDirtyWantsForceRedraw(dirty) || ctx.dayMode != gLastMode; gLastCtx = ctx; gHaveLastCtx = true; diff --git a/components/flint/ui/ui_dirty_rules.h b/components/flint/ui/ui_dirty_rules.h new file mode 100644 index 0000000..e109638 --- /dev/null +++ b/components/flint/ui/ui_dirty_rules.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "ui/ui_dirty.h" + +// Pure dirty-mask rules used by uiFaceTick (host-testable). + +inline bool uiDirtyIsMotionOnly(uint32_t dirty) { + return dirty != 0 && (dirty & ~UI_DIRTY_MOTION) == 0; +} + +inline uint32_t uiDirtyFilterSeconds(uint32_t dirty, bool showSeconds) { + if (!showSeconds) { + dirty &= ~UI_DIRTY_SECOND; + } + return dirty; +} + +inline bool uiDirtyWantsForceRedraw(uint32_t dirty) { + return (dirty & (UI_DIRTY_FORCE | UI_DIRTY_DAY_MODE | UI_DIRTY_TIME_VALID)) != 0; +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 75cd536..1a967ed 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,19 +1,25 @@ # FlinT OS architecture Firmware platform for **FlinT** devices (base: **FlinT Spark**), by **DeeTech Labs**. -This note covers the Face paint contract and dirty/event wiring. Module ownership: see [`MODULES.md`](MODULES.md) when present; Face SDK: `components/flint/sdk/flint_face_sdk.h`. +Face paint contract and dirty/event wiring. Module map: [`MODULES.md`](MODULES.md). Face SDK: `components/flint/sdk/flint_face_sdk.h`. ## Layers ```text hardware / board JSON - → services (time, net, settings, day_mode) + → services (time, net, settings/cfg, day_mode) → FaceContext (uiBuildContext) → Face::onTick / onForceRedraw → display (partial or full present) ``` -Faces read `FaceContext` and draw. They must not call Wi‑Fi, HTTP, NVS, or settings APIs. +Faces read `FaceContext` and draw. They must not call Wi‑Fi, HTTP, NVS, or settings/cfg APIs. + +## Day mode and waiting + +Time-of-day modes (`MORNING` / `DAY` / `EVENING` / `NIGHT`) come only from a **valid** local hour via `dayModeFromHour` (`day_mode_hour.*`). + +When `clockTimeGetLocal` fails (`!hasTime`), `uiBuildContext` sets **`DAY_MODE_WAITING`** — not a synthetic noon hour. Waiting uses a neutral profile (`showSeconds` / `showModeLabel` off for AUTO; date line already carries “Waiting for time”). `dayModeFromHour` never returns `WAITING`. ## Events → dirty → paint @@ -32,9 +38,19 @@ Faces read `FaceContext` and draw. They must not call Wi‑Fi, HTTP, NVS, or set ### Prefs vs FORCE -- **Prefs are soft.** After a settings/NVS change, emit **`FLINT_EVT_SETTINGS` only**. Do **not** also emit `FLINT_EVT_FORCE_REDRAW`. +- **Prefs are soft.** After a prefs change on **`/cfg/ui.cfg`**, emit **`FLINT_EVT_SETTINGS` only**. Do **not** also emit `FLINT_EVT_FORCE_REDRAW`. - `uiFaceTick` routes `UI_DIRTY_SETTINGS` to **`onTick`**, not `onForceRedraw`. - Faces must treat `UI_DIRTY_SETTINGS` (and/or changed fields in `FaceContext`) as layout-sensitive: rebuild time layout, date line, mode label, i18n strings as needed — often a full paint **from `onTick`**. - Use **`FLINT_EVT_FORCE_REDRAW`** for shell transitions (enter face), explicit user force, and other “wipe the canvas” cases — not as a stand-in for prefs. Clock edits from the settings overlay emit `MINUTE` / `TIME_VALID` as appropriate; returning to the face still uses shell `FORCE` on enter. + +## Persistence + +| Store | Role | +|-------|------| +| `storage` LittleFS | Asset pack (`core.flintpack`) | +| `cfg` LittleFS | UI prefs (`ui.cfg`) + soft clock (`time.cfg`) | +| ESP-IDF NVS | System / Wi‑Fi — **not** FlinT UI prefs | + +One-shot migrators still copy old `"flint"` NVS UI / epoch keys into `cfg` when the file is missing (`settings.cpp`, `clock_time.cpp`). **Sunset plan:** keep until field devices are known migrated; then delete migrate paths and NVS UI key reads in the same change (no long-lived dual-write). diff --git a/docs/MODULES.md b/docs/MODULES.md index cb1239f..028f55d 100644 --- a/docs/MODULES.md +++ b/docs/MODULES.md @@ -5,21 +5,21 @@ What exists under `components/flint/` today. Paths are from that component root. | Directory | Role | |-----------|------| | `app/` | Boot orchestration: bring up services, drive shell loop | -| `shell/` | State machine: face, face picker, settings overlays | -| `settings/` | Load/save prefs and soft clock on `cfg` LittleFS (`ui.cfg`) | +| `shell/` | State machine (`shell.cpp`) + overlays: `shell_picker.*`, `shell_settings.*` | +| `settings/` | Load/save prefs and soft clock on `cfg` LittleFS (`ui.cfg` / `time.cfg`) | | `config/` | Compile-time defaults, version macros, local `secrets.h` | | `board/` | Board JSON profiles → generated `board_config.inc` | | `display/` | LovyanGFX HAL, shared fonts, icon draw, UI tokens | | `assets/` | Mount `storage` LittleFS; blit from `core.flintpack` | | `sdk/` | Public Face SDK headers (`flint_face_sdk.h`, resources) | -| `faces/` | `Face` API, registry, glances, built-in faces | -| `day_mode/` | Day segments + palettes consumed via `FaceContext` | +| `faces/` | `Face` API, registry, glances, built-in faces (+ `face_digital_layout.*`) | +| `day_mode/` | Hour→mode (`day_mode_hour.*`), profiles (`day_mode_profile.*`), palettes/wash | | `i18n/` | Language packs / `flintTr` | | `net/` | Wi‑Fi STA | | `time/` | Soft clock, SNTP, date formatting helpers | -| `ui/` | Active-face paint path + dirty bitfield | +| `ui/` | Active-face paint path + dirty bitfield + chrome/splash | | `event/` | Lightweight event bus | -| `anim/` | Short motion helpers (e.g. colon pulse) | +| `anim/` | Short motion helpers (e.g. overlay fade) | | `util/` | `millis` / delay / heap helpers | Related outside `flint/`: @@ -31,10 +31,15 @@ Related outside `flint/`: | `config/` | `sdkconfig.defaults`, `partitions.csv` | | `assets/` | PNG sources for the asset pack | | `tools/` | Codegen (board catalog, i18n, asset pack) | +| `tests/host/` | Host C++ (date_fmt, day-mode, dirty rules, settings clamp); CI via `host-tests.yml` | +| `tools/tests/` | Unittest smoke for `gen_*` schema / ids (+ all boards validate) | | `community/` | Face templates (not linked into the build) | +| `.github/workflows/` | CI (`host-tests.yml`) | + +**Shell growth:** keep `shell_settings.cpp` focused — new setting rows / formatters / cycles go into helpers beside it, not back into a monolithic shell file. Persistence split: - **`storage`** — read-mostly asset pack (rewritten on full flash) - **`cfg`** — mutable prefs + soft clock (survives asset reflash) -- **`nvs`** — ESP-IDF / Wi‑Fi system NVS (not FlinT UI prefs) +- **`nvs`** — ESP-IDF / Wi‑Fi system NVS (not FlinT UI prefs; one-shot migrate into `cfg` only) diff --git a/tests/host/Makefile b/tests/host/Makefile index 6755e73..5518321 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -4,12 +4,14 @@ FLINT := ../../components/flint BUILD := build -CXX ?= c++ -CXXFLAGS := -std=c++17 -Wall -Wextra -Werror -I$(FLINT) +CXX ?= g++ +# Keep asserts armed if any sneak in; prefer hostFail / hostRequire. +CXXFLAGS := -std=c++17 -Wall -Wextra -Werror -UNDEBUG -I$(FLINT) -I. .PHONY: all run clean -all: $(BUILD)/test_day_mode $(BUILD)/test_date_fmt +all: $(BUILD)/test_day_mode $(BUILD)/test_date_fmt $(BUILD)/test_ui_dirty \ + $(BUILD)/test_settings_clamp $(BUILD): mkdir -p $(BUILD) @@ -22,9 +24,17 @@ $(BUILD)/test_day_mode: test_day_mode.cpp $(FLINT)/day_mode/day_mode_hour.cpp \ $(BUILD)/test_date_fmt: test_date_fmt.cpp stubs/i18n_stub.cpp $(FLINT)/time/date_fmt.cpp | $(BUILD) $(CXX) $(CXXFLAGS) -o $@ test_date_fmt.cpp stubs/i18n_stub.cpp $(FLINT)/time/date_fmt.cpp +$(BUILD)/test_ui_dirty: test_ui_dirty.cpp | $(BUILD) + $(CXX) $(CXXFLAGS) -o $@ test_ui_dirty.cpp + +$(BUILD)/test_settings_clamp: test_settings_clamp.cpp | $(BUILD) + $(CXX) $(CXXFLAGS) -o $@ test_settings_clamp.cpp + run: all ./$(BUILD)/test_day_mode ./$(BUILD)/test_date_fmt + ./$(BUILD)/test_ui_dirty + ./$(BUILD)/test_settings_clamp clean: rm -rf $(BUILD) diff --git a/tests/host/host_check.h b/tests/host/host_check.h new file mode 100644 index 0000000..82128f6 --- /dev/null +++ b/tests/host/host_check.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +// Host tests must not use assert(): -DNDEBUG turns failures into silent success. + +[[noreturn]] inline void hostFail(const char* msg) { + std::fprintf(stderr, "FAIL: %s\n", msg); + std::abort(); +} + +inline void hostRequire(bool cond, const char* msg) { + if (!cond) { + hostFail(msg); + } +} diff --git a/tests/host/test_date_fmt.cpp b/tests/host/test_date_fmt.cpp index 3355099..af7e90e 100644 --- a/tests/host/test_date_fmt.cpp +++ b/tests/host/test_date_fmt.cpp @@ -1,6 +1,6 @@ #include "time/date_fmt.h" +#include "host_check.h" -#include #include #include #include @@ -8,7 +8,7 @@ static void expectEq(const char* got, const char* want, const char* what) { if (std::strcmp(got, want) != 0) { std::fprintf(stderr, "%s: got \"%s\" want \"%s\"\n", what, got, want); - assert(false); + hostFail("string mismatch"); } } @@ -22,9 +22,9 @@ static struct tm sampleDec31() { } int main() { - assert(flintDateSepChar(SETTINGS_DATE_SEP_DOT) == '.'); - assert(flintDateSepChar(SETTINGS_DATE_SEP_SLASH) == '/'); - assert(flintDateSepChar(SETTINGS_DATE_SEP_DASH) == '-'); + hostRequire(flintDateSepChar(SETTINGS_DATE_SEP_DOT) == '.', "sep dot"); + hostRequire(flintDateSepChar(SETTINGS_DATE_SEP_SLASH) == '/', "sep slash"); + hostRequire(flintDateSepChar(SETTINGS_DATE_SEP_DASH) == '-', "sep dash"); const struct tm ti = sampleDec31(); char buf[32]; diff --git a/tests/host/test_day_mode.cpp b/tests/host/test_day_mode.cpp index 8f5788d..cd2b8d8 100644 --- a/tests/host/test_day_mode.cpp +++ b/tests/host/test_day_mode.cpp @@ -1,7 +1,7 @@ #include "day_mode/day_mode_hour.h" #include "day_mode/day_mode_profile.h" +#include "host_check.h" -#include #include static void expectMode(int hour, DayMode want) { @@ -9,12 +9,11 @@ static void expectMode(int hour, DayMode want) { if (got != want) { std::fprintf(stderr, "dayModeFromHour(%d): got %u want %u\n", hour, static_cast(got), static_cast(want)); - assert(false); + hostFail("dayModeFromHour mismatch"); } } int main() { - // Boundaries from kDayMode* in day_mode_hour.h expectMode(0, DAY_MODE_NIGHT); expectMode(4, DAY_MODE_NIGHT); expectMode(5, DAY_MODE_MORNING); @@ -29,17 +28,16 @@ int main() { // WAITING is never derived from an hour — uiBuildContext sets it only when !hasTime. for (int h = 0; h < 24; h++) { - assert(dayModeFromHour(h) != DAY_MODE_WAITING); + hostRequire(dayModeFromHour(h) != DAY_MODE_WAITING, "hour must not map to WAITING"); } const DayModeProfile& waiting = dayModeProfile(DAY_MODE_WAITING); - assert(!waiting.showSeconds); - assert(!waiting.showModeLabel); - assert(waiting.washHeight > 0); + hostRequire(!waiting.showSeconds, "waiting auto-seconds off"); + hostRequire(!waiting.showModeLabel, "waiting mode label off"); + hostRequire(waiting.washHeight > 0, "waiting wash height"); - // Day profile still enables auto-seconds (contrast with waiting). - assert(dayModeProfile(DAY_MODE_DAY).showSeconds); - assert(dayModeProfile(DAY_MODE_DAY).showModeLabel); + hostRequire(dayModeProfile(DAY_MODE_DAY).showSeconds, "day auto-seconds on"); + hostRequire(dayModeProfile(DAY_MODE_DAY).showModeLabel, "day mode label on"); std::puts("ok test_day_mode"); return 0; diff --git a/tests/host/test_settings_clamp.cpp b/tests/host/test_settings_clamp.cpp new file mode 100644 index 0000000..4c9ffc1 --- /dev/null +++ b/tests/host/test_settings_clamp.cpp @@ -0,0 +1,28 @@ +#include "settings/settings_clamp.h" +#include "host_check.h" + +#include + +int main() { + hostRequire(settingsClampHourFormat(SETTINGS_HOUR_12) == SETTINGS_HOUR_12, "hour 12"); + hostRequire(settingsClampHourFormat(SETTINGS_HOUR_24) == SETTINGS_HOUR_24, "hour 24"); + hostRequire(settingsClampHourFormat(99) == SETTINGS_HOUR_24, "hour invalid→24"); + + hostRequire(settingsClampDateFormat(SETTINGS_DATE_YMD4) == SETTINGS_DATE_YMD4, "date ok"); + hostRequire(settingsClampDateFormat(SETTINGS_DATE_FORMAT_COUNT) == SETTINGS_DATE_DMY4, + "date overflow→DMY4"); + + hostRequire(settingsClampDateSep(SETTINGS_DATE_SEP_DASH) == SETTINGS_DATE_SEP_DASH, "sep ok"); + hostRequire(settingsClampDateSep(SETTINGS_DATE_SEP_COUNT) == SETTINGS_DATE_SEP_DOT, + "sep overflow→dot"); + + hostRequire(settingsClampLang(SETTINGS_LANG_EN) == SETTINGS_LANG_EN, "lang en"); + hostRequire(settingsClampLang(42) == SETTINGS_LANG_RU, "lang invalid→ru"); + + hostRequire(settingsClampShowSeconds(SETTINGS_SEC_ON) == SETTINGS_SEC_ON, "sec on"); + hostRequire(settingsClampShowSeconds(SETTINGS_SEC_AUTO) == SETTINGS_SEC_AUTO, "sec auto"); + hostRequire(settingsClampShowSeconds(7) == SETTINGS_SEC_AUTO, "sec invalid→auto"); + + std::puts("ok test_settings_clamp"); + return 0; +} diff --git a/tests/host/test_ui_dirty.cpp b/tests/host/test_ui_dirty.cpp new file mode 100644 index 0000000..604f095 --- /dev/null +++ b/tests/host/test_ui_dirty.cpp @@ -0,0 +1,25 @@ +#include "ui/ui_dirty.h" +#include "ui/ui_dirty_rules.h" +#include "host_check.h" + +#include + +int main() { + hostRequire(uiDirtyIsMotionOnly(UI_DIRTY_MOTION), "motion-only"); + hostRequire(!uiDirtyIsMotionOnly(0), "empty not motion-only"); + hostRequire(!uiDirtyIsMotionOnly(UI_DIRTY_MOTION | UI_DIRTY_SECOND), "motion+second"); + + hostRequire(uiDirtyFilterSeconds(UI_DIRTY_SECOND | UI_DIRTY_MINUTE, false) == UI_DIRTY_MINUTE, + "drop SECOND when !showSeconds"); + hostRequire(uiDirtyFilterSeconds(UI_DIRTY_SECOND, true) == UI_DIRTY_SECOND, + "keep SECOND when showSeconds"); + + hostRequire(uiDirtyWantsForceRedraw(UI_DIRTY_FORCE), "FORCE"); + hostRequire(uiDirtyWantsForceRedraw(UI_DIRTY_DAY_MODE), "DAY_MODE"); + hostRequire(uiDirtyWantsForceRedraw(UI_DIRTY_TIME_VALID), "TIME_VALID"); + hostRequire(!uiDirtyWantsForceRedraw(UI_DIRTY_SETTINGS), "SETTINGS soft"); + hostRequire(!uiDirtyWantsForceRedraw(UI_DIRTY_MINUTE | UI_DIRTY_WIFI), "minute/wifi soft"); + + std::puts("ok test_ui_dirty"); + return 0; +} diff --git a/tools/tests/test_gen_tools.py b/tools/tests/test_gen_tools.py index aa6f713..f9ed37f 100644 --- a/tools/tests/test_gen_tools.py +++ b/tools/tests/test_gen_tools.py @@ -39,17 +39,23 @@ def test_board_index_ids_match_files(self): self.assertEqual(data["id"], bid) self.assertEqual(path.parent, boards_dir) - def test_flint_spark_validates(self): + def test_all_boards_validate(self): index = self.gen.load_index() - board_id = "flint_spark" - entry = self.gen.find_board_entry(index, board_id) - board = self.gen.load_json(self.gen.board_json_path(entry, board_id)) - display_id = board["display"] - display_path = ( - ROOT / "components" / "flint" / "board" / "periph" / "display" / f"{display_id}.json" - ) - display = self.gen.load_json(display_path) - self.gen.validate(board, copy.deepcopy(display), board_id) + for entry in index.get("boards") or []: + board_id = entry["id"] + board = self.gen.load_json(self.gen.board_json_path(entry, board_id)) + display_id = board["display"] + display_path = ( + ROOT + / "components" + / "flint" + / "board" + / "periph" + / "display" + / f"{display_id}.json" + ) + display = self.gen.load_json(display_path) + self.gen.validate(board, copy.deepcopy(display), board_id) def test_board_validate_rejects_id_mismatch(self): index = self.gen.load_index()