. "$BST_LIB" # BST-TIMEOUT: 900 # TWO interpreter starts, and those starts are the whole # # cost: python3 on this image is one ~59 MB static binary # # and a start has been minutes on a loaded guest, so the # # runner's default bound would report the port as broken # # when what ran out was the clock. Same reasoning, and the # # same number, as 99-mercurial. require guider guider/present # guider 3.9.8+git (meta-oe recipes-devtools/guider, SRCREV a502cd93, NO # bbappend) on wasm32-hwjs -- the pure-Python system-wide performance analyzer, # running on the ported Python 3 interpreter (BRINTOS-1476). It is one # guider.py of 80 131 lines / 2 675 535 bytes plus a shell launcher in # ${bindir}, and nothing else. # # WHAT THIS SUITE IS WRITTEN TO. guider's modes split in two and only one half # is reachable here. The PROFILE and TRACE modes go through mountDebugfs() and # want ftrace, which this kernel does not provide; the reporting modes read # /proc and print, and those are what the port closes on. The tracing half is # stated in section 4 rather than dropped silently. # # TWO STARTS, NOT FIVE, AND THAT IS THE BINDING CONSTRAINT ON THE SHAPE. The # guest does not return memory on process exit, so interpreter starts are what a # python suite is budgeted in: six of them wedged the docutils lane outright and # it emitted no case at all (BRINTOS-1751), and 99-mercurial is built to two for # the same reason. So this suite spends one start on a canary that reads no # /proc, and one on the report that carries the most per start -- and the modes # it does not run are named in section 4 with what they were measured to do, # rather than being covered at the price of a wedged guest. # # `top -R 2` IS THAT ONE REPORT BECAUSE ITS HEADER IS systat's HEADER. The # [Top Info] line is emitted by the same printer for both modes and carries # [Core:] from /proc/stat and [Load:] from /proc/loadavg -- the two figures the # port is asked to cross-check -- plus [RAM:]/[Swap:] from /proc/meminfo, and # the Total CPU row under it. -R 2 is what makes the mode terminate on its own, # and -g pins the process table to a task this suite started so the row asserted # below cannot be crowded out by guider's own CPU ordering. What running `top` # instead of `systat` gives up is the per-core block and the mount table; what # it buys is a start, and the header cross-checks are unaffected. # # THE RUN IS BOUNDED BY bst_probe, AND THAT IS THIS SUITE'S SAFETY. guider's # system modes walk /proc//{stat,status,smaps,io,schedstat,cgroup,ns} for # EVERY task -- 4729 /proc opens for one report on the x86-64 control -- which # is exactly the many-read shape that hangs on this arm (brintos/linux#9: the # kernel's view of the signal mask is stale-empty, a masked signal is delivered # mid-read and the read returns EINTR unrestarted). 85-procps meets that shape # by NOT RUNNING top/vmstat/pgrep/`ps -e` at all, because a hung tool wedges the # runner. bst_probe is the third option: it bounds the child on /proc/uptime and # SIGKILLs it at the bound, so the mode is measured, the transcript survives # whatever happened, and the cases flip green by themselves if #9 closes. A # permanent skip could never do that. # # NOTHING IS WRAPPED IN timeout(1), for the reason 85-nmon and 85-procps both # record: coreutils timeout arms a SIGALRM and wait()s, and on this runtime that # signal path itself hangs, so the wrapper adds a second hang rather than # bounding the first. # # EVERY EXPECTED VALUE BELOW WAS MEASURED, NOT READ OFF THE SOURCE. The control # is an x86-64 run of the identical guider 3.9.8 source on CPython 3.12.3, # recorded 2026-09-01 and repeated under a namespace root; the packaging figures # come from this port's own guider_3.9.8+git0+a502cd93b1-r0_wasm32.deb rather # than from the recipe. Section 1's 28 cases start no interpreter at all, so # they still report on a boot where python3 will not run. # # NON-INTERACTIVE SUBJECT: no manual browser pass is claimed, and no follow-on # interactive-test ticket is filed. Deferred -- no manual pass (headless run). SITE=/usr/lib/python3.12/site-packages PKG=$SITE/guider DI=$SITE/guider-3.9.8.dist-info LAUNCH=/usr/bin/guider work=${TMPDIR:-/tmp}/bst-guider.$$ rm -rf "$work"; mkdir -p "$work" || { bst_notok guider/workdir "cannot create $work"; exit 0; } # has : ok iff contains the literal . has() { check "$1" grep -qF "$2" "$3"; } # ------------------------- 1. what the port SHIPS: no interpreter started ----- # The whole of the package, asserted against the .deb the build produced. A # setuptools3 recipe over a pure-Python tree emits one runtime package and the # bare name is all of it -- guider-dev and guider-dbg exist and carry nothing # this image installs. check guider/launcher-file test -f $LAUNCH check guider/launcher-exec test -x $LAUNCH check_eq guider/launcher-size "905" "$(wc -c < $LAUNCH 2>/dev/null | tr -d ' ')" # THE LAUNCHER HAS NO SHEBANG, and that is upstream's doing rather than a # packaging fault. setup.py declares guider/guider under `scripts=`, so # setuptools copies it whole and only rewrites a first line that already begins # `#!`; this one begins with a comment. execve(2) therefore returns ENOEXEC and # the shell's fallback is what has to run it -- a fallback that is broken on this # runtime (brintos/linux#34), which is why section 2 measures it on a control of # its own before choosing how to enter the launcher. A version that grew a # shebang would change how the program starts on every image, so it is pinned. check_eq guider/launcher-no-shebang "# Guider Launcher" "$(head -n 1 $LAUNCH 2>/dev/null)" check guider/site-dir test -d $SITE check guider/package-dir test -d $PKG check guider/mod-init test -f $PKG/__init__.py check guider/mod-main test -f $PKG/__main__.py check guider/mod-guider test -f $PKG/guider.py # The whole program is ONE module, and its size is the SRCREV pin restated in a # form the image can check: a502cd93's guider.py is 2 675 535 bytes exactly. check_eq guider/mod-size "2675535" "$(wc -c < $PKG/guider.py 2>/dev/null | tr -d ' ')" # THE BUILD-TIME BYTECODE SHIPS, one .pyc per module. It matters more here than # on a small package: guider.cpython-312.pyc is 2 629 999 bytes, and an image # that stopped shipping it would pay a compile of an 80 131-line module on the # first import, on an arm where the interpreter start is already the expensive # part. check guider/pycache-dir test -d $PKG/__pycache__ check_eq guider/pycache-count "3" \ "$(ls $PKG/__pycache__/*.pyc 2>/dev/null | wc -l | tr -d ' ')" check guider/pycache-main test -f $PKG/__pycache__/guider.cpython-312.pyc check guider/distinfo-dir test -d $DI check guider/distinfo-record test -f $DI/RECORD check guider/distinfo-metadata test -f $DI/METADATA check guider/distinfo-wheel test -f $DI/WHEEL check guider/distinfo-license test -f $DI/LICENSE check_eq guider/distinfo-toplevel "guider" "$(cat $DI/top_level.txt 2>/dev/null)" has guider/metadata-version "Version: 3.9.8" "$DI/METADATA" # Root-Is-Purelib is the packaging's own statement that nothing here is # arch-specific, which is the claim this port rests on. has guider/wheel-purelib "Root-Is-Purelib: true" "$DI/WHEEL" has guider/wheel-tag "Tag: py3-none-any" "$DI/WHEEL" # RECORD reaches OUT of site-packages for the launcher, which is how the one # file in ${bindir} is accounted for. has guider/record-launcher "../../../bin/guider" "$DI/RECORD" # The version the code reports, asserted on the source with no interpreter -- so # section 2's banner has something to be checked against even on a boot where # python3 will not start. has guider/source-version '__version__ = "3.9.8"' "$PKG/guider.py" # NO COMPILED EXTENSION, ANYWHERE. The upstream tree carries guiderLib.c and # guiderMod.c for an optional C helper; `packages=find_packages()` ships neither # them nor anything built from them, and this image's python3 is one static # binary with an empty lib-dynload and no dynamic loader (BRINTOS-1476), so a # module carrying an accelerator could not be imported here however cleanly it # built. Asserted over the INSTALLED tree rather than over the recipe, so a # version that grew one fails loudly instead of at `import` three tickets later. check_eq guider/no-shared-object "" "$(find $PKG -name '*.so' 2>/dev/null)" bst_lib_shape guider/lib-shape "$PKG/*.so" "$PKG/*/*.so" check_eq guider/no-c-sources "" "$(find $SITE -name 'guiderLib.c' -o -name 'guiderMod.c' 2>/dev/null)" # guider.conf is in the upstream tree and is NOT package data, so it does not # ship. Pinned so that a recipe which started installing it shows up as a change. check_eq guider/no-conf-shipped "" "$(find $PKG -name 'guider.conf' 2>/dev/null)" # The tracing facility section 4 is about, asserted with no interpreter so that # it reports even on a boot where guider cannot start. check_eq guider/no-ftrace "" "$(ls -d /sys/kernel/debug/tracing 2>/dev/null)" # The FFI facility SECTION 3 needs, asserted the same way and for the same # reason. python3-ctypes IS installed here and ships Lib/ctypes, but this # distro's python3 is configured WITHOUT the _ctypes extension: wasmld refuses # libffi's ffi_closure_alloc, so the module cannot link into the static # interpreter at all -- not "aborts at the first foreign call", cannot link. # The exclusion and its BRINTOS-WORKAROUND marker live in python3_%.bbappend # (hwjs-cc#15). lib-dynload is empty image-wide, so the absence is a file-level # fact and establishing it costs no interpreter start; when #15 closes and # _ctypes is built, this case goes red and section 3 starts reporting for real. check_eq guider/no-ctypes "" "$(find /usr/lib/python3*/lib-dynload -name '_ctypes*' 2>/dev/null)" # ------------------------- 2. start one: launcher + import + version ---------- # The one mode that reads no /proc and needs no root. It proves three things at # once: the launcher's `which python3` resolves on this image, `python3 -m # guider` unmarshals 2.6 MB of bytecode, and the program reports 3.9.8. It is # also the canary for section 3 -- if the module does not import, a second # bounded start would spend the rest of the boot learning that again. # # HOW THE LAUNCHER IS ENTERED IS MEASURED FIRST, ON A CONTROL, because on this # runtime it is the ENTRY and not guider that fails. The batch-1871 verify of # 2026-09-01 ran `guider help` by name and got rc=132 (SIGILL), with the # runtime's own console naming the trap: `memory access out of bounds` at # `busybox_unstripped.__stpcpy.resume` -- the module that trapped is the SHELL, # so python3 was never started and nothing about guider was measured. That is # ash's ENOEXEC fallback for a shebang-less script (brintos/linux#34, where the # netcat port recorded the same fallback failing in its milder `not found` # form). The control below is two lines of sh with no shebang and nothing to do # with guider, so it attributes the entry failure to the platform rather than to # this package; when it cannot run, the launcher is entered with an explicit # `sh` and the 19 cases that measure guider itself report instead of going dark # behind a shell defect. When #34 closes the control passes, the by-name entry # is used again and the case flips green by itself. ctl=$work/noshebang.ctl printf '# no shebang\nexit 0\n' > "$ctl" chmod +x "$ctl" 2>/dev/null # Backgrounded exactly as bst_probe stages its own child: this runtime turns the # trap into kill(0, SIGILL) territory, and the suite must not be the process # that runs the control. rm -f "$ctl.rc" { "$ctl" >/dev/null 2>&1; echo $? > "$ctl.rc"; } & wait ctl_rc=1 [ -s "$ctl.rc" ] && read -r ctl_rc < "$ctl.rc" 2>/dev/null case "$ctl_rc" in ''|*[!0-9]*) ctl_rc=1 ;; esac ghelp=$work/help.out if [ "$ctl_rc" -eq 0 ]; then bst_ok guider/enoexec-fallback bst_probe guider/help-run "$ghelp" 300 guider help help_rc=$? else bst_notok guider/enoexec-fallback "brintos/linux#34: this shell cannot run an executable shebang-less script -- measured on a two-line control that never mentions guider (rc=$ctl_rc), so the launcher is entered below with an explicit sh" # $0 is still the launcher's own path under `sh $LAUNCH`, so # guider/help-cmdline below remains the case that tells the launcher apart # from `python3 -m guider`. bst_probe guider/help-run "$ghelp" 300 sh "$LAUNCH" help help_rc=$? fi if [ "$help_rc" -eq 0 ]; then has guider/help-title "G.u.i.d.e.r" "$ghelp" has guider/help-version "ver.3.9.8" "$ghelp" has guider/help-commands "COMMAND:" "$ghelp" has guider/help-monitor "[MONITOR]" "$ghelp" has guider/help-systat "systat" "$ghelp" # THE USAGE LINE NAMES THE LAUNCHER, which is the proof that the launcher ran # rather than `python3 -m guider`: the script exports CMDLINE=$0 and guider # renders the usage from it. Invoked as a module it prints __main__.py # instead, so this case distinguishes the two entry points. has guider/help-cmdline "$LAUNCH" "$ghelp" else for c in help-title help-version help-commands help-monitor help-systat help-cmdline; do bst_notok "guider/$c" "the launcher run did not return 0, so its transcript was not asserted" done fi # THE LAUNCHER'S STATUS ALONE CANNOT BE THE CANARY: upstream's script ends in a # bare `exit 0` after `$PYTHON -m guider $*`, so it returns 0 whatever the # interpreter did -- and entered through `sh` it is the script's status that is # reported rather than the trap that used to stand in for it. So section 3 is # gated on the version banner actually being in the transcript, which is what # says the module imported. canary_rc=$help_rc if [ "$canary_rc" -eq 0 ] && ! grep -qF "ver.3.9.8" "$ghelp" 2>/dev/null; then canary_rc=1 fi # ------------------------- 3. start two: top -R 2, the /proc report ----------- # REMOTERUN is what makes the report tables plain ASCII: guider keys its ANSI # colour off `'REMOTERUN' in os.environ`. It is set HERE and not before section # 2, because it also drops the title box that `help` prints the version in. REMOTERUN=1 export REMOTERUN if [ "$canary_rc" -ne 0 ]; then for c in top-run top-reports top-banner top-filterline top-table top-row top-comm \ top-header top-core top-ram top-swap top-load15 top-cputable; do bst_notok "guider/$c" "not run: the section-2 canary (guider help) printed no version banner, so the module does not import on this boot" done else sleep 200 & sp=$! gtop=$work/top.out bst_probe guider/top-run "$gtop" 300 python3 -m guider top -R 2 -g "$sp" # The banner is printed before any /proc work, so it holds on both arms below. has guider/top-banner "ver.3.9.8" "$gtop" # THE REPORT MODES REACH /proc THROUGH ctypes, AND THIS IMAGE HAS NONE. # SysMgr.loadLibcObj() is cdll.LoadLibrary() over Lib/ctypes; with no _ctypes # (guider/no-ctypes in section 1, hwjs-cc#15) it prints `[ERROR] fail to load # libc` and then EXITS 0 -- guider returns 0 from every error path -- so # guider/top-run is green and NO report is emitted. Read from guider's own # transcript rather than predicted: the message IS the measurement, and when # #15 closes it stops appearing and the assertions below run unchanged. if grep -qF "fail to load libc" "$gtop" 2>/dev/null; then bst_notok guider/top-libc "hwjs-cc#15: every /proc report loads libc through ctypes, and this image's python3 is configured without _ctypes (wasmld refuses libffi ffi_closure_alloc); guider printed \"fail to load libc\" and exited 0, so no report was produced" # LEFT FAILING RATHER THAN SKIPPED, deliberately: an attributed red is the # regression signal this suite is buying (ticket-scoping-and-gates, "a # partially-red self-test"). Each flips green on its own the day hwjs-cc#15 # lands and the else-arm below starts asserting them for real. for c in top-reports top-filterline top-table top-row top-comm top-header \ top-core top-ram top-swap top-load15 top-cputable; do bst_notok "guider/$c" "hwjs-cc#15: no report to assert against -- see guider/top-libc; the mode needs ctypes and this image has no _ctypes" done kill "$sp" 2>/dev/null else bst_ok guider/top-libc # -R 2 means exactly two reports and then exit; the count IS the assertion # that the mode terminated on its own rather than being cut off at the bound. got_reports=$(grep -cF '[Top Info]' "$gtop" 2>/dev/null | tr -d ' ') check_eq guider/top-reports "2" "$got_reports" has guider/top-filterline "are shown" "$gtop" has guider/top-table "Process (" "$gtop" # The row itself. guider prints ` ( / / ...`, so the pid # is matched with its parenthesis and slash rather than as a bare number, # which would also match a PPID or a byte count. got_row=$(grep -c "( *${sp}/" "$gtop" 2>/dev/null | tr -d ' ') if [ "${got_row:-0}" -ge 1 ]; then bst_ok guider/top-row else bst_notok guider/top-row "no process row for the pid this suite started ($sp)"; fi got_comm=$(grep -c "sleep ( *${sp}/" "$gtop" 2>/dev/null | tr -d ' ') if [ "${got_comm:-0}" -ge 1 ]; then bst_ok guider/top-comm else bst_notok guider/top-comm "the row for pid $sp does not name sleep as its comm"; fi kill "$sp" 2>/dev/null # --- the header, cross-checked against the /proc files it is derived from --- hdr=$(sed -n '/\[Top Info\]/{p;q;}' "$gtop" 2>/dev/null) if [ -n "$hdr" ]; then bst_ok guider/top-header else bst_notok guider/top-header "no [Top Info] line in the transcript"; fi got_core=$(printf '%s\n' "$hdr" | sed -n 's/.*\[Core: \([0-9]*\)\].*/\1/p') got_ram=$(printf '%s\n' "$hdr" | sed -n 's/.*\[RAM: \([^]]*\)\].*/\1/p') got_swap=$(printf '%s\n' "$hdr" | sed -n 's/.*\[Swap: \([^]]*\)\].*/\1/p') got_load=$(printf '%s\n' "$hdr" | sed -n 's/.*\[Load: \([^]]*\)\].*/\1/p') # Core is getNrCore(): the /proc/stat lines whose first field starts with # "cpu" and is not exactly "cpu". Reproduced here rather than pinned to a # number, because the guest's core count is a property of the runtime and not # of guider. want_core=$(awk '$1 ~ /^cpu[0-9]/ {n++} END {print n+0}' /proc/stat 2>/dev/null) check_eq guider/top-core "$want_core" "$got_core" # RAM and Swap are convSize2Unit(MemTotal<<10) and convSize2Unit(SwapTotal<<10), # whose float branch is '%.1fG' % ((bytes>>20)/1024.0) with the >>20 an integer # floor -- reproduced exactly below, including the sub-KiB branch that prints a # bare '%d' and is what a guest with no swap gets. gconv() { awk -v key="$1" '$1 == key ":" || $1 == key {kb=$2} END { b = kb * 1024 if (b >= 1073741824) printf "%.1fG", int(b/1048576)/1024 else if (b >= 1048576) printf "%.1fM", int(b/1024)/1024 else if (b >= 1024) printf "%.1fK", b/1024 else printf "%d", b }' /proc/meminfo 2>/dev/null } check_eq guider/top-ram "$(gconv MemTotal)" "$got_ram" check_eq guider/top-swap "$(gconv SwapTotal)" "$got_swap" # Load is '%d' % float(x) over the first three /proc/loadavg fields -- a # truncation, not a rounding. Only the 15-minute figure is compared: it is the # one whose time constant makes it immune to the seconds between guider's read # and this one, and an equality on the 1-minute figure would be a race rather # than an assertion. want_load15=$(awk '{printf "%d", $3}' /proc/loadavg 2>/dev/null) got_load15=$(printf '%s\n' "$got_load" | awk -F/ '{printf "%s", $3}') check_eq guider/top-load15 "$want_load15" "$got_load15" has guider/top-cputable "CPU(Usr/Ker/Blk/IRQ)" "$gtop" fi fi # ------------------------- 4. the modes this port does NOT run --------------- # Following 85-procps, a mode that is present and deliberately not executed is # reported as a skip carrying the reason, rather than being left out of the # summary. None of these is a defect in the port and none is a platform red. # # THE PROFILE AND TRACE MODES (rec, funcrec, sysrec, strace, utrace, btrace, # sigtrace, ...) need ftrace through debugfs, which guider/no-ftrace above # asserts this image does not have. What they DO here was measured on the # control with ftrace removed -- twice, once with /sys/kernel/debug replaced by # an empty tmpfs and once with the whole of /sys/kernel replaced: guider prints # its mode banner, emits NO diagnostic at all, and EXITS 0. mountDebugfs()'s # failure path is `printErr(...); sys.exit(0)`, and guider returns 0 from every # error path it has -- so a run here would cost a third interpreter start to # assert a silent exit that is upstream's and reproduces off this arm. bst_skip guider/trace-modes "present; need ftrace through debugfs, which this kernel does not provide -- measured on a control with ftrace removed, guider prints its mode banner, emits no diagnostic and exits 0" # `guider mem` IS NOT A SYSTEM MEMORY REPORT, which is worth recording because # it reads like one. Measured on the control: with no argument it exits with # "fail to recognize target because no pid"; given a pid it renders THAT # process's /proc//maps, folding the consecutive mappings of one file into # one area row (25 lines of maps came back as 10 rows). So the /proc/meminfo # cross-check this port owes lives in section 3's [RAM:] field, and covering # this mode as well would cost a third interpreter start for a second reading of # /proc by the same code. bst_skip guider/mem-mode "present; a per-process /proc//maps reporter rather than a system memory report -- not run, to hold this suite to two interpreter starts (the /proc/meminfo cross-check is guider/top-ram)" rm -rf "$work"