"""Bound custom Cardputer runtime owners and truncated display text.""" from pathlib import Path import re import subprocess import sys import tempfile import unittest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) from check_firmware_ram import requirements from test_replay_reset_integration import extract_braced class CardputerRuntimeMemoryTests(unittest.TestCase): def test_custom_ui_owners_are_additive_and_fonts_fit_their_allowance(self): base = {"ESP32_PLATFORM": "1", "BLE_PIN_CODE": "123456"} target = "M5Stack_Cardputer_Adv_companion_radio_ble" ordinary = requirements("ESP32_PLATFORM", base, target) custom = requirements("ESP32_PLATFORM", dict(base, CARDPUTER_ADV_UI="1"), target) self.assertEqual(ordinary["components"], { key: value for key, value in custom["components"].items() if not key.startswith("cardputer_")}) self.assertGreater(custom["required_heap_bytes"], ordinary["required_heap_bytes"]) fonts = ROOT / "variants/m5stack_cardputer_adv/companion_radio/ui-cardputer" for name in ("font.h", "font_16.h", "font_19.h"): data = (fonts / name).read_text().split("{", 1)[1] prefix = bytes(int(byte, 16) for byte in re.findall(r"0x([0-9a-fA-F]{2})", data)[:4]) glyphs = int.from_bytes(prefix, "big") self.assertGreater(glyphs, 0) self.assertLessEqual(glyphs, 716) # Five VLW arrays use 4+2+1+1+1 bytes/glyph. Also allow # allocation metadata and the runtime font object itself. self.assertLessEqual(glyphs * 9 + 1024, custom["components"]["cardputer_font_metadata"]) disabled = requirements("ESP32_PLATFORM", dict(base, CARDPUTER_ADV_UI="0"), target) self.assertEqual(disabled, ordinary) with self.assertRaisesRegex(ValueError, "ESP32 Companion"): requirements("NRF52_PLATFORM", {"CARDPUTER_ADV_UI": "1"}, target) def test_scroll_keeps_truncated_utf8_inside_the_input_buffer(self): driver = (ROOT / "variants/m5stack_cardputer_adv/hw/CardputerDisplay.cpp").read_text() method = extract_braced(driver, "void CardputerDisplay::drawTextLeftAlignWithScroll(") prelude = r''' #include #include class CardputerDisplay { public: const char* begin = nullptr; const char* end = nullptr; int getTextWidth(const char* text) { return strlen(text); } void drawTextLeftAlign(int, int, const char* text) { assert(text >= begin && text <= end); (void)strlen(text); } void drawTextLeftAlignWithScroll(int, int, int, const char*); }; int main() { CardputerDisplay display; for (unsigned lead : {0xC2U, 0xE2U, 0xF0U}) { char text[2] = {static_cast(lead), 0}; display.begin = text; display.end = text + 1; display.drawTextLeftAlignWithScroll(0, 0, 0, text); } } ''' prelude = '#include \n' + prelude for legacy in (False, True): body = method.replace(" offset = text_len;\n", "") if legacy else method with self.subTest(legacy=legacy), tempfile.TemporaryDirectory() as directory: work = Path(directory) cpp, binary = work / "scroll.cpp", work / "scroll" cpp.write_text(prelude + body) build = subprocess.run(["g++", "-std=c++17", "-Wall", "-Wextra", "-Werror", "-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie", str(cpp), "-o", str(binary)], capture_output=True, text=True) self.assertEqual(build.returncode, 0, build.stdout + build.stderr) run = subprocess.run([str(binary)], capture_output=True, text=True) if legacy: self.assertNotEqual(run.returncode, 0, "Unclamped scroll unexpectedly passed") else: self.assertEqual(run.returncode, 0, run.stdout + run.stderr) if __name__ == "__main__": unittest.main()