. "$BST_LIB" # BST-TIMEOUT: 650 # 126 cases and ~190 execs -- 101 corpus comparisons, a # 75-tool split probe and 14 version reads. MEASURED at # elapsed=129s on an idle hz1 (2026-08-23, # ==BST-SUITE sg3-utils elapsed=129s cases=126), declared at 5x # that. The default 60s would trip on the split probe alone; # the 1800 this first carried was 14x and gave the sentinel # back the protection the bound exists to provide. # sg3-utils -- the DECODER arm, compared BYTE FOR BYTE against a native x86-64 # oracle of the same version. # # WHAT THIS MACHINE CAN AND CANNOT TEST. sg3_utils is 75 programs that issue SCSI # commands, and there is no SCSI device here: no /dev/sg*, no /dev/sd*, nothing # behind SG_IO. Most of the package therefore cannot run, and the two live cases # at the end are recorded as expected reds rather than skipped. What CAN run is # the arm the port was scoped around: 14 of the 75 accept --inhex=FN, which # decodes a response captured earlier from a FILE and opens no device, and # sg_decode_sense additionally takes the bytes on its own command line. That is # parse, table-index and format -- arithmetic on bytes, with no I/O in it. # # WHY A COMPARISON RATHER THAN ASSERTIONS. A decoder is deterministic across # architectures, so a correct wasm32 build must print exactly what a native build # of the SAME VERSION prints on the SAME BYTES. Weaker assertions do not survive # contact with the failure they are supposed to catch: "sg_vpd ran" and "the # output mentions NAA" are both satisfied by a decode that got a bitfield boundary # wrong and mislabelled every designator on the page. # # WHERE THE CORPUS COMES FROM. Upstream's own inhex/ -- 45 responses captured from # real hardware, shipped inside the sg3_utils tarball, plus upstream's # tst_script.sh naming the invocation each was captured for. It is read out of ${S} # at build time, so it is the corpus THIS version ships and cannot drift from the # code it exercises. # # THE NINE SHELL HELPERS ARE HERE TO TEST BASH, NOT sg3_utils. They are # byte-identical on both sides -- they are not compiled -- so a divergence in what # they print is a divergence in the bash that ran them. The package RDEPENDS on # bash for exactly these, and "bash is in the image" is a weaker claim than "bash # on wasm32 executes them and produces the same bytes as bash on x86-64". # # AND THE CONTROLS AT THE END ARE NOT DECORATION. A comparator with a bug in it # passes everything, and 101 green comparisons look identical either way. bst_ok sg3-utils/suite-canary # first line: absorbs brintos/linux#96, which drops it require sg_vpd sg3-utils/present T=/usr/libexec/sg3-utils-tests if [ ! -f "$T/oracle-info" ] || [ ! -d "$T/cases" ]; then bst_notok sg3-utils/corpus "no oracle corpus at $T -- sg3-utils-tests is not installed, so every comparison in this suite is unrunnable" exit 0 fi W=${TMPDIR:-/tmp}/bst-sg3-utils.$$ if ! mkdir -p "$W" 2>/dev/null; then bst_notok sg3-utils/stage "cannot create working directory $W" exit 0 fi # The corpus is COPIED TO /tmp and read from there, never redirected from the # rootfs. A top-level `while read ... done < /usr/...` on the virtiofs root hits # brintos/linux#73: dup2(2) returns 513, ash prints "Unknown error 513" and exits, # and the runner then reports every case emitted so far as a pass and nothing at # all about the ones never reached -- a clean-looking run that is simply shorter. if ! cp -r "$T/cases" "$W/cases" 2>/dev/null || ! cp -r "$T/inhex" "$W/inhex" 2>/dev/null; then bst_notok sg3-utils/stage "cannot copy the corpus from $T to $W" rm -rf "$W"; exit 0 fi for f in oracle-info binaries.list inhex-tools.list helpers.list tool-versions manifest.sha256; do cp "$T/$f" "$W/$f" 2>/dev/null done info_pv=$(sed -n 's/^pv //p' "$W/oracle-info") info_cases=$(sed -n 's/^cases //p' "$W/oracle-info") info_buffers=$(sed -n 's/^buffers //p' "$W/oracle-info") info_binaries=$(sed -n 's/^binaries //p' "$W/oracle-info") info_inhex=$(sed -n 's/^inhex-tools //p' "$W/oracle-info") info_helpers=$(sed -n 's/^helpers //p' "$W/oracle-info") # --- The comparator. --------------------------------------------------------- # cmp is byte-exact; the fallback exists so a stripped image reports a real # comparison rather than skipping the suite. `$(cat)` drops trailing newlines on # both sides, so the byte count is what closes that hole -- and # control-trailing-newline below proves the pair actually does. if command -v cmp >/dev/null 2>&1; then SG_CMP=1; else SG_CMP=; fi sg_same() { # sg_same if [ -n "$SG_CMP" ]; then cmp -s "$1" "$2" else [ "$(cat "$1")" = "$(cat "$2")" ] && [ "$(wc -c < "$1")" = "$(wc -c < "$2")" ] fi } # --- THE SPLIT: the question the ticket said to check rather than assume. ----- # Re-measured HERE, on the shipped wasm32 binaries, because that is what the claim # is about. The build-time gate measures it from the NATIVE binaries and # cross-checks against the source's long-option table; both are proxies for this. # # The probe has one trap and it is worth naming, because the obvious version of it # is wrong by nine: grepping the rejection for getopt_long's "unrecognized option" # counts sg_dd, sgm_dd, sgp_dd, sg_xcopy, sg_read, sg_map, sg_scan, sg_emc_trespass # and sg_rdac as ACCEPTING --inhex, because each parses its own command line and # rejects in its own words ("Unrecognized argument", "Unknown switch:", # "Unrecognized switch:"). Measured 2026-08-23: the naive probe says 23, the truth # is 14. n_bin=0; n_inhex=0; n_missing=0; missing= while IFS= read -r b; do [ -n "$b" ] || continue n_bin=$((n_bin + 1)) if [ ! -x "/usr/bin/$b" ]; then n_missing=$((n_missing + 1)); missing="$missing $b"; continue fi o=$("/usr/bin/$b" --inhex=/dev/null 2>&1) case "$o" in *nrecogni*|*nknown\ switch*|*nvalid\ option*|*nknown\ option*) ;; *) n_inhex=$((n_inhex + 1)); printf '%s\n' "$b" >> "$W/inhex.observed" ;; esac done < "$W/binaries.list" if [ "$n_missing" -eq 0 ]; then bst_ok sg3-utils/binaries-installed else bst_notok sg3-utils/binaries-installed "$n_missing of $n_bin binaries the oracle enumerated are not executable on target:$(_bst_1line "$missing")" fi check_eq sg3-utils/binary-count "$info_binaries" "$n_bin" check_eq sg3-utils/split-inhex "$info_inhex" "$n_inhex" check_eq sg3-utils/split-device-only "$((info_binaries - info_inhex))" "$((n_bin - n_inhex))" # The counts agreeing is not the same claim as the same tools being in the set -- # two different partitions of 75 can both be 14. Compare the membership. sort -o "$W/inhex.observed" "$W/inhex.observed" 2>/dev/null sort "$W/inhex-tools.list" > "$W/inhex.expected" if sg_same "$W/inhex.expected" "$W/inhex.observed"; then bst_ok sg3-utils/split-membership else bst_notok sg3-utils/split-membership "the target's --inhex set is not the oracle's: only-oracle=$(_bst_1line "$(comm -23 "$W/inhex.expected" "$W/inhex.observed" | tr '\n' ' ')") only-target=$(_bst_1line "$(comm -13 "$W/inhex.expected" "$W/inhex.observed" | tr '\n' ' ')")" fi # --- Per-tool versions. ------------------------------------------------------ # sg3_utils versions its tools INDIVIDUALLY -- sg_vpd is 1.96, sg_inq 2.48 -- so # the package PV says nothing about any one of them, and the claim this port rests # on ("the same decoder on both architectures") is per tool. A version skew here # would make every comparison below a comparison of two different programs. n_ver=0; n_verbad=0; verbad= while IFS= read -r line; do [ -n "$line" ] || continue t=${line%% *}; want=${line#* } n_ver=$((n_ver + 1)) got=$("/usr/bin/$t" --version 2>&1 | head -1 | sed 's/^.*: *//') [ "$got" = "$want" ] || { n_verbad=$((n_verbad + 1)); verbad="$verbad $t(want '$want' got '$got')"; } done < "$W/tool-versions" if [ "$n_verbad" -eq 0 ] && [ "$n_ver" -gt 0 ]; then bst_ok sg3-utils/tool-versions else bst_notok sg3-utils/tool-versions "$n_verbad of $n_ver tools differ in version from the oracle:$(_bst_1line "$verbad")" fi # --- The corpus arrived intact. ---------------------------------------------- # Every comparison below is against these bytes, so a corpus truncated in # transport would make the suite pass over whatever survived. The manifest is fed # on STDIN, which is what keeps 320 filenames off the argv -- the guest's argument # buffer is 8192 bytes and an exec that exceeds it wedges rather than errors # (brintos/hardwarejs#15). if command -v sha256sum >/dev/null 2>&1; then if grep -v '^#' "$W/manifest.sha256" | ( cd "$T" && sha256sum -c --quiet ) >"$W/sha.err" 2>&1; then bst_ok sg3-utils/corpus-identity else bst_notok sg3-utils/corpus-identity "the installed corpus does not match the manifest the oracle wrote: $(_bst_1line "$(head -3 "$W/sha.err")")" fi else bst_skip sg3-utils/corpus-identity "no sha256sum on this image, so the corpus bytes cannot be pinned to the oracle's" fi check_eq sg3-utils/buffer-count "$info_buffers" "$(find "$W/inhex" -type f | wc -l)" # --- The rest of the deliverable: the library and its headers. --------------- check sg3-utils/libsgutils-static test -f /usr/lib/libsgutils2.a check sg3-utils/header-sg-cmds test -f /usr/include/scsi/sg_cmds.h check sg3-utils/header-sg-lib test -f /usr/include/scsi/sg_lib.h # The helpers are #!/bin/bash and the package RDEPENDS on bash for them, so the # interpreter is part of the deliverable and is asserted before they are run. check sg3-utils/bash-present test -x /bin/bash n_help=0; n_helpbad=0 while IFS= read -r h; do [ -n "$h" ] || continue n_help=$((n_help + 1)) [ -x "/usr/bin/$h" ] || n_helpbad=$((n_helpbad + 1)) done < "$W/helpers.list" check_eq sg3-utils/helper-count "$info_helpers" "$n_help" if [ "$n_helpbad" -eq 0 ]; then bst_ok sg3-utils/helpers-installed else bst_notok sg3-utils/helpers-installed "$n_helpbad of $n_help shell helpers are not executable"; fi # --- The comparison. --------------------------------------------------------- # The argv is READ FROM THE CORPUS, not re-typed here -- so a case added to the # oracle reaches the target without editing this file, and the two cannot drift. # Each case runs from the corpus directory and names the tool and the buffer by # BARE NAME, which is what makes the argv byte-identical to the oracle's: --json # echoes the invocation back inside its own output, so an absolute path on either # side would diverge every JSON case for a reason that is not the decoder. sg_run() { # sg_run ; sets $sg_rc, writes $W/o sg_dir=$1; sg_id=$2 set -- # $sg_dir is always "$W/cases", the $TMPDIR copy staged at the top of this file # precisely so this loop does not read from the virtiofs root # (brintos/linux#73) -- the checker cannot see that through a function # parameter. The loop must stay in the top-level shell: it builds the # positional parameters the caller then runs, and a subshell would discard # them. All 126 cases reached the console on the recorded run. # BST-REDIRECT-OK: reads the $TMPDIR corpus copy, never the rootfs while IFS= read -r a; do set -- "$@" "$a"; done < "$sg_dir/$sg_id.argv" sg_tool=$1; shift ( cd "$W/inhex" && "$sg_tool" "$@" ) > "$W/o" 2>&1 sg_rc=$? } ( cd "$W/cases" && find . -name '*.argv' | sed 's|^\./||; s|\.argv$||' | sort ) > "$W/list" n_all=0 while IFS= read -r id; do [ -n "$id" ] || continue n_all=$((n_all + 1)) sg_run "$W/cases" "$id" arc=$sg_rc erc=$(cat "$W/cases/$id.rc") diff= sg_same "$W/cases/$id.out" "$W/o" || diff=output [ "$arc" = "$erc" ] || diff="${diff:+$diff,}rc(oracle $erc, target $arc)" if [ -z "$diff" ]; then bst_ok "sg3-utils/$id" else bst_notok "sg3-utils/$id" "differs from the x86-64 oracle in $diff -- oracle: $(_bst_1line "$(cat "$W/cases/$id.out")") target: $(_bst_1line "$(cat "$W/o")")" fi done < "$W/list" # The corpus must be the WHOLE corpus. A partially-installed one would make every # comparison above pass over fewer cases, which is the one regression a green run # cannot show. check_eq sg3-utils/case-count "$info_cases" "$n_all" # --- Controls on the comparator itself. -------------------------------------- # Each takes a known-good oracle answer, changes exactly one thing, and requires # the comparison to notice. Whether the mutation APPLIED is asked with grep, never # with the comparator under test: asking sg_same both questions makes them one # question, and a mutation that changed nothing then reports a blind comparator # when the truth is that it posed no question at all. ctl=$W/cases/up-vpd-dev-id.out if [ -f "$ctl" ]; then cp "$ctl" "$W/ctl.orig" # 1. One character of a decoded FIELD NAME -- the table-lookup half. if ! grep -q 'NAA' "$W/ctl.orig"; then bst_notok sg3-utils/control-one-char "the corpus holds no 'NAA' to change, so this control posed no question -- which itself means the decode produced no designator names" else sed 's/NAA/NAB/' "$W/ctl.orig" > "$W/ctl.1" if sg_same "$W/ctl.orig" "$W/ctl.1"; then bst_notok sg3-utils/control-one-char "the comparator cannot see a changed field name" else bst_ok sg3-utils/control-one-char fi fi # 2. One HEX DIGIT of a decoded VALUE. The values come out of the buffer # parser, which is the half the name mutation above cannot reach. if ! grep -q '0x5000c5003011cb2b' "$W/ctl.orig"; then bst_notok sg3-utils/control-one-value "the corpus holds no 0x5000c5003011cb2b to change, so this control posed no question" else sed 's/0x5000c5003011cb2b/0x5000c5003011cb2c/' "$W/ctl.orig" > "$W/ctl.2" if sg_same "$W/ctl.orig" "$W/ctl.2"; then bst_notok sg3-utils/control-one-value "the comparator cannot see a changed decoded value" else bst_ok sg3-utils/control-one-value fi fi # 3. The TRAILING NEWLINE alone -- the one the `$(cat)` fallback comparator is # structurally blind to, which is why sg_same carries a byte count beside # the string compare, and why that pairing is tested rather than assumed. printf '%s' "$(cat "$W/ctl.orig")" > "$W/ctl.3" if sg_same "$W/ctl.orig" "$W/ctl.3"; then bst_notok sg3-utils/control-trailing-newline "the comparator cannot see a missing trailing newline" else bst_ok sg3-utils/control-trailing-newline fi # 4. A DROPPED LINE. A corpus truncated in transport would otherwise compare # equal over the part that arrived. sed '2d' "$W/ctl.orig" > "$W/ctl.4" if sg_same "$W/ctl.orig" "$W/ctl.4"; then bst_notok sg3-utils/control-dropped-line "the comparator cannot see a dropped line" else bst_ok sg3-utils/control-dropped-line fi else bst_notok sg3-utils/control-one-char "no $ctl to mutate" bst_notok sg3-utils/control-one-value "no $ctl to mutate" bst_notok sg3-utils/control-trailing-newline "no $ctl to mutate" bst_notok sg3-utils/control-dropped-line "no $ctl to mutate" fi # 5. What a non-empty comparison MEANS. If a tool printed a full decode for input # it could not parse, "the output matched" would stop being evidence that the # bytes were understood. The same control the oracle ran on the build host, # re-run here so the claim holds on both sides. printf 'this is not scsi response data\n' > "$W/inhex/.control.hex" ( cd "$W/inhex" && sg_vpd -I .control.hex ) >"$W/bad.out" 2>&1 if [ $? -ne 0 ]; then bst_ok sg3-utils/control-unparseable else bst_notok sg3-utils/control-unparseable "unparseable input exited 0, so a rc=0 case no longer proves the buffer parsed: $(_bst_1line "$(cat "$W/bad.out")")" fi rm -f "$W/inhex/.control.hex" # 6. ...and that an absent buffer is reported rather than silently decoding # nothing. This separates "the corpus was read" from "the corpus was missing # and every case compared empty to empty". ( cd "$W/inhex" && sg_vpd -I .no-such-file.hex ) >/dev/null 2>&1 if [ $? -ne 0 ]; then bst_ok sg3-utils/control-missing-file else bst_notok sg3-utils/control-missing-file "an absent buffer exited 0"; fi # 7. The decode is DATA-DRIVEN, asserted on target and not only at build time. A # decoder that ignored its input and printed a fixed banner would satisfy every # comparison above, because the oracle would have recorded the same banner. # Byte 1 of a VPD response is the PAGE CODE -- the one byte the decoder must # consult before it can choose a decoder at all. Byte 0 does NOT work here and # the reason is worth keeping: it is the peripheral qualifier and device type, # which sg_vpd decoding a named page does not print, so flipping it changes the # file and nothing on stdout. sed '0,/^[0-9a-fA-F]/s/^\([0-9a-fA-F][0-9a-fA-F] \)[0-9a-fA-F][0-9a-fA-F]/\1b1/' \ "$W/inhex/vpd_dev_id.hex" > "$W/inhex/.mut.hex" 2>/dev/null if [ -s "$W/inhex/.mut.hex" ] && ! cmp -s "$W/inhex/vpd_dev_id.hex" "$W/inhex/.mut.hex"; then ( cd "$W/inhex" && sg_vpd -I vpd_dev_id.hex ) > "$W/dd.a" 2>&1 ( cd "$W/inhex" && sg_vpd -I .mut.hex ) > "$W/dd.b" 2>&1 if sg_same "$W/dd.a" "$W/dd.b"; then bst_notok sg3-utils/control-data-driven "sg_vpd printed identical bytes for two different page codes -- this suite cannot distinguish a working decoder from one that ignores its input" else bst_ok sg3-utils/control-data-driven fi else bst_notok sg3-utils/control-data-driven "the page-code mutation did not apply, so this control posed no question" fi rm -f "$W/inhex/.mut.hex" # --- The live-device arm: expected reds, recorded rather than skipped. -------- # These are the cases the ticket named in advance as an acceptable result. They # are left FAILING rather than skipped so they flip green on their own if this # machine ever grows a SCSI device; a skip would have to be noticed and edited by # a human. if [ -e /dev/sg0 ] || [ -e /dev/sda ]; then dev=/dev/sg0; [ -e "$dev" ] || dev=/dev/sda check sg3-utils/live-inq sg_inq "$dev" check sg3-utils/live-turs sg_turs "$dev" else bst_notok sg3-utils/live-inq "no /dev/sg* and no /dev/sd* on this machine, so sg_inq has no device to send INQUIRY to. EXPECTED: this is a machine with no SCSI hardware, not a defect in the port -- 61 of the 75 tools are device-only and this is one of them. The 14-tool decoder arm above is what this port was scoped around." bst_notok sg3-utils/live-turs "sg_turs issues TEST UNIT READY through SG_IO, which needs the same absent device. EXPECTED, and for the same reason." fi rm -rf "$W"