Skip to content

Add container features, test improvements, and GDB fixes#2

Merged
sjg20 merged 36 commits into
masterfrom
emn
Mar 14, 2026
Merged

Add container features, test improvements, and GDB fixes#2
sjg20 merged 36 commits into
masterfrom
emn

Conversation

@sjg20
Copy link
Copy Markdown
Owner

@sjg20 sjg20 commented Mar 14, 2026

Summary

  • Embed u_boot_pylib and fix setup/alias issues
  • Git rebase helpers: rename alias, better errors, Python sequence editor
  • Setup: add remote deployment, list components
  • Test runner: flag detection, progress display, wildcards, malloc dump
  • Docker: new subcommand for CI container testing with gdbserver
  • GDB: execvp for Ctrl-C, split -g flag, silence SIGUSR2
  • CC containers: mount management (-m/-M/-u), patman, privileged mode,
    /tmp/b control, X11 clipboard, editor proxy, sudo hint, PulseAudio voice,
    GitHub/GitLab CLI tools

sjg20 added 30 commits March 14, 2026 12:42
Uman imports u_boot_pylib from $UBOOT_TOOLS (default ~/u/tools), but
that directory points to whatever U-Boot tree the user has checked out.
Older trees may lack features uman needs, or the directory may not
exist at all.

Copy u_boot_pylib into the uman repository so it always works
regardless of the U-Boot tree. Add UMAN_EXTERNAL_PYLIB=1 env var to
override and use the external version for testing newer library
versions. Update CLAUDE.md test commands to drop the PYTHONPATH prefix.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
When running inside a U-Boot tree, an older u_boot_pylib from the
tree's tools/ directory can end up on sys.path and take priority over
uman's embedded copy. This causes failures like missing CommandExc.

Use sys.path.insert(0, ...) instead of sys.path.append() so the
embedded u_boot_pylib always wins. The UMAN_EXTERNAL_PYLIB override
still works for testing newer versions.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The pickman module lives in the U-Boot tools directory but this path
is not on sys.path when using the embedded u_boot_pylib, since the
UMAN_EXTERNAL_PYLIB path addition in __main__.py is skipped.

Add the UBOOT_TOOLS path (defaulting to ~/u/tools) to sys.path in
do_merge_request() before importing pickman, so that 'um ci -sm' works
without needing UMAN_EXTERNAL_PYLIB=1.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The 'dh' alias for diff-head shadows /usr/bin/dh, the Debian build
helper command. Rename it to 'di' to avoid the conflict.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
When a rebase command fails with a return code but the output does not
match any known pattern (e.g. 'Could not apply'), the error is silently
swallowed. This happens with unstaged changes, where git prints
'error: cannot rebase: You have unstaged changes' but
show_rebase_status() has no handler for it.

Show the raw error output as a fallback when no pattern matches.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The sed -i flag has incompatible syntax between GNU sed (Linux) and
BSD sed (macOS). GNU sed uses 'sed -i "..."' while BSD sed requires
'sed -i '' "..."'.

Replace the sed commands with Python one-liners so the rebase
commands (rf, rn, rp, rb) work on both Linux and macOS.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
On first run (e.g. on a new machine), um is not in PATH yet so
shutil.which() fails. The fallback looks for 'um' in the repo root,
which is a symlink that may not exist.

Look for uman_pkg/uman first, since that is the actual executable
and is always present in the repo.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a 'remote' setup component that deploys uman to a remote machine
over SSH. It rsyncs the uman directory (excluding .git, caches and
build artifacts), creates the ~/bin/um symlink and runs setup aliases
on the remote.

Usage: um setup remote <hostname>

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Running 'um setup' with no component now lists the available
components instead of running them all. Use 'um setup all' to
run all components (excluding remote, which needs a hostname).

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Use an argparse argument group in add_build_opts() so the build-related
flags (-a, -f, -F, -j, -L, -o, -T, --no-trace-early) appear under a
'build options:' heading in the help output for the py and t subcommands.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The -F sandbox flag to skip flat-tree tests is not yet upstream. Running
'um t' against upstream U-Boot fails with "U-Boot does not support -F
flag" since build_ut_cmd() unconditionally adds it.

Add has_no_flat() to check whether arch/sandbox/cpu/start.c contains the
'noflat' callback, and only pass -F when the source tree supports it.
This mirrors how has_no_full() in cmdpy.py detects --no-full support for
the pytest path.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The -E ut flag to emit per-test result lines is not yet upstream.
Running 'um t' against upstream U-Boot fails because build_ut_cmd()
unconditionally adds -E, then run_tests() cannot parse results and
reports "No results detected".

Add has_emit_result() to check whether test/cmd_ut.c contains
'emit_result', and only pass -E when the source tree supports it. When
-E is unavailable, automatically fall back to legacy result parsing,
removing the need for the -L flag on older trees.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The upstream sandbox outputs a summary line like
'Tests run: 540, 11595 ms, average: 21 ms, failures: 7' but neither the
Result: parser nor the legacy '... ok' parser handles this format. When
-E is unavailable, run_tests() reports only 1 test because it falls
through to no-results handling.

Add parse_summary() to extract pass/fail counts from the 'Tests run:'
summary line, and use it as a final fallback in run_tests(). This gives
correct totals when running against upstream U-Boot without the -E
patch.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The legacy parser matches '... ok' anywhere in the output, which
incorrectly counts lines like 'Loading Environment from nowhere... OK'
as a passing test. This causes wrong results when running against
upstream U-Boot without the -E flag.

Require a 'Test:' prefix (via RE_TEST_NAME) before checking for the
result suffix, so only actual test output lines are counted.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
There is no feedback while tests run, which is unhelpful for large
suites like 'dm' with over 500 tests.

Add a Progress class that uses the output_func callback in
command.run_pipe() to parse sandbox output as it arrives, displaying a
running count of passed/failed/skipped tests that updates in place on
stderr. With -E, it counts Result: lines; without -E, it counts Test:
lines as passes and detects "Test '<name>' failed N times" lines as
failures. Progress is only shown when stderr is a TTY.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The progress line shows plain text with no indication of how many tests
remain.

Add colour to the passed/failed/skipped counts (green/red/yellow,
matching the final summary) and show the total expected test count
obtained from nm symbols. Add count_tests() to count matching tests for
the given specs.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The validate_specs() and resolve_specs() functions use endswith() to
match test patterns, which does not handle glob wildcards like 'adj*'.
This causes 'um t dm_test_adj*' to fail with 'No tests found matching'
even though matching tests exist.

Use fnmatch to support glob patterns in test specs.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a --malloc-dump FILE option that writes a malloc heap dump on
exit. For the t command this passes --malloc_dump to the sandbox
binary directly. For the py command it passes --malloc-dump to
test.py which forwards it to sandbox.

A %d placeholder in the filename is expanded to a sequence number,
so that each restart of U-Boot writes to a separate file.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Add a 'docker' (alias 'd') subcommand that runs U-Boot tests inside the
same Docker container used by GitLab CI. This parses .gitlab-ci.yml to
determine the Docker image and build/test script, so the local test
environment matches CI exactly.

The container bind-mounts the U-Boot source at /source and runs as the
current user. Flags include -B (board), -a (adjust-cfg), -x (exitfirst),
-s (show-output), -I (interactive shell), and -i (image override).

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Add -g flag to docker, sharing it with pytest via add_test_opts(). When
enabled, the docker container runs as root with --cap-add=SYS_PTRACE
and port 1234 exposed, allowing gdbserver to run inside the container.

For U-Boot debugging (-g or -g u-boot), a wrapper script replaces the
u-Boot binary so gdbserver starts only after SPL exec's the main
binary. For SPL debugging (-g spl), --gdbserver is passed to test.py
so it wraps the initial binary directly. In either case, a message
tells the user how to connect from another terminal using um py -G

Also factor out add_test_opts() for common test flags (test_spec, -B,
-g, -s, -x) shared between the docker and pytest subcommands.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
When launching gdb with -G, Ctrl-C is caught by uman's Python
process instead of being forwarded to gdb, making it unusable.

Replace the uman process with gdb using os.execvp() so that
signal handling is entirely under gdb's control.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The -g flag uses nargs='?' which makes argparse greedily consume the
next positional argument as its optional value. This means that
'um py -bg not slow' sets gdb_phase='not' and test_spec=['slow'],
silently dropping the 'not' from the filter expression.

Split -g into a simple store_true flag (always u-boot phase) and a
separate --gdb-phase option with explicit choices (spl, tpl, vpl).
The post-parse logic reconciles both into gdb_phase. This means
'-g spl' becomes '--gdb-phase spl', but '-bg not slow' now correctly
keeps 'not slow' as the test_spec.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Sandbox uses SIGUSR2 internally for coroutine setup, which causes gdb
to stop repeatedly with signal notifications during debugging sessions.

Add a gdb init command to handle SIGUSR2 as nostop/noprint/pass so
debugging is not interrupted by these expected signals.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
There is no way to mount extra host directories into a container from
the command line without editing ~/.uman config.

Add a -m/--mount flag that accepts HOST:DEST or just HOST (mounted at
the same path inside the container). The flag can be repeated for
multiple mounts. Tilde in the destination expands to the container home
(/home/ubuntu) rather than the host home. The device name is derived
from the leaf directory (e.g. 'linux' for ~/dev/linux) with a numbered
suffix for duplicates. Used alone, -m adds the mount without entering
the container; combine with -s to also launch a shell.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
There is no easy way to see what directories are mounted into a
container.

Add a -M/--mounts flag that parses 'lxc config device show' output and
displays a table of device name, source path and container path. Host
home paths are shortened to ~ for readability.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
There is no way to remove a CLI mount once added.

Add a -u/--unmount flag that takes a device name (as shown by -M) and
removes it with 'lxc config device remove'. Report an error if the
device or container does not exist.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Patman is not available inside cc containers, so patch workflows that
need it fail.

Mount ~/dev/patman into the container (if present) and create a symlink
at ~/.local/bin/patman so it is on the PATH.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Tests that use LUKS2 encryption need device-mapper, which is not
available inside LXD containers by default.

Add -p/--privileged to enable privileged mode with security.nesting,
AppArmor and seccomp overrides for device-mapper access. Add
-P/--no-privileged to restore normal unprivileged operation, clearing
the overrides and restoring the uid/gid idmap. Show [privileged]
in the container listing output.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The /tmp/b mount is used to access build output from the host. It
was previously always mounted, which is disruptive when not needed.

Add -o to explicitly mount /tmp/b and -O to remove it. Without
either flag the mount state is left unchanged.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Claude Code uses the clipboard for image paste (Ctrl-V), which
requires X11 access. Without it, pasting images fails with 'No
image found in clipboard'.

Mount the X11 socket, install xclip, and export DISPLAY into the
container environment so clipboard operations work.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
sjg20 added 4 commits March 14, 2026 12:43
When running Claude Code inside an LXC container, there is no way to
open files in the host editor for review or editing.

Add an editor proxy that listens on a Unix socket in the project
directory. A lightweight script installed in the container sends file
paths over the socket, and the host-side listener translates container
paths to host paths and opens $EDITOR. The socket is cleaned up when
the container session ends. Paths outside the project are rejected.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The 'sudo -iu' used to launch shells triggers Ubuntu's "To run a
command as administrator" message on every session.

Touch ~/.sudo_as_admin_successful during container setup to silence
this hint while keeping the login shell behaviour.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Claude Code's /voice command needs microphone access via SoX. There is
no audio stack inside the container.

Mount the host PulseAudio socket into the container, install sox with
the PulseAudio format plugin, and set PULSE_SERVER, AUDIODRIVER and
ALSA_CONFIG_PATH in the container environment so that SoX uses
PulseAudio directly and ALSA hardware-probe errors are suppressed.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
The gh and glab CLI tools are useful for managing pull requests and
CI pipelines from inside the container.

Add them to the default package list.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
@sjg20 sjg20 changed the title Emn Add container features, test improvements, and GDB fixes Mar 14, 2026
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
@sjg20 sjg20 force-pushed the emn branch 2 times, most recently from 04de570 to f639285 Compare March 14, 2026 19:27
Run pytest and pylint on push and pull requests against master,
testing on Python 3.10 and 3.12.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
@sjg20 sjg20 merged commit b649b42 into master Mar 14, 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