Files
uv-k5-v3-emulator/tools/test_uvk5_image.py
mckero 2667e046e8 Emulator: multiboot slots from the page, flash controller, portable tests
flash controller: store ACR/OPTKEYR instead of swallowing them, which is what stopped the factory bootloader from starting

slots over the firmware's own serial protocol (0x0720 family); uvk5_socket/uvk5_testenv so a fresh checkout skips instead of failing; web UI slot table and Multiboot button; quick start, CONTRIBUTING, and stop tracking firmware images and radio dumps
2026-10-01 14:54:34 +08:00

87 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Unit tests for firmware image detection. No emulator needed.
The distinction matters because getting it wrong is silent: an image loaded at the
wrong offset runs 0x2800 bytes off and the first fetch reads whatever data is there.
"""
import os
import struct
import tempfile
import unittest
import uvk5_image as ui
def write(tmp, name, data):
path = os.path.join(tmp, name)
with open(path, "wb") as fh:
fh.write(data)
return path
def image(tmp, name, sp, reset, size=0x4000, extra_at_2800=None):
"""A file whose first two words are a vector table."""
buf = bytearray(size)
struct.pack_into("<II", buf, 0, sp, reset)
if extra_at_2800 is not None:
struct.pack_into("<II", buf, 0x2800, *extra_at_2800)
return write(tmp, name, bytes(buf))
class TestDetect(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.mkdtemp()
def test_application_image(self):
"""Reset handler past the application offset: linked for 0x08002800."""
path = image(self.tmp, "app.bin", 0x20004000, 0x08002d49)
info = ui.detect(path)
self.assertEqual(info.kind, "application")
self.assertEqual(info.app_offset, ui.APP_OFFSET)
def test_full_flash_image(self):
"""Entry point inside the bootloader region: address 0 aliases the base."""
path = image(self.tmp, "bl.bin", 0x200032c0, 0x08000901,
extra_at_2800=(0x20004000, 0x08002d49))
info = ui.detect(path)
self.assertEqual(info.kind, "full-flash")
self.assertEqual(info.app_offset, 0)
def test_elf_is_passed_through(self):
path = write(self.tmp, "app.elf", b"\x7fELF" + bytes(64))
info = ui.detect(path)
self.assertEqual(info.kind, "elf")
self.assertEqual(info.app_offset, ui.APP_OFFSET)
def test_rejects_a_file_with_no_vector_table(self):
path = write(self.tmp, "junk.bin", b"not a firmware at all" * 8)
with self.assertRaises(ui.ImageError):
ui.detect(path)
def test_rejects_an_sp_outside_sram(self):
"""A plausible reset handler is not enough; both words have to hold."""
path = image(self.tmp, "bad.bin", 0x12345678, 0x08002d49)
with self.assertRaises(ui.ImageError):
ui.detect(path)
def test_rejects_an_empty_file(self):
path = write(self.tmp, "empty.bin", b"")
with self.assertRaises(ui.ImageError):
ui.detect(path)
def test_rejects_a_missing_file(self):
with self.assertRaises(ui.ImageError):
ui.detect(os.path.join(self.tmp, "nope.bin"))
def test_slot_can_be_swapped(self):
slot = ui.ImageSlot()
self.assertIsNone(slot.path)
slot.set(image(self.tmp, "app.bin", 0x20004000, 0x08002d49))
self.assertEqual(slot.app_offset, ui.APP_OFFSET)
slot.set(image(self.tmp, "bl.bin", 0x200032c0, 0x08000901))
self.assertEqual(slot.app_offset, 0)
if __name__ == "__main__":
unittest.main()