# BST-TIMEOUT: 300 # # MEASURED, NOT GUESSED: this suite reported # `==BST-SUITE dbus elapsed=30s bound=300s cases=53==' on a wasm32-hwjs guest # (local-verify, 2026-09-10). 300 is deliberately ten times that rather than the # corpus's usual three, because what this file spends is dominated by PROCESS # SPAWNS -- a daemon, a probe, a monitor and about forty dbus-send invocations -- # and a spawn here is a fresh module instantiation whose cost is the most # variable thing on this substrate. 30 s is also only half the 60 s default, so # without the marker a guest merely twice as slow as this one would trip it. # # dbus 1.14.10 (poky meta/recipes-core/dbus, autotools) on wasm32-hwjs -- # BRINTOS-2601. libdbus-1, dbus-daemon, and the dbus-send / dbus-monitor / # dbus-launch tools. # # THE SUITE CLOSES ON ITS OWN FIXTURES AND REACHES NO PEER OUTSIDE ITSELF. It # writes a session.conf of its own, starts a dbus-daemon on a unix socket of its # own under $TMPDIR, and stops it again. Nothing here touches the system bus, # /run/dbus, any device or any terminal, and the config file names no # -- so the bus can activate nothing and every # peer on it is a process this file started. # # THE ARGUMENT-TYPE CASES ARE A REAL ROUND TRIP THROUGH TWO IMPLEMENTATIONS, # not a self-check. Nothing already on the image answers a method call: the bus # driver's own interface takes only `s' and `u', and dbus-send cannot serve. So # data/dbus-probe (src/dbus_probe.c, cross-linked against the very # /usr/lib/libdbus-1.a this port installs) supplies the service, and dbus-send # stays the caller. Each type is therefore marshalled by dbus-send, # demarshalled and re-marshalled by libdbus in the probe, then demarshalled and # printed by dbus-send -- and the case compares that printed form BYTE-EXACTLY. # # EVERY EXPECTED STRING BELOW WAS MEASURED, NOT PREDICTED. They come from an # x86-64 control built from this same src/dbus_probe.c against the host's # libdbus (2026-09-10), driven by these same dbus-send command lines. The # control's dbus was 1.16.2 and the target's is 1.14.10, so the formats were # then re-read out of 1.14.10's OWN tools/dbus-print-message.c rather than # assumed to have been stable: every scalar (`int16 %d', `uint64 %PRIu64', # `double %g', `boolean true/false', `object path "', `signature "', # `variant ') and every container (`array [' / `]', `dict entry(' / `)', # `struct {' / `}') is identical between the two. What the target can still # change is the VALUES, which is the thing under test. # # ONE FORM DELIBERATELY NOT EXERCISED, because it is a trap for the next person # to add a case here: an array of BYTE does not print as `array [ byte .. ]'. # dbus-print-message.c:93,155,166 gives it three special forms of its own # (`array of bytes [', `array of bytes ".."', and a `+ \0' variant for a # NUL-terminated one), chosen by whether the content is printable. dbus-send # can send `array:byte:1,2', so the case looks available; its expected string # is not the one the pattern above would lead you to write. # # A # substring test would pass against a reply carrying the right type and the # wrong value, or the arguments in the wrong order; comparing the whole # volatile-free tail does not. Only dbus-send's first line is dropped -- it # carries the timestamp, the sender's unique name and two serial numbers, and # nothing else in the output moves between runs. # # TWO OF THE BUS'S OWN TYPES ARE ORIGINATED BY THE PROBE RATHER THAN ECHOED, # because dbus-send has no syntax for sending them: STRUCT and SIGNATURE. They # are covered by MakeStruct and MakeSignature, which take no argument and put # one in the reply, so the marshalling is still asserted in the direction that # can be. The one type NOT covered at all is UNIX_FD (`h'): the build has # HAVE_UNIX_FD_PASSING, but dbus-send cannot send a file descriptor and the # probe has none to pass, so nothing here would be measuring dbus. # # THREE NEGATIVE CONTROLS, because everything above would pass vacuously # against a bus that says yes to whatever it is handed: a call to a name nobody # owns must come back ServiceUnknown, a call to a method the probe does not # implement must come back UnknownMethod, and a call after the daemon has been # stopped must fail rather than succeed. # # NOTHING HERE SLEEPS FOR A DURATION and nothing uses timeout(1). `sleep N' # does not sleep on this image (linux-in-the-browser#120), and timeout(1)'s # alarm(2) was unwired before the brintos/linux#77 fix, so every wait below is # an ITERATION COUNT whose sleep is only a yield, and the one command that could # hang is run under bst_run. # # `kill -0' IS NOT USED TO ASK WHETHER THE DAEMON IS ALIVE -- it succeeds on a # zombie, so a daemon that died at startup would read as running. Read # /proc//status, the same way 83-dlt-daemon does. # # THE SOCKET PATH IS LENGTH-CHECKED FIRST. sun_path is 108 bytes and # dbus-daemon's failure for an over-long one is "Failed to start message bus: # Socket name too long" with no socket and no other clue. Measured while # building the x86-64 control, where $TMPDIR was long enough to trip it. The # case exists so a future image with a longer $TMPDIR reds with the cause named # rather than as a daemon that would not start. . "$BST_LIB" require dbus-daemon dbus/present D=${TMPDIR:-/tmp}/bst-dbus.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok dbus/workdir "cannot create $D"; exit 0; } SOCK=$D/bus running() { case "$(sed -n 's/^State:[ \t]*//p' "/proc/$1/status" 2>/dev/null)" in "") return 1 ;; # gone entirely Z*) return 1 ;; # exited, awaiting reap -- dead for every purpose here *) return 0 ;; esac } # Nothing this suite starts may outlive it. dbus-daemon --nofork, dbus-monitor # and the probe all run until told otherwise, and if the runner kills this file # at its own bound they would survive into the NEXT suite on the same guest, # holding memory the guest never returns (README, Sharding). The trap is the # only thing that runs on that path -- the tidy-up at the foot of the file does # not. cleanup() { [ -n "$MPID" ] && kill "$MPID" 2>/dev/null [ -n "$QPID" ] && kill "$QPID" 2>/dev/null [ -n "$DPID" ] && kill "$DPID" 2>/dev/null rm -rf "$D" } DPID=""; QPID=""; MPID="" trap cleanup EXIT HUP INT TERM # wait_for -- poll a file this suite owns # until one of its lines matches, with ONLY BUILTINS in the loop body. # # That constraint is the whole reason this is a function rather than an inline # grep loop. bst.sh is explicit about it: a relative sleep placed directly after # the shell reaps a forked child returns instantly instead of sleeping # (linux-in-the-browser#120), so a loop that greps and then sleeps does not wait # at all -- it spins as fast as the guest schedules it and grows the host # process without bound (brintos/hardwarejs#20). read, case, [ and sleep are # builtins; grep is not. wait_for() { wf_n=0 while [ "$wf_n" -lt "$3" ]; do wf_hit=0 while IFS= read -r wf_l; do case "$wf_l" in $2) wf_hit=1 ;; esac # The marker is read on the redirect's OWN line or the one immediately above # it, so it belongs here rather than above the `while' -- which is where it # was, and the build-time gate rejected the suite for it. # BST-REDIRECT-OK: $1 is always a file under $D, which is under $TMPDIR. done < "$1" [ "$wf_hit" -eq 1 ] && return 0 wf_n=$((wf_n + 1)) sleep 1 done return 1 } # sv [arg...] -- a method call on the probe's object. sv() { dbus-send --session --print-reply --dest=io.brintos.SelfTest \ /io/brintos/SelfTest io.brintos.SelfTest."$@" 2>&1 } # db_case [arg...] -- compare the reply's # volatile-free tail, flattened to one line, against what the control produced. db_case() { db_id=$1; db_want=$2; shift 2 db_raw=$(sv "$@") # Drop the first line ONLY when it is the volatile method-return header. An # error reply is a SINGLE line and is the whole of what dbus-send says, so # dropping it unconditionally turns every diagnosable failure into an empty # got: -- the one result a reader cannot act on. The likeliest target failure, # a probe that never answers, arrives as exactly that single line # (org.freedesktop.DBus.Error.NoReply). case "$db_raw" in "method return"*) db_got=$(printf '%s\n' "$db_raw" | sed -n '2,$p') ;; *) db_got=$db_raw ;; esac db_got=$(printf '%s\n' "$db_got" | tr -s ' \t\n' ' ' | sed 's/^ *//; s/ *$//') check_eq "dbus/$db_id" "$db_want" "$db_got" } # ------------------------------------------------------------- the tool set for t in dbus-daemon dbus-send dbus-monitor dbus-launch dbus-uuidgen \ dbus-run-session dbus-cleanup-sockets dbus-update-activation-environment; do if command -v "$t" >/dev/null 2>&1; then bst_ok "dbus/have-$t" else bst_notok "dbus/have-$t" "not on PATH"; fi done check_out dbus/version "1.14.10" dbus-daemon --version check_out dbus/launch-version "1.14.10" dbus-launch --version # The three deliverables that are files rather than behaviour: the static # archive is the whole of the library on this arm (the -lib package is empty # because --disable-shared leaves no lib*.so.*), plus the public header and the # pkg-config file a consumer needs to find it. check dbus/staticdev-archive test -f /usr/lib/libdbus-1.a check dbus/dev-header test -f /usr/include/dbus-1.0/dbus/dbus.h check dbus/dev-pkgconfig test -f /usr/lib/pkgconfig/dbus-1.pc # dbus-common's two shipped configurations. for f in /etc/dbus-1/session.conf /etc/dbus-1/system.conf; do if [ -f "$f" ]; then bst_ok "dbus/have-$(basename "$f")" else bst_notok "dbus/have-$(basename "$f")" "absent"; fi done # dbus-uuidgen is a whole tool whose entire output is one 32-character hex id. uuid=$(dbus-uuidgen 2>&1) case "$uuid" in [0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]) bst_ok dbus/uuidgen ;; *) bst_notok dbus/uuidgen "not a 32-char hex id: $(_bst_1line "$uuid")" ;; esac # ----------------------------------------------------- the private session bus if [ "${#SOCK}" -gt 100 ]; then bst_notok dbus/sock-path-fits "\$TMPDIR makes $SOCK ${#SOCK} bytes; sun_path holds 108" else bst_ok dbus/sock-path-fits fi CFG=$D/session.conf { echo '' echo '' echo ' session' echo " unix:path=$SOCK" echo ' EXTERNAL' echo ' ' echo ' ' echo ' ' echo ' ' echo ' ' echo ' ' echo '' } > "$CFG" DBUS_SESSION_BUS_ADDRESS="unix:path=$SOCK"; export DBUS_SESSION_BUS_ADDRESS : > "$D/daemon.log" dbus-daemon --config-file="$CFG" --nofork > "$D/daemon.log" 2>&1 & DPID=$! # Readiness is the socket appearing, not a number of seconds. Bounded by # iterations, and it gives up as soon as the daemon is no longer alive so a # daemon that died at startup costs one iteration rather than sixty. # Builtins only in the body, per wait_for above -- [ -S ] is a builtin, running # forks. Liveness is therefore checked once, after the wait, which costs a dead # daemon the full bound and buys every iteration a real second. i=0 while [ "$i" -lt 60 ]; do [ -S "$SOCK" ] && break i=$((i + 1)); sleep 1 done if running "$DPID"; then daemon_ran=1; bst_ok dbus/daemon-alive else daemon_ran=0; bst_notok dbus/daemon-alive "$(_bst_1line "$(cat "$D/daemon.log" 2>/dev/null)")"; fi if [ -S "$SOCK" ]; then bst_ok dbus/socket-created else bst_notok dbus/socket-created "no socket at $SOCK: $(_bst_1line "$(cat "$D/daemon.log" 2>/dev/null)")"; fi if running "$DPID"; then # --------------------------------------------- the bus driver answers for itself # GetId is the id of the machine the bus runs on, so its VALUE is not fixed # here -- its shape is. A 32-character hex string is the whole of what the # spec promises, and it is what separates "the driver answered" from "the # driver answered with an empty string". gid=$(dbus-send --session --print-reply --dest=org.freedesktop.DBus / \ org.freedesktop.DBus.GetId 2>&1 \ | sed -n 's/.*string "\([0-9a-f]*\)".*/\1/p') check_eq dbus/getid-length 32 "${#gid}" check_out dbus/listnames "org.freedesktop.DBus" \ dbus-send --session --print-reply --dest=org.freedesktop.DBus / \ org.freedesktop.DBus.ListNames # ... and the caller's OWN unique name, which is the half that proves the # driver is tracking connections rather than reciting a constant. check_out dbus/listnames-caller '":1.' \ dbus-send --session --print-reply --dest=org.freedesktop.DBus / \ org.freedesktop.DBus.ListNames check dbus/peer-ping \ dbus-send --session --print-reply --dest=org.freedesktop.DBus / \ org.freedesktop.DBus.Peer.Ping check_out dbus/introspect "&1) case "$eout" in *"org.freedesktop.DBus.Error.ServiceUnknown"*) bst_ok dbus/error-unknown-service ;; *) bst_notok dbus/error-unknown-service "$(_bst_1line "$eout")" ;; esac # ------------------------------------------------------------- the probe probe=$D/dbus-probe if [ -f "$BST_DATA/dbus-probe" ] \ && cp "$BST_DATA/dbus-probe" "$probe" 2>/dev/null \ && chmod +x "$probe" 2>/dev/null; then bst_ok dbus/probe-stage : > "$D/probe.log" "$probe" > "$D/probe.log" 2>&1 & QPID=$! wait_for "$D/probe.log" 'P-ready' 60 if grep -q '^P-connect ok$' "$D/probe.log" 2>/dev/null; then bst_ok dbus/probe-connect else bst_notok dbus/probe-connect "$(_bst_1line "$(cat "$D/probe.log" 2>/dev/null)")"; fi if grep -q '^P-name ok$' "$D/probe.log" 2>/dev/null; then bst_ok dbus/probe-owns-name else bst_notok dbus/probe-owns-name "$(_bst_1line "$(cat "$D/probe.log" 2>/dev/null)")"; fi if grep -q '^P-ready$' "$D/probe.log" 2>/dev/null; then bst_ok dbus/probe-ready # The bus now knows a well-known name, which is a different question from # ListNames reciting its own. check_out dbus/name-has-owner "boolean true" \ dbus-send --session --print-reply --dest=org.freedesktop.DBus / \ org.freedesktop.DBus.NameHasOwner string:io.brintos.SelfTest # ------------------------------- one case per argument type the bus marshals db_case echo-byte 'byte 42' EchoBasic byte:42 db_case echo-boolean 'boolean true' EchoBasic boolean:true db_case echo-int16 'int16 -3' EchoBasic int16:-3 db_case echo-uint16 'uint16 7' EchoBasic uint16:7 db_case echo-int32 'int32 -2601' EchoBasic int32:-2601 db_case echo-uint32 'uint32 2601' EchoBasic uint32:2601 # The two 64-bit cases carry values that do NOT survive a double, so a # build that widened them through one would come back wrong rather than # merely differently formatted. db_case echo-int64 'int64 -9007199254740993' EchoBasic int64:-9007199254740993 db_case echo-uint64 'uint64 18446744073709551615' EchoBasic uint64:18446744073709551615 db_case echo-double 'double 2.5' EchoBasic double:2.5 db_case echo-string 'string "brintOS-dbus"' EchoBasic string:brintOS-dbus db_case echo-objpath 'object path "/io/brintos/Thing"' EchoBasic objpath:/io/brintos/Thing db_case echo-array 'array [ string "alpha" string "beta" ]' \ EchoArray array:string:alpha,beta db_case echo-dict 'array [ dict entry( string "k" string "v" ) ]' \ EchoDict dict:string:string:k,v db_case echo-variant 'variant int32 7' EchoVariant variant:int32:7 db_case make-struct 'struct { int32 2601 string "brintos" double 2.5 }' MakeStruct db_case make-signature 'signature "a{sv}"' MakeSignature # NEGATIVE CONTROL 2 -- a method the probe does not implement. libdbus must # turn that into the standard error rather than a reply. mout=$(sv NoSuchMethod) case "$mout" in *"org.freedesktop.DBus.Error.UnknownMethod"*) bst_ok dbus/error-unknown-method ;; *) bst_notok dbus/error-unknown-method "$(_bst_1line "$mout")" ;; esac # ------------------------------------------------- a signal, seen by dbus-monitor : > "$D/mon.log" dbus-monitor --session "type='signal',interface='io.brintos.SelfTest'" \ > "$D/mon.log" 2>&1 & MPID=$! # dbus-monitor is ready once the bus has told it its own name; asserting on # that rather than on a duration is what keeps the Emit below from racing it. wait_for "$D/mon.log" '*NameAcquired*' 40 sv Emit string:BRINTOS-2601-PING > /dev/null 2>&1 wait_for "$D/mon.log" '*BRINTOS-2601-PING*' 40 if grep -q "member=Pinged" "$D/mon.log" 2>/dev/null \ && grep -q "BRINTOS-2601-PING" "$D/mon.log" 2>/dev/null; then bst_ok dbus/signal-seen else bst_notok dbus/signal-seen "$(_bst_1line "$(cat "$D/mon.log" 2>/dev/null)")" fi kill "$MPID" 2>/dev/null else bst_notok dbus/probe-ready "$(_bst_1line "$(cat "$D/probe.log" 2>/dev/null)")" for c in name-has-owner echo-byte echo-boolean echo-int16 echo-uint16 \ echo-int32 echo-uint32 echo-int64 echo-uint64 echo-double \ echo-string echo-objpath echo-array echo-dict echo-variant \ make-struct make-signature error-unknown-method signal-seen; do bst_notok "dbus/$c" "the probe never became ready -- see dbus/probe-ready" done fi # The probe leaves on its own Quit, which is also the last method call this # suite makes -- so a clean exit here is one more round trip, not just a # tidy-up. sv Quit > /dev/null 2>&1 wait_for "$D/probe.log" 'P-exit *' 40 if grep -q '^P-exit ok$' "$D/probe.log" 2>/dev/null; then bst_ok dbus/probe-quit else bst_notok dbus/probe-quit "$(_bst_1line "$(tail -2 "$D/probe.log" 2>/dev/null)")"; fi kill "$QPID" 2>/dev/null else bst_notok dbus/probe-stage "cannot stage $BST_DATA/dbus-probe" for c in probe-connect probe-owns-name probe-ready name-has-owner echo-byte \ echo-boolean echo-int16 echo-uint16 echo-int32 echo-uint32 \ echo-int64 echo-uint64 echo-double echo-string echo-objpath \ echo-array echo-dict echo-variant make-struct make-signature \ error-unknown-method signal-seen probe-quit; do bst_notok "dbus/$c" "no probe to talk to -- see dbus/probe-stage" done fi else for c in getid-length listnames listnames-caller peer-ping introspect \ error-unknown-service probe-stage probe-connect probe-owns-name \ probe-ready name-has-owner echo-byte echo-boolean echo-int16 \ echo-uint16 echo-int32 echo-uint32 echo-int64 echo-uint64 \ echo-double echo-string echo-objpath echo-array echo-dict \ echo-variant make-struct make-signature error-unknown-method \ signal-seen probe-quit; do bst_notok "dbus/$c" "no daemon to talk to -- see dbus/daemon-alive" done fi # dbus-run-session starts a bus of its OWN and tears it down again when the # command exits -- a second, independent path through daemon startup, and the # only case here that does not use the bus this suite started. # # IT THEREFORE SITS OUTSIDE THE BLOCK ABOVE. It was inside it, and that was # wrong in the one situation where it is most informative: a private daemon # that will not start would have skipped this case as "no daemon to talk to", # which is a cause that does not apply to it. Bounded because it is the one # command in the file that could fail by not returning. bst_run 60 dbus-run-session -- /bin/true if [ "$rc" -eq 0 ]; then bst_ok dbus/run-session elif [ "$rc" -eq 124 ]; then bst_notok dbus/run-session "did not return within 60 s" else bst_notok dbus/run-session "rc=$rc $(_bst_1line "$out")"; fi # --------------------------------------------------------------- the clean stop # # GATED ON THE DAEMON HAVING ACTUALLY RUN, and that gate is the whole point of # this block rather than a formality. All three cases below pass by DEFAULT when # there is no daemon: a process that never started is not running, its socket # does not exist, and a call to a bus that was never listening fails. Ungated, # this file reported "the daemon shut down cleanly on SIGTERM" and "the bus is # unreachable after stop" about a daemon that had never started -- measured, and # the same false-green class BRINTOS-1057 already paid for once on the sibling # dlt-daemon port. if [ "$daemon_ran" -eq 1 ]; then kill -TERM "$DPID" 2>/dev/null i=0 while [ "$i" -lt 40 ]; do [ -e "$SOCK" ] || break i=$((i + 1)); sleep 1 done if running "$DPID"; then bst_notok dbus/daemon-stopped "still running after SIGTERM" else bst_ok dbus/daemon-stopped; fi # dbus-daemon unlinks its listening socket on the way out; a leftover is a # shutdown that did not run its cleanup. if [ -e "$SOCK" ]; then bst_notok dbus/socket-removed "$SOCK survived shutdown" else bst_ok dbus/socket-removed; fi # NEGATIVE CONTROL 3 -- with the bus gone the same call that worked above must # now fail. Without this, every case above would also pass against a dbus-send # that reports success without connecting to anything. check_rc dbus/unreachable-after-stop 1 \ dbus-send --session --print-reply --dest=org.freedesktop.DBus / \ org.freedesktop.DBus.GetId else for c in daemon-stopped socket-removed unreachable-after-stop; do bst_notok "dbus/$c" "no daemon was ever running -- see dbus/daemon-alive" done fi # $D is removed by the EXIT trap, which also covers the paths that never reach # here.