. "$BST_LIB" require jpnevulator jpnevulator/present # jpnevulator 2.3.6+git (meta-oe recipes-extended) -- Freddy Spierenburg's serial # sniffer. It has exactly two actions. --read opens every interface it was given, # select(2)s on them and writes a hex (or binary) dump of whatever arrives, with # an optional ASCII column and an optional byte-count column. --write does the # reverse: it parses a hex dump out of its input file and writes the bytes it # decodes to the interface, optionally appending a checksum, a CRC-16 or a CRC-8. # # THIS IMAGE EXPOSES NO SERIAL PORT THAT WILL OPEN. /dev/ttySER0 is registered by # the wasm32 driver on every image and refuses open(2) with -ENODEV until a host # WebSerial port attaches (the grant boundary, linux-in-the-browser#60 M2); there # is no /dev/ttyS0 at all. That is a property of the platform rather than of the # port, and it is what the probe below establishes rather than assumes. # # What makes almost all of this suite REAL ASSERTIONS anyway is one fact about # the source: jpnevulator's interface open is tty.c::ttyOpen(), which is a bare # # open(name, O_RDWR) # # with no tcgetattr, no isatty and no termios of any kind. So an ORDINARY FILE # named by --tty is a perfectly good interface in both directions, and the whole # of the dump formatter, the hex parser, the checksum arms and the argument # validation can be driven end to end with no tty anywhere. The suite splits: # # ASSERTED FOR REAL -- the usage and version banners and which stream they use, # every argument-validation diagnostic, the dump formatting (default width, the # ASCII column, --width wrapping, --byte-count offsets, --base=2 binary and the # non-printable substitution) driven from a file, and the write direction # including a byte-exact dump->parse->bytes round trip and all four trailer # arms. None of these needs a tty and none of them is a proxy for one. # # PROBED, then skipped -- the serial capture over /dev/ttySER0, the one thing # that genuinely needs the port. The probe is jpnevulator's OWN open of it, so # a probe that fails IS the port failing rather than a stand-in for it, and the # skip reason carries what it said. # # PROBED, then RUN -- the same capture over a pty pair, which is a real tty and # which this kernel does provide (78-libutempter asserts the whole # posix_openpt/grantpt/unlockpt/ptsname sequence green). --pty is jpnevulator's # own acquisition of it, so this exercises the select/read/format path over a # character device with a line discipline, not over a regular file. # # EVERY INVOCATION IS BOUNDED, and that is not decoration. jpnevulator's read # loop is `for(;count!=0;)` with count defaulting to -1, so a --read with no # --count NEVER RETURNS -- on a regular file it spins at 100% CPU on a select # that is always ready and a read that always returns 0. An unbounded case here # would not fail, it would take the machine. Two independent bounds are used # together: every read case that is meant to dump names a --count, and every # invocation in the file runs under `timeout`. The validation cases take a third # and stronger one -- they name an interface that cannot be opened, so option # parsing emits the diagnostic and jpnevulator exits at "No available interface" # WITHOUT EVER ENTERING THE LOOP. # # Every expectation below was executed against jpnevulator at SRCREV bc1d4f65 # built natively on x86-64 before it was written down, rc and bytes compared. TO="timeout 20" t=${TMPDIR:-/tmp}/bst-jpnevulator.$$ mkdir -p "$t" # The fixtures. Written here rather than shipped in data/ because a suite must # not redirect stdin from the virtiofs root (brintos/linux#73) and because both # directions want to write as well as read. # # f16 is exactly one full dump line at the default width of 16. f19 adds three # non-printable bytes so the wrap, the short-line padding and the '.' ASCII # substitution all have something to act on. NUL is deliberately not among them: # the fixture is built by the shell's printf, and a NUL in that path is not worth # depending on when 0x01/0x7F/0xFF answer the same question. printf 'Hello, brintOS!\n' > "$t/f16" cp "$t/f16" "$t/f19" printf '\001\177\377' >> "$t/f19" # An interface name nothing can open. Used by every validation case so the # diagnostic is reached and the read loop is not. ABSENT=/dev/bst-jpnevulator-absent # _case : ok iff exits AND its # combined output contains . Both halves are needed almost everywhere # here: jpnevulator answers --version and --help with status 1, answers a # validation complaint with whatever the run went on to do, and puts the whole of # the information in the string. _case() { _cid=$1; _cwant=$2; _cn=$3; shift 3 _co=$($TO "$@" &1); _cr=$? case "$_co" in *"$_cn"*) _ch=1 ;; *) _ch=0 ;; esac if [ "$_cr" -eq "$_cwant" ] && [ "$_ch" -eq 1 ]; then bst_ok "$_cid" elif bst_execfail "$_co"; then bst_unrun "$_cid" "$_co" else bst_notok "$_cid" "rc=$_cr want=$_cwant needle:'$_cn' got:'$(_bst_1line "$_co")'"; fi } # --- the package ------------------------------------------------------------- check jpnevulator/path test -x /usr/bin/jpnevulator check_out jpnevulator/on-path "/usr/bin/jpnevulator" command -v jpnevulator check jpnevulator/manpage test -s /usr/share/man/man1/jpnevulator.1.gz # --- the version banner ------------------------------------------------------ # --version RETURNS 1, not 0: options.c returns optionsRtrnVersion, main.c maps # every non-ok parse to jpnevulatorRtrnOptions == 1. A port that "fixed" that to # 0 would be a behavioural change, so the status is pinned as hard as the text. _case jpnevulator/version-text 1 "jpnevulator version 2.3.6" jpnevulator --version _case jpnevulator/version-rc 1 "Freddy Spierenburg" jpnevulator --version _case jpnevulator/version-gpl 1 "GNU General Public License" jpnevulator --version _vs=$($TO jpnevulator --version 2>/dev/null " jpnevulator --help _case jpnevulator/help-crc 1 "[--checksum] [--crc16=poly]" jpnevulator --help _hs=$($TO jpnevulator --help 2>/dev/null /dev/null /dev/null /dev/null /dev/null /dev/null /dev/null "$t/hex" : > "$t/out" check_rc jpnevulator/write-rc 0 $TO jpnevulator --write --tty="$t/out" "$t/hex" check_eq jpnevulator/write-bytes "48 65 6c 6c 6f 0a" "$(od -An -tx1 < "$t/out" | tr -s ' ' | sed 's/^ //;s/ $//' | tr -d '\n')" # THE round trip: dump f19, feed the dump straight back in, and compare the bytes # against the original. This is the single strongest assertion in the file -- # formatter and parser both, over all nineteen bytes including the three # non-printable ones, with no tty and no expectation typed by hand. $TO jpnevulator --read --count=19 --tty="$t/f19" > "$t/rt.hex" 2>/dev/null "$t/rt.bin" $TO jpnevulator --write --tty="$t/rt.bin" "$t/rt.hex" >/dev/null 2>&1 "$t/ab" _trailer() { : > "$t/tr" $TO jpnevulator --write $2 --tty="$t/tr" "$t/ab" >/dev/null 2>&1 "$t/ns" _case jpnevulator/write-no-send-prints 0 "41 42" jpnevulator --write --no-send --print --tty="$t/ns" "$t/ab" check_eq jpnevulator/write-no-send-writes-nothing "0" "$(wc -c < "$t/ns" | tr -d ' ')" # --- probe: is there a serial port on this machine? -------------------------- # The probe is jpnevulator's own open of /dev/ttySER0 -- the exact ttyOpen() call # the capture would make, not a stand-in for it. Three worlds are self-consistent # and one is not: # # no /dev/ttySER0 -> no serial capture is possible; skip. # /dev/ttySER0, open refused -> the grant boundary; no host WebSerial port is # attached. Skip, carrying what it said. # /dev/ttySER0, open granted -> a port exists; RUN the capture. # # A refusal is not a red: it is this image's documented unattended state # (linux-in-the-browser#60 M2), the same one 59-picocom records for the same # device. if [ ! -c /dev/ttySER0 ]; then bst_ok jpnevulator/probe-serial _serial_why="/dev/ttySER0 is absent on this kernel" _serial=0 else bst_ok jpnevulator/probe-serial _so=$($TO jpnevulator --read --count=1 --tty=/dev/ttySER0 2>&1 >/dev/null &1 "$_pts"'), and a redirection is performed by the shell, so no # `timeout` can wrap it: an open that does not return takes the suite shell, and # with it the machine. On 2026-08-26 that is exactly what happened, twice on one # image -- once with this suite first among eight, and again on the fresh boot # the runner gives a quarantined suite alone. Both captures end at the same # point: '==BST-BEGIN==', the kernel's own '[k] wasm-serial: open(/dev/ttySER0) # REFUSED' printk from the serial probe above, and then nothing at all until # '[hardwarejs] powering off'. The suite reports per-suite, so a suite that never # returns publishes none of the ~45 cases it had already passed. The same image # without this suite completed 376 cases across seven sibling suites. # # The bound is what places it: everything above here is wrapped in $TO and all of # it ran, which is what the serial probe's printk proves. This block held the # file's only construct with no bound at all, and a grep across all 764 suites in # the layer finds no other suite that opens a pts slave from its shell. # # The write direction is still used for the probe because it is bounded by its # input: a --read --pty would block forever waiting for a byte nobody will send. _po=$($TO jpnevulator --write --pty &1); _por=$? case "$_po" in *"slave pts device is /dev/"*) bst_ok jpnevulator/probe-pty; _pty=1 ;; *) bst_ok jpnevulator/probe-pty; _pty=0 _pty_why="jpnevulator could not acquire a pty (rc=$_por): $(_bst_1line "$_po")" ;; esac _pty_capture_why="not attempted on this image: feeding the announced slave requires the suite shell to open it by name, which no timeout can bound, and that open wedged the guest on 2026-08-26 (two boots, no case output published at all). The acquisition itself is asserted by jpnevulator/probe-pty above; the read/format path it would have exercised is asserted over a regular file by the dump and roundtrip cases." if [ "$_pty" -eq 0 ]; then bst_skip jpnevulator/pty-capture "$_pty_why" bst_skip jpnevulator/pty-capture-ascii "$_pty_why" else bst_skip jpnevulator/pty-capture "$_pty_capture_why" bst_skip jpnevulator/pty-capture-ascii "$_pty_capture_why" fi rm -rf "$t"