Files
mckero 1e9fdf685c Find the screen buffers in the firmware instead of hardcoding one build's
The page was told --frame-addr 0x200012BE --status-addr 0x2000163E and used them as a fallback. The firmware the user actually flashed keeps its buffers at 0x2000129E/0x2000161E, 32 bytes earlier, so every line landed 32 bytes off: that is the "other firmware looks shifted" report. The images here are minimal ELFs with no symbol table, so there is nothing to read -- but the firmware's own buffers hold the same bytes the controller holds, and tools/uvk5_buffers.py finds them by matching (1024/1024 bytes for that file).

The two address flags are optional now, work/run-webui.ps1 passes no machine-specific values at all, and the page reports what it found in /api/status and /api/panel. tools/uvk5_testenv.qemu() also looks in the sibling qemu-7.2/build the rest of the repo assumes. Fixed /api/panel's emulator-off branch, which called jsonify with both a dict and kwargs and 500'd.

Tests: test_uvk5_buffers (the search must count matches, not pairs -- its first version scored every offset full marks and always answered the first one).
2026-10-01 16:09:26 +08:00

108 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Where the emulator tests find a QEMU and a firmware, and what to say when they cannot.
The emulator tests used to name one developer's build directory -- a QEMU under their
home, and one firmware build from a source tree outside this repository --
which is fine on the machine they were written on and useless anywhere else -- and a
missing file used to be a hard failure, so a checkout without that build could never
see a green run and nobody could tell "broken" from "not set up here".
Resolution order, for both:
qemu() env QEMU, env UVK5_QEMU, qemu-system-arm on PATH
firmware() env ELF, env UVK5_FIRMWARE, assets/firmware/*, work/*
firmware() prefers assets/firmware, which tools/fetch_firmware.py fills from the upstream
project's release archive; that is what makes these tests reproducible rather than
anchored to one private build.
"""
from __future__ import annotations
import glob
import os
import pathlib
import shutil
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
FIRMWARE_DIR = os.path.join(ROOT, "assets", "firmware")
def qemu():
"""Path to a qemu-system-arm, or None."""
for var in ("QEMU", "UVK5_QEMU"):
value = os.environ.get(var)
if value and os.path.exists(value):
return pathlib.Path(value)
found = shutil.which("qemu-system-arm")
if found:
return pathlib.Path(found)
# The sibling tree tools/setup_qemu.sh builds into, which is also what run_tests.sh
# assumes. Not a machine-specific path: it is this checkout's own convention, so a
# fresh clone that followed the README finds its QEMU without being told.
sibling = pathlib.Path(__file__).resolve().parent.parent.parent / "qemu-7.2" / "build"
for name in ("qemu-system-arm", "qemu-system-arm.exe"):
if (sibling / name).exists():
return sibling / name
return None
def gdb():
"""Path to a gdb that speaks ARM, or None.
A few tests read firmware globals over a gdb attach. That is the one part of the
suite a Windows box cannot do at all, and it used to be a hard failure rather than
a skip -- so those tests reported a regression where there was only a missing tool.
"""
for var in ("GDB", "UVK5_GDB"):
value = os.environ.get(var)
if value and os.path.exists(value):
return pathlib.Path(value)
# Not plain gdb: on most hosts that is the *host* architecture's gdb, which attaches
# to the guest, returns nothing useful, and produces a confusing parse failure
# instead of "no debugger". The two names below can actually read an ARM target.
for name in ("gdb-multiarch", "arm-none-eabi-gdb"):
found = shutil.which(name)
if found:
return pathlib.Path(found)
return None
def firmware():
"""Path to a firmware image to run, or None."""
for var in ("ELF", "UVK5_FIRMWARE", "UVK5_MULTIBOOT_IMAGE"):
value = os.environ.get(var)
if value and os.path.exists(value):
return pathlib.Path(value)
patterns = [os.path.join(FIRMWARE_DIR, "*.bin"),
os.path.join(FIRMWARE_DIR, "*.elf"),
os.path.join(ROOT, "work", "*.bin"),
os.path.join(ROOT, "work", "*.elf")]
for pattern in patterns:
for hit in sorted(glob.glob(pattern)):
return pathlib.Path(hit)
return None
def missing(items):
"""First thing in @items that is absent, described with how to fix it.
@items is a list of (path, what, how). Returns None when everything is present.
"""
for path, what, how in items:
if not path or not os.path.exists(path):
return "%s is missing (%s); %s" % (what, path or "not found", how)
return None
def skip(message):
"""Print a skip line and return the exit code a test should use.
A missing prerequisite is not a failing test. Saying so loudly beats exiting
non-zero, which is indistinguishable from a regression and is why a Windows or
fresh checkout could never show a green run.
"""
print("SKIP: %s" % message)
return 0