Skip to content

uman: emq — adi-ldr setup, t/py polish, container fixes - #5

Merged
sjg20 merged 21 commits into
masterfrom
emq
May 20, 2026
Merged

uman: emq — adi-ldr setup, t/py polish, container fixes#5
sjg20 merged 21 commits into
masterfrom
emq

Conversation

@sjg20

@sjg20 sjg20 commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • New setup adi-ldr component to clone and build the Analog Devices
    ldr tool with meson in a venv, plus prefixed symlinks
  • Test runner improvements: switch t progress to Result: lines once
    seen, omit zero counts from summaries, return partial-match names
    from resolve, tighten leak-detail regex, --pollute filters and new
    pollution search algorithms
  • Container (cc) fixes: --ssh key setup, auto-mount /tmp/b when in
    the uman tree, detect lxc start failures, persist unmounts and
    detect mount failures
  • pytest (py) improvements: add tools/ to PYTHONPATH, --id option,
    let user test spec override the gitlab default, --why-skip flag
  • Misc: -e flag for build env vars, ci SAGE_LAB control, Ctrl-C
    forwarded to gdb in monitor loop, UTF_DM constant fix, README
    Subcommands list updated and sorted

Test plan

  • python3 -m pytest uman_pkg/ftest.py -v
  • python3 -m pylint uman_pkg/
  • Smoke-test um setup adi-ldr -n and um t -r on sandbox

sjg20 and others added 21 commits April 22, 2026 13:09
The find_gitlab_ci_file() function only searches parent directories
and ~/u/ for the GitLab CI file. When running from the U-Boot root
directory, or when $USRC is set to point to the source tree, the file
is not found, so 'um ci -p help' shows empty board and job lists.

Add the current directory as the first candidate path, and also check
$USRC/.gitlab-ci.yml when the environment variable is set.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some boards need extra environment variables like BL31 or TEE to point
to firmware binaries. There is currently no way to pass these through
the command line.

Add a repeatable -e/--env KEY=VALUE flag to the build and pytest
subcommands. For pytest, the variables are passed to both the build
step and the test execution.

Example: um py -B qemu_arm64 -b -e BL31=/path/bl31.bin test_fw_handoff

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When testing boards like qemu_arm64 with firmware handoff, the test
framework needs TEST_PY_ID to select the right configuration, and
PYTHONPATH must include the hooks directory so the boardenv file
(u_boot_boardenv_<board>_<id>.py) is found.

Add an --id flag that overrides the auto-detected TEST_PY_ID from
.gitlab-ci.yml, passes --id to test.py and adds
test/hooks/py/travis-ci/ to PYTHONPATH.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When running pytest, skipped tests are silent by default, giving no
indication of why a test was skipped. This makes it hard to know
whether a skip is expected or indicates a misconfiguration.

Add a --why-skip flag that passes -rs to pytest so the reason for
each skipped test is shown in the summary.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The gdb_monitor() function puts the parent terminal in raw mode so
that all keystrokes (arrows, backspace, etc.) pass through to gdb.
Raw mode disables ISIG, so Ctrl-C no longer generates SIGINT on the
parent side, and the \x03 byte is just forwarded through the pty to
gdb where it is echoed but not acted on.

Intercept \x03 bytes on stdin and send SIGINT directly to gdb's
process group. The byte is stripped from the forwarded data so it
does not also reach gdb via the pty.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The RE_LEAK_DETAIL regex matches any line with whitespace and two
hex-ish tokens. This false-matches on rsvmem dumps, GPT partition
tables, cryptographic hashes, and host info lines that happen to have
the same shape. As a result, tests like rsvmem, files_api_norun and
cmd_host report hundreds of kilobytes of phantom leaks even though
u-boot's own 'Leak:' line shows zero or near-zero allocs.

Require the caller field to match the function:line format (\S+:\d+,
e.g. sandbox_mmc_probe:194) which is distinctive to real leak
entries emitted by u-boot's allocator and does not appear in the
false-matching lines.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The --pollute verify-alone step uses -k with just the target test name,
which does substring matching and picks up other tests containing that
name. Worse, it uses /tmp/b/<board>-pollute as the build dir but only
populates it when -b is passed, so without -b the tests fail with a
FileNotFoundError on the u-boot binary rather than a genuine pollution
signal.

Include both the user's test_spec and the gitlab TEST_PY_TEST_SPEC in
the -k expression for both pollute_run() and collect_tests(), wrapping
each clause in parentheses and joining with 'and'. Always run buildman
incrementally (fast for no-op) so the pollute build is current whether
or not -b is passed.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The --pollute bisect algorithm assumes a single polluter test, so it
cannot diagnose failures that need two or more prior tests to trigger.
When the verify step returns PASS, the user is told the result is
inconclusive with no further help.

Add a --pollute-algo option with two new algorithms alongside bisect:

- linear: grow the suffix from the target one test at a time and
  stop at the minimal window that reproduces the failure
- ddmin: delta debugging to find a 1-minimal subset of tests that
  together cause the pollution, suitable for multi-test interactions

The existing binary search is refactored into pollute_bisect() and
remains the default. On an inconclusive bisect result the hint now
points the user at --pollute-algo linear.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The setup command has no way to install the ldr tool used on Analog
Devices boards. The U-Boot Docker image builds it from source with
meson inside a Python venv and exposes it via prefixed symlinks, but
local users have no equivalent and have to follow the Docker recipe
by hand.

Add an adi-ldr component that clones adsp-ldr v1.0.2 to
~/dev/adi-adsp-ldr, builds it with meson inside a venv and creates
the arm-linux-gnueabi-ldr and aarch64-linux-ldr symlinks needed to
match $(CROSS_COMPILE) on supported platforms. Each step is skipped
when its output is already present, so reruns are cheap, and -f
forces a rebuild. Output is captured to ~/dev/adi-adsp-ldr/build.log
in the same style as qemu-build. Update the README accordingly.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ci subcommand sets WORLD, SUITES, PYTEST and SJG_LAB pipeline
variables but has no control over the sage-lab stage, so the only
way to skip or target it is to edit the GitLab pipeline manually.

Add a -S/--sage [NAME] option that mirrors --sjg: bare -S enables
all sage-lab jobs, an explicit value selects a specific job (e.g.
'Raspberry Pi 4 (rpi_arm64)'), and 'help' lists the available
choices. Job names are discovered by parsing the included
.gitlab-ci-sage-lab.yml for top-level dictionary entries with a
variables section, so the list stays in sync with the lab
definition. SAGE_LAB is added to the default ci_vars and to
-a/--all, and a [skip-sage] tag is emitted in merge-request
descriptions when the stage is skipped.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two related cc subcommand bugs around mount handling:

First, 'cc -u <name>' removes the LXC device but the next 'cc' run
re-adds it from get_essential_mounts(), so unmounts do not stick
across restarts. Track explicitly-removed mounts in a per-container
skip file at ~/.claude/cc/<name>/skipped-mounts. add_all_mounts()
now consults this list, and an explicit 'cc -m' re-mount clears
the entry.

Second, 'cc -m' prints "Mounted ..." even when the lxc command
fails (e.g. when the host source path does not exist), giving the
illusion of success while the device is never created. add_mount()
now validates that the source path exists, checks the lxc return
code, returns a bool, and the CLI handler reports failure with a
non-zero exit.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The pytest subcommand ANDs the user-supplied -t spec with the gitlab
TEST_PY_TEST_SPEC for the board. For boards like qemu_arm64_spl with
TEST_PY_TEST_SPEC="test_passage", running 'uman py -t help' produces
'-k help and (test_passage)', which selects nothing.

Use the gitlab spec only as a fallback when the user does not supply
-t, so 'uman py -t help' simply runs '-k help'.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When 'lxc start' fails (e.g. a missing mount source path), cc keeps
going and falls into wait_for_user(), which loops forever running
'lxc exec' and never reports the underlying problem.

Make ensure_running() return whether the container is actually
running and abort the cc subcommand with an error if it is not. Also
give wait_for_user() a timeout so it cannot hang the run if 'lxc
exec' keeps failing.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the t subcommand cannot resolve a partial name like 'efi' to a
known suite, it scans all tests for any whose name contains the
search term and returns one spec per matching suite. The pattern it
builds, '{suite}_test_{search}*', assumes the matching tests start
with that exact prefix. Real tests like 'bootstd_test_bootflow_efi'
and tests in suites whose name itself contains 'efi' (e.g.
efi_unicoll_test_a) do not match the constructed prefix, so 'um t -b
-B sandbox efi' reports 'No tests found matching' for every suite
that should have matched.

Return the actual matching (suite, test_name) tuples from the scan
so each matching test runs as its own ut command, with its real
name, instead of relying on a constructed glob.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The progress line and final 'Results:' line always show all three of
'passed', 'failed' and 'skipped' even when the latter two are zero,
which adds visual noise to the common all-passing case.

Drop the failed and skipped fields when their count is zero. The
progress line now also clears to end of line so it does not leave
trailing characters when transitioning to a shorter form.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Progress class uses has_emit_result() to pick its counting
strategy at construction. When that returns False (older U-Boot tree
without 'emit_result' in cmd_ut.c), Progress counts every Test: line
as a pass, while parse_results() at the end still counts only
Result: lines. The two disagree when the framework emits Result:
lines despite the static check thinking it does not, leaving live
output like '24 passed' followed by 'Results: 2 passed'.

Make Progress prefer Result: lines as soon as it sees one, dropping
any Test:-line counts gathered before. The Test: fallback still
covers very old trees that emit no Result: lines at all.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The UTF_DM constant in cmdtest.py is 0x80, which is actually
UTF_OTHER_FDT (BIT(7)) in include/test/test.h

UTF_DM is BIT(6) = 0x40. As a result, predict_test_count()
misclassifies some tests: real UTF_DM tests are not double-counted
on flat tree under --full, and tests that happen to set
UTF_OTHER_FDT are double-counted incorrectly.

Set UTF_DM to 0x40 and add BIT() comments next to each constant so
the link to U-Boot's header is obvious.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Working on uman itself usually means inspecting build output under
/tmp/b, so the -o/--output flag is reached for almost every cc
invocation in this tree. For unrelated projects the mount has no
purpose and is just noise.

Default -o on when uman_pkg/__init__.py is present in the project
source. The new is_uman_project() check is also defeated cleanly by
-O, which calls remove_mount() and adds 'tmpb' to the per-container
skip list, so an explicit removal sticks across runs.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There is no convenient way to authorise the container to ssh into
another host. Doing it by hand means picking the right user, running
ssh-keygen inside the container, copying the public key out, then
appending it on the remote side.

Add a --ssh [USER@]HOST option to the cc subcommand. It generates an
ed25519 keypair under /home/ubuntu/.ssh in the named container if
one is missing, then runs ssh-copy-id from inside the container so
the remote authorised_keys is updated. When USER is omitted the
current host user name is used. The ssh-copy-id call runs without
output capture, so the destination password prompt is interactive.

The container has its own resolver and may not see the user's local
hostnames or /etc/hosts entries, so the host name is resolved on the
host side and an /etc/hosts entry is added inside the container if
the destination is not already a literal IP address.

Plain 'ssh HOST' from inside the container would otherwise pick up
the ubuntu default user. Add a matching 'Host'/'User' block to
/home/ubuntu/.ssh/config so the right user is selected automatically.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On upstream U-Boot, running 'um py -B sandbox bootstd' aborts during
pytest collection with:

    test/py/img/vbe.py:14: in <module>
        from u_boot_pylib import tools
    E   ModuleNotFoundError: No module named 'u_boot_pylib'

The u_boot_pylib module lives at <uboot>/tools/u_boot_pylib but
nothing puts <uboot>/tools on sys.path, so any test module that
imports it fails to load. Custom CI trees work because they set
PYTHONPATH elsewhere; upstream does not.

Always include <uboot>/tools in PYTHONPATH when it exists. The
--id hooks directory, when used, is layered in front so its
modules still take precedence.

Co-developed-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Subcommands section at the top of the README is missing the
build and test entries, even though both are first-class commands
with two-letter aliases (b and t). Readers scanning the table get
the wrong impression of what uman covers.

Add them, and while here sort the whole list alphabetically so the
order stays predictable as new commands are added.

Also broaden the setup description: it now installs apt packages,
toolchains, the adi-ldr tool and aliases, and deploys to remote
hosts, so the "firmware blobs (OpenSBI, TF-A, etc.)" wording is
too narrow.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sjg20
sjg20 merged commit 02c37d7 into master May 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant