Files
MeshCore/test/test_scrolling_status_bar_rollover.py
T

129 lines
4.4 KiB
Python

"""Exercise the actual tiny UI status-bar header across clock rollover."""
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
CXX = shutil.which("g++")
DRIVER = r'''#pragma once
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
extern uint32_t now_ms;
inline uint32_t millis() { return now_ms; }
namespace UIColor { constexpr unsigned primary_txt = 1; }
struct DisplayDriver {
int width = 200;
unsigned prints = 0;
int last_x = 0;
void setTextSize(int) {}
int getTextWidth(const char*) { return width; }
void setColor(unsigned) {}
void setCursor(int x, int) { last_x = x; }
void print(const char*) { ++prints; }
};
'''
FIXTURE = r'''#include <ScrollingStatusBar.h>
#include <cstdlib>
uint32_t now_ms = 0;
int main(int argc, char** argv) {
assert(argc == 2);
const int mode = std::atoi(argv[1]);
ScrollingStatusBar bar;
DisplayDriver display;
auto update = [&](const char* name) { bar.update(display, name, 4000, false, false, true); };
if (mode == 1) {
now_ms = 0xfffffff0U;
bar.begin(72); update("wrapped-next");
assert(bar.needsRedraw());
bar.render(display);
assert(!bar.needsRedraw());
now_ms += 79; assert(!bar.needsRedraw());
++now_ms; assert(bar.needsRedraw());
bar.render(display); assert(!bar.needsRedraw());
} else if (mode == 2) {
now_ms = 0xffffffffU - 100;
bar.begin(72); update("overdue-high");
assert(bar.needsRedraw()); bar.render(display);
now_ms = 5; assert(bar.needsRedraw());
bar.render(display); assert(!bar.needsRedraw());
} else if (mode == 3) {
now_ms = 0x80000000U;
bar.begin(72); update("upper-half");
assert(bar.needsRedraw()); bar.render(display);
assert(!bar.needsRedraw());
// Fresh content must redraw immediately, not inherit the old deadline.
update("changed-with-future-scroll"); assert(bar.needsRedraw());
bar.render(display); assert(!bar.needsRedraw());
} else if (mode == 4) {
now_ms = 0x80000000U;
display.width = 50;
bar.begin(72); update("static");
assert(bar.needsRedraw()); bar.render(display);
assert(!bar.needsRedraw());
now_ms += 1000; assert(!bar.needsRedraw());
update("static-changed"); assert(bar.needsRedraw());
bar.render(display); assert(!bar.needsRedraw());
} else if (mode == 5) {
now_ms = 1000;
display.width = 50;
bar.begin(72); update("static");
assert(bar.needsRedraw()); bar.render(display);
assert(!bar.needsRedraw());
bar.begin(72); assert(bar.needsRedraw());
} else assert(false);
assert(display.prints);
}
'''
@unittest.skipUnless(CXX, "Native C++ compiler is required")
class ScrollingStatusBarRolloverTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temporary = tempfile.TemporaryDirectory(prefix="statusbar-rollover-")
cls.addClassCleanup(cls.temporary.cleanup)
directory = Path(cls.temporary.name)
stub = directory / "helpers/ui/DisplayDriver.h"
stub.parent.mkdir(parents=True)
stub.write_text(DRIVER, encoding="ascii")
source = directory / "fixture.cpp"
source.write_text(FIXTURE, encoding="ascii")
cls.binary = directory / "probe"
subprocess.run([
CXX, "-std=c++17", "-Wall", "-Wextra", "-Werror",
"-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-omit-frame-pointer", "-fno-pie", "-no-pie",
"-I" + str(ROOT / "examples/companion_radio/ui-tiny"),
"-I" + str(directory), str(source), "-o", str(cls.binary),
], check=True, capture_output=True, timeout=30)
def run_mode(self, mode):
result = subprocess.run([str(self.binary), str(mode)], capture_output=True,
text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_future_wrapped_scroll_deadline_waits_exactly_eighty_ms(self):
self.run_mode(1)
def test_high_deadline_expired_after_wrap_is_immediately_due(self):
self.run_mode(2)
def test_begin_upper_half_and_changed_scrolling_content_are_immediate(self):
self.run_mode(3)
def test_static_content_updates_only_when_dirty(self):
self.run_mode(4)
def test_begin_rearms_static_content_for_redraw(self):
self.run_mode(5)
if __name__ == "__main__":
unittest.main(verbosity=2)