#!/usr/bin/env python3 """Exercise the production Pocket e-paper driver against a recording panel.""" import os from pathlib import Path import shutil import subprocess import tempfile import unittest ROOT = Path(__file__).resolve().parents[1] UI = ROOT / "src/helpers/ui" FIXTURE = ROOT / "test/fixtures/pocket_eink_refresh" class PocketEinkRefreshTest(unittest.TestCase): @classmethod def setUpClass(cls): compiler = shutil.which(os.environ.get("CXX", "g++")) if compiler is None: raise unittest.SkipTest("host G++ required") cls.temporary = tempfile.TemporaryDirectory(prefix="pocket-eink-refresh-") cls.work = Path(cls.temporary.name) # Copy the actual driver so its quoted hardware includes resolve to the # fixture. The DisplayDriver base and Pocket feature configuration stay # production code, while the panel records calls crossing the HW API. for name in ("GxEPDDisplay.h", "GxEPDDisplay.cpp"): shutil.copyfile(UI / name, cls.work / name) cls.binary = cls.work / "refresh-test" flags = ["-std=c++17", "-Wall", "-Wextra", "-Werror", "-g", "-Wno-unused-parameter", "-Wno-sign-compare"] if os.name != "nt": flags += ["-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-omit-frame-pointer", "-fno-pie", "-no-pie"] command = [compiler, *flags, "-DHELTEC_MESH_POCKET=1", "-DCOMPANION_RADIO_FULL=1", "-DEINK_DISPLAY_MODEL=GxEPD2_213_B74", "-DDISPLAY_ROTATION=3", "-I" + str(cls.work), "-I" + str(FIXTURE), "-I" + str(UI), "-I" + str(ROOT / "src"), str(FIXTURE / "main.cpp"), str(cls.work / "GxEPDDisplay.cpp"), "-o", str(cls.binary)] result = subprocess.run(command, capture_output=True, text=True, timeout=90) if result.returncode: cls.temporary.cleanup() raise AssertionError(" ".join(command) + "\n" + result.stdout + result.stderr) @classmethod def tearDownClass(cls): cls.temporary.cleanup() def run_case(self, scenario): result = subprocess.run([str(self.binary), scenario], capture_output=True, text=True, timeout=10) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) def test_manual_clean_affects_current_frame_even_when_crc_is_unchanged(self): self.run_case("manual") def test_first_image_is_clean_even_when_gui_applies_its_refresh_settings(self): self.run_case("boot") def test_unchanged_frames_skip_io_and_do_not_use_partial_refresh_budget(self): self.run_case("periodic") def test_changing_clean_interval_preserves_a_pending_full_refresh(self): self.run_case("interval") def test_waking_from_hibernate_restores_panel_history_before_partial_updates(self): self.run_case("wake") def test_rotation_cleans_the_current_frame_and_keeps_native_dimensions(self): self.run_case("rotation") if __name__ == "__main__": unittest.main()