. "$BST_LIB" # BST-TIMEOUT: 900 # 6 transfers x a 45s+5s settle: five of them are the # # brintos/linux#75 reds and burn the full bound every run require lsz lrzsz/present # lrzsz 0.12.20 -- the X/YMODEM/ZMODEM file transfer pair, lsz (send) and lrz # (receive), plus the six update-alternatives names sz/sx/sb and rz/rx/rb. # # THE RIG IS TWO CROSSED FIFOS, AND ITS OPEN ORDER IS LOAD-BEARING. These # protocols are bidirectional -- the receiver ACKs, NAKs and negotiates -- so a # single pipe cannot carry one, and no serial device is involved or needed. The # sender opens `up` for WRITE first and the receiver opens the same FIFO for # READ first, which is what lets the two rendezvous; the mirror-image pair on # `down` then does the same. Written the other way round -- `< down > up` on the # sender, the order a redirection list is normally read in -- both processes # block in open(2) forever waiting for the other's end, and the suite dies at # its bound with no case to name. Verified under dash and busybox ash before it # was written here, because the runner executes this file under ash. # # THE PAYLOAD IS SIZED AND SHAPED, NOT ARBITRARY. data/lrzsz-payload.bin is # 1024 bytes carrying all 256 byte values four times over: # * every value means the protocol-critical control bytes are actually in the # stream -- ZDLE 0x18, XON 0x11, XOFF 0x13, CPMEOF 0x1a, CR, LF. A payload # of text would leave ZMODEM's escaping path untested, which is the one path # where a wrong answer looks like a working transfer. # * 1024 is 8*128, so XMODEM's fixed block size divides it exactly. That is # the ONLY reason "byte-exact" is achievable for all three protocols at # once, and it is a property of the payload rather than of the port -- see # xmodem-pads-unaligned below, which asserts the other half positively so # the convenience cannot be mistaken for a guarantee. # # WHY `require lsz` AND NOT `require sz`. lsz/lrz are what the lrzsz package # installs; sz/rz/sx/rx/sb/rb are update-alternatives symlinks written host-side # at rootfs time. Requiring an alternative would turn a rootfs step that # silently did not happen into a `skip` -- a coverage prompt -- when it is a # real failure. So the package's own binary gates the file and the six links are # ordinary cases that go red. # # Calibrated against a native x86_64 build of the same 0.12.20 tarball, so every # expectation below is a measurement rather than a reading of the protocol # specs. t=${TMPDIR:-/tmp}/bst-lrzsz.$$ rm -rf "$t"; mkdir -p "$t" || { bst_notok lrzsz/scratch "cannot mkdir $t"; return 2>/dev/null || exit 0; } cd "$t" || { bst_notok lrzsz/scratch "cannot cd $t"; return 2>/dev/null || exit 0; } pay=$BST_DATA/lrzsz-payload.bin # xfer [recv-args...] # Runs one transfer and waits for the RECEIVED FILE to settle. `-` sends no # protocol flag at all, which is how the argv[0]-dispatch case is driven. # # THREE OBVIOUS WAYS TO WRITE THIS ARE ALL UNAVAILABLE ON THIS PLATFORM, AND # EACH FAILS SILENTLY RATHER THAN LOUDLY. Stated here because a future editor # will otherwise reach for them in exactly this order: # # * BRINTOS-WORKAROUND: brintos/linux#74 revisit -- this rig cannot use `wait`. # A parent blocked in wait(2) on a child that is STILL RUNNING is not woken # when that child exits: it blocks forever, with no EINTR storm to notice it # by. Called AFTER the child has already exited it returns fine, which is # what makes it look serviceable in a quick test. Both transfers here finish # in under a second; a `wait` after them still hangs, so the suite's whole # bound was being spent inside a shell builtin rather than on lrzsz. When # #74 closes, `wait` becomes available again -- though the poll below is # serviceable regardless, so this is a revisit rather than an automatic # removal. # * BRINTOS-WORKAROUND: brintos/linux#21 revisit -- no `timeout N cmd` bound. # It signals, and signal delivery does not work on this arm at all, SIGKILL # included. Measured: `timeout 3 sleep 30` exits 0 after 30 seconds. # Wrapping a transfer in it would read as a per-transfer bound while # providing none, which is worse than none. When #21 closes, a real # per-transfer bound can replace the settle loop's iteration cap. # * BRINTOS-WORKAROUND: brintos/hwjs-cc#69 revisit -- a background subshell's # trailing command does not reliably run, so this suite asserts the received # ARTIFACT rather than any exit status. Writing `( cmd; echo $? > rc ) &` and # polling for `rc` looks like the signal-free answer, and it is not: the # `echo` is skipped on most runs while `cmd` itself completes perfectly. The # guest says why -- # [k] hwjs-rt VIOLATION[lane N]: set-over-set: parked slot not empty at a # new park -- a coroutinized frame parked under a NATIVE caller # -- with the leaked park slot of brintos/hardwarejs#17. Measured over four # consecutive identical ZMODEM transfers: the payload arrived complete # (1024/1024) in 4 of 4, and the rc file appeared in 1 of 4. When #69 closes, # the per-transfer exit statuses become assertable again. # # So this suite asserts THE ARTIFACT, not the exit status: it polls the received # file until its size settles. That is both the only observable this platform # delivers reliably and the stronger assertion anyway -- a transfer that failed # would leave a short or wrong file, which the byte-exact cases below catch. # Exit status is still asserted where it CAN be observed: sz-missing-file-rc # below runs lsz in the FOREGROUND, which is unaffected by any of the above. xfer() { _p=$1; _sb=$2; _rb=$3; _src=$4; _d=$5; _out=$6; shift 6 rm -rf "$_d"; mkdir -p "$_d" || return 1 mkfifo "$_d/up" "$_d/down" 2>/dev/null || return 2 if [ "$_p" = "-" ]; then ( cd "$_d" && "$_sb" "$_src" > up < down 2> sz.err ) & ( cd "$_d" && "$_rb" "$@" < up > down 2> rz.err ) & else ( cd "$_d" && "$_sb" "$_p" "$_src" > up < down 2> sz.err ) & ( cd "$_d" && "$_rb" "$_p" "$@" < up > down 2> rz.err ) & fi # Settle on the received file appearing, then give the last block a moment to # land. Two rules, both of them learned by breaking them: # # * BRINTOS-WORKAROUND: brintos/hardwarejs#20 revisit -- THE CONDITION MUST # USE ONLY SHELL BUILTINS, `[ -s ]`, never `$(wc -c ...)`. A command # substitution forks, forks leak coroutine park slots here # (brintos/hwjs-cc#69, brintos/hardwarejs#17), and a fork per tick across # six transfers wedged the guest outright: the host process grew # 3273 MiB/min to 13 GiB and the harness ended it. The builtin form costs # nothing, so this stays defensible after #20 closes -- hence revisit. # * 45 s IS MEASURED, NOT PICKED. A ZMODEM transfer of this payload can # settle in under a second or still be going at 20 -- observed across four # identical consecutive runs. A 20 s bound reddened the ZMODEM case on a # run where the transfer was merely slow, which is the worst failure this # suite could have: a platform-timing artifact wearing the costume of a # port defect. The trailing sleep is the same argument one level down -- # `[ -s ]` trips on the FIRST byte written, not the last. _w=0 while [ "$_w" -lt 45 ]; do sleep 1; _w=$((_w+1)) [ -s "$_d/$_out" ] && break done sleep 5 return 0 } # ---- controls: everything below is meaningless if these do not hold ---------- # The shipped payload is the instrument. If it arrived truncated or mangled, # every round-trip case would still compare it against itself and pass. check lrzsz/control-payload-present test -f "$pay" check_eq lrzsz/control-payload-size 1024 "$(wc -c < "$pay" 2>/dev/null | tr -d ' ')" check_eq lrzsz/control-payload-distinct 256 \ "$(od -An -tu1 -v "$pay" 2>/dev/null | tr -s ' ' '\n' | grep '^[0-9]' | sort -u | wc -l | tr -d ' ')" # A cmp that answered 0 to everything would sweep every byte-exact case green. printf 'not the payload\n' > other.bin check_rc lrzsz/control-cmp-differs 1 cmp -s "$pay" other.bin # mkfifo on this filesystem is the rig. psmisc found mkfifo refused on the # virtiofs root, so this is asserted here rather than assumed of /tmp's tmpfs: # without it every transfer below reds for a reason that is not lrzsz's. check lrzsz/control-mkfifo sh -c 'mkfifo probe.fifo && test -p probe.fifo' rm -f probe.fifo # ---- ORDER MATTERS: THE TRANSFERS RUN FIRST ---------------------------------- # BRINTOS-WORKAROUND: brintos/hwjs-cc#69 revisit -- the case ORDER is load-bearing # only because forks degrade the guest; when #69 closes the ordering stops # mattering and this block can go back to reading top-down by topic. # The deliverable is measured while the guest is FRESH, ahead of the presence # cases, and that ordering is load-bearing rather than cosmetic. Every bst.sh # assertion forks (`check` and friends run their command in a command # substitution), forks leak coroutine park slots on this arm # (brintos/hwjs-cc#69, brintos/hardwarejs#17), and the guest degrades as they # accumulate. With the eight presence cases ahead of them the ZMODEM round trip # went flaky -- passing on some runs and failing on others with the received # file never created -- while the same transfer standalone after boot completed # 4 times out of 4. That is a harness artifact, not a port defect, and putting # the transfers first is what stops it being reported as one. # # The payload controls above still precede everything: they are what makes the # comparisons trustworthy, and they must not move below the thing they gate. # ---- the deliverable: one round trip per protocol, byte-exact ---------------- # ZMODEM and YMODEM carry the filename, so the receiver is given no output name # and the file landing under the sender's basename is itself an assertion. xfer --zmodem lsz lrz "$pay" z lrzsz-payload.bin check lrzsz/zmodem-roundtrip cmp -s "$pay" z/lrzsz-payload.bin # --------------------------------------------------------------------------- # EVERY X/YMODEM CASE FROM HERE DOWN IS AN EXPECTED, ATTRIBUTED RED: # brintos/linux#75 -- XMODEM and YMODEM never start over a FIFO pair on # wasm32-hwjs. The receiver prints "lrz waiting to receive." and never # emits the NAK/`C` poll, so the sender waits on a poll that never comes: # 144 s observed with zero bytes moved and the sender silent throughout. # ZMODEM above, over the IDENTICAL rig, completes byte-exactly 4 runs of 4, # and the XMODEM *sender* emits 1483 bytes and retries correctly the moment # a poll is fed to it from a regular file -- so this is neither the rig nor # the port. All three protocols pass over the same rig on x86-64. # # They are left FAILING rather than skipped, and written as POSITIVE assertions # of what a working platform gives, so they flip green by themselves when #75 # lands and nothing here has to be rewritten at that point. A `skip` would have # read as coverage; that is the failure mode this project has ruled out. # --------------------------------------------------------------------------- xfer --ymodem lsz lrz "$pay" y lrzsz-payload.bin check lrzsz/ymodem-roundtrip cmp -s "$pay" y/lrzsz-payload.bin # XMODEM carries neither name nor length, so the receiver is told the name. xfer --xmodem lsz lrz "$pay" x got.bin got.bin check lrzsz/xmodem-roundtrip cmp -s "$pay" x/got.bin # ---- protocol properties, measured on the x86_64 control ---------------------- # A 1000-byte payload is 7.8 XMODEM blocks. YMODEM sends a length in its header # and the receiver truncates to it; XMODEM has no length field at all, so the # last block arrives padded with CPMEOF (0x1a) and the receiver cannot know # where the file ended. Both are protocol-correct and neither is a port defect # -- they are asserted so that a future change to either behaviour is visible. head -c 1000 "$pay" > odd.bin check_eq lrzsz/control-odd-size 1000 "$(wc -c < odd.bin | tr -d ' ')" xfer --ymodem lsz lrz "$t/odd.bin" yo odd.bin check_eq lrzsz/ymodem-carries-length 1000 "$(wc -c < yo/odd.bin 2>/dev/null | tr -d ' ')" check lrzsz/ymodem-roundtrip-unaligned cmp -s odd.bin yo/odd.bin xfer --xmodem lsz lrz "$t/odd.bin" xo got.bin got.bin check_eq lrzsz/xmodem-pads-unaligned 1024 "$(wc -c < xo/got.bin 2>/dev/null | tr -d ' ')" # the padding is CPMEOF, not NUL -- the CP/M convention the protocol inherited check_eq lrzsz/xmodem-pad-byte-is-cpmeof "032 032 032 032" \ "$(od -An -to1 -j 1020 -N 4 xo/got.bin 2>/dev/null | tr -s ' ' ' ' | sed 's/^ //;s/ $//')" # the bytes the source did carry are untouched -- padding, not corruption head -c 1000 xo/got.bin > xo-head.bin 2>/dev/null check lrzsz/xmodem-unaligned-prefix-exact cmp -s odd.bin xo-head.bin # ---- argv[0] dispatch: what makes the six alternatives mean anything ---------- # sx and rx take NO protocol flag here. If argv[0] dispatch were broken they # would fall back to ZMODEM and still transfer the file, so this case is pinned # on the XMODEM-specific evidence: an unaligned source comes back block-padded. # A ZMODEM transfer would return 1000 bytes and red this. xfer - sx rx "$t/odd.bin" a0 got.bin got.bin check_eq lrzsz/argv0-selects-xmodem 1024 "$(wc -c < a0/got.bin 2>/dev/null | tr -d ' ')" check lrzsz/argv0-prefix-exact sh -c 'head -c 1000 a0/got.bin > a0-head.bin && cmp -s odd.bin a0-head.bin' # ---- presence ---------------------------------------------------------------- check_out lrzsz/lsz-version "lsz (lrzsz) 0.12.20" lsz --version check_out lrzsz/lrz-version "lrz (lrzsz) 0.12.20" lrz --version # The six update-alternatives names. sz/rz are the pair the ticket is about; # the other four exist only because argv[0] dispatch makes them mean something, # which argv0-xmodem below is what actually proves. for n in sz sx sb rz rx rb; do check lrzsz/alt-$n sh -c "command -v $n >/dev/null" done # ---- diagnostics: a failure is reported, not silently swallowed --------------- # lsz exits 128 and says so on stderr. A sender that returned 0 here would make # every "transfer succeeded" reading above worthless. check_rc lrzsz/sz-missing-file-rc 128 sh -c 'lsz --zmodem /no/such/file < /dev/null > /dev/null 2>&1' check_out lrzsz/sz-missing-file-msg "No such file or directory" \ sh -c 'lsz --zmodem /no/such/file < /dev/null 2>&1 >/dev/null; true' cd /; rm -rf "$t"