// Actual guarded loop block and timer field are injected without rewriting. // Only Arduino clock/GPIO and display wake are host boundary adapters. #include #include #include #include static uint32_t clock_ms = 0; uint32_t millis() { return clock_ms; } static unsigned reads = 0, wakes = 0; static uint32_t read_duration = 0, wake_duration = 0; static bool pin_pressed = false; static const char* active_case = "startup"; static unsigned checks = 0; #define CHECK(value) do { \ ++checks; \ if (!(value)) { \ std::fprintf(stderr, "FAIL %s: %s (line %d)\n", active_case, #value, __LINE__); \ std::exit(1); \ } \ } while (0) #if defined(BACKLIGHT_BTN) constexpr unsigned LOW = 0, HIGH = 1; int digitalRead(unsigned pin) { CHECK(pin == PIN_BUTTON2); ++reads; clock_ms += read_duration; return pin_pressed ? LOW : HIGH; } #endif namespace mesh { namespace ui { enum class DisplayWake { Button }; } } struct DisplayDriver { void wake(mesh::ui::DisplayWake reason) { CHECK(reason == mesh::ui::DisplayWake::Button); ++wakes; clock_ms += wake_duration; } } display; struct UITask { @FIELD@ DisplayDriver* _display = &display; void poll() { @BLOCK@ } }; static void at(UITask& ui, uint32_t now, bool pressed, unsigned expected_reads, unsigned expected_wakes) { clock_ms = now; pin_pressed = pressed; ui.poll(); CHECK(reads == expected_reads); CHECK(wakes == expected_wakes); } #if defined(BACKLIGHT_BTN) static void cadence(UITask& ui, uint32_t start, bool initially_pressed) { const unsigned first_wake = initially_pressed ? 1 : 0; at(ui, start, initially_pressed, 1, first_wake); at(ui, start, initially_pressed, 1, first_wake); at(ui, start + 1, !initially_pressed, 1, first_wake); at(ui, start + 300, !initially_pressed, 1, first_wake); at(ui, start + 301, !initially_pressed, 2, 1); at(ui, start + 301, !initially_pressed, 2, 1); } #endif // Every case is a fresh process: real static sampled-button state is retained. int main(int argc, char** argv) { CHECK(argc == 2); active_case = argv[1]; UITask ui; const char* name = argv[1]; #if defined(BACKLIGHT_BTN) if (!std::strcmp(name, "zero")) { at(ui, 0, true, 0, 0); cadence(ui, 1, true); } else if (!std::strcmp(name, "upper")) { cadence(ui, 0x80000000u, false); } else if (!std::strcmp(name, "upper-pressed")) { cadence(ui, 0x80000000u, true); } else if (!std::strcmp(name, "max")) { cadence(ui, UINT32_MAX, false); } else if (!std::strcmp(name, "normal")) { cadence(ui, 100, false); } else if (!std::strcmp(name, "signed-boundary")) { cadence(ui, 0x7fffffffu - 150, false); } else if (!std::strcmp(name, "wrap-visible")) { cadence(ui, UINT32_MAX - 15, false); } else if (!std::strcmp(name, "wrap-wake")) { at(ui, UINT32_MAX - 310, false, 1, 0); at(ui, 20, true, 2, 1); at(ui, 20, true, 2, 1); } else if (!std::strcmp(name, "deadline-zero")) { cadence(ui, uint32_t(0u - 300u), false); } else if (!std::strcmp(name, "held")) { at(ui, 1, true, 1, 1); at(ui, 302, true, 2, 1); at(ui, 603, true, 3, 1); at(ui, 904, false, 4, 1); at(ui, 1205, true, 5, 2); } else if (!std::strcmp(name, "unobserved-release")) { at(ui, 1, true, 1, 1); at(ui, 100, false, 1, 1); at(ui, 101, true, 1, 1); at(ui, 302, true, 2, 1); } else if (!std::strcmp(name, "null-pressed")) { ui._display = nullptr; at(ui, 1, true, 1, 0); ui._display = &display; at(ui, 302, true, 2, 0); at(ui, 603, false, 3, 0); at(ui, 904, true, 4, 1); } else if (!std::strcmp(name, "null-released")) { ui._display = nullptr; at(ui, 1, false, 1, 0); ui._display = &display; at(ui, 302, true, 2, 1); } else if (!std::strcmp(name, "completion")) { read_duration = 7; wake_duration = 37; at(ui, 100, true, 1, 1); CHECK(clock_ms == 144); read_duration = wake_duration = 0; at(ui, 444, false, 1, 1); at(ui, 445, false, 2, 1); at(ui, 745, true, 2, 1); at(ui, 746, true, 3, 2); } else if (!std::strcmp(name, "completion-zero-wake")) { wake_duration = 11; at(ui, UINT32_MAX - 10, true, 1, 1); CHECK(clock_ms == 0); wake_duration = 0; at(ui, 0, true, 1, 1); at(ui, 1, true, 1, 1); at(ui, 300, false, 1, 1); at(ui, 301, false, 2, 1); } else if (!std::strcmp(name, "completion-zero-read")) { read_duration = 11; at(ui, UINT32_MAX - 10, false, 1, 0); CHECK(clock_ms == 0); read_duration = 0; at(ui, 0, true, 1, 0); at(ui, 300, true, 1, 0); at(ui, 301, true, 2, 1); } else if (!std::strcmp(name, "long-released")) { at(ui, 1, false, 1, 0); at(ui, 1u + 26u * 24u * 60u * 60u * 1000u, true, 2, 1); } else if (!std::strcmp(name, "long-pressed")) { at(ui, 1, true, 1, 1); const uint32_t later = 1u + 26u * 24u * 60u * 60u * 1000u; at(ui, later, true, 2, 1); at(ui, later + 301, false, 3, 1); at(ui, later + 602, true, 4, 2); } else if (!std::strcmp(name, "near-full-cycle")) { at(ui, 100, false, 1, 0); at(ui, uint32_t(100u + UINT32_MAX - 1000u), true, 2, 1); } else if (!std::strcmp(name, "late-one-poll")) { at(ui, 1, false, 1, 0); at(ui, 100000, true, 2, 1); at(ui, 100000, false, 2, 1); at(ui, 100300, false, 2, 1); at(ui, 100301, false, 3, 1); } else { std::fprintf(stderr, "unknown case: %s\n", name); return 2; } #else CHECK(std::strcmp(name, "no-feature") == 0); const uint32_t stamps[] = {0u, 1u, 0x7fffffffu, 0x80000000u, UINT32_MAX}; for (uint32_t stamp : stamps) { at(ui, stamp, true, 0, 0); at(ui, stamp, false, 0, 0); } CHECK(read_duration == 0 && wake_duration == 0); #endif std::printf("PASS %s: %u checks\n", name, checks); }