. "$BST_LIB" # BST-TIMEOUT: 900 # 47 corpus cases, each a fresh scratch dir + an exec, plus # blkiomon's 2 s interval; measured, see the design doc # blktrace -- the analysis arm compared BYTE FOR BYTE against a native x86-64 # oracle, and the recorder measured where it actually stands. # # WHY THIS SUITE IS A COMPARISON AND NOT A SET OF ASSERTIONS. A blktrace # recording is a raw kernel struct written to a file: 48 fixed-width bytes with # no self-description beyond a magic, two payload types the tools byte-swap # unconditionally, and a large family of derived numbers -- latencies, # histograms, seek distances, throughput, floating-point series -- computed in # 64-bit arithmetic and printed through formats that name `unsigned long`. A # 32-bit build that got any of that subtly wrong produces a report that is ALMOST # right: right event count, right action letters, one field truncated or one # average off in a column nobody reads first. Writing those expectations by hand # is the one thing not worth doing -- a hand-written expectation is a snapshot of # one SRCREV that rots into a false red the day poky bumps blktrace, and then # gets re-blessed from whatever the target printed, which is the comparison # inverted. So the expectation is generated at build time by blktrace-native -- # the SAME recipe version, same Makefile, built for the build host -- and # installed as /usr/libexec/blktrace-tests. This suite re-runs the identical # command lines here and requires identical bytes. # # THE RECORDING IS GENERATED TOO, AND BY THE BUILD HOST ON PURPOSE. The ticket's # question is what a 32-bit wasm build does with a 64-bit x86 recording, so the # recording is laid out by the x86-64 compiler (blktrace-mkfixture, which carries # the same struct-layout assertions the target build compiles). It could not # have been recorded for real on any machine involved: the build host has # CONFIG_BLK_DEV_IO_TRACE and no root, and this guest has neither a block device # nor a debugfs. # # ONE COMMAND STRING, TWO CONSUMERS. Each case's command line lives in the # corpus and is read from there, not written out again here. A case list # duplicated between the generator and the suite drifts, and it drifts in the # direction where the suite runs something easier than what was measured. # # AND THE CONTROLS AT THE END ARE NOT DECORATION. A comparator with a bug in it # passes everything, and 47 green comparisons look exactly the same either way. # The controls take a known-good oracle answer, change ONE character of it -- a # digit in a sector, the trailing newline, one byte of a binary dump -- and # require the comparator to say so. Each mutation reports whether it actually # applied, so a control that poses no question is a named failure rather than a # quiet pass. bst_ok blktrace/suite-canary # first line: absorbs brintos/linux#96, which drops it require blkparse blktrace/present # The comparison is defined in the C locale and in UTC, and neither is hygiene. # blkparse ends its report with ctime(3) of the trace's own start timestamp, # which is LOCAL time: the same fixture prints 22:13:20 under TZ=UTC and # 23:13:20 one zone east. The oracle exported this identical pair. TZ=UTC; LC_ALL=C export TZ LC_ALL T=/usr/libexec/blktrace-tests if [ ! -f "$T/manifest" ] || [ ! -f "$T/oracle-info" ]; then bst_notok blktrace/corpus "no oracle corpus at $T -- blktrace-tests is not installed, so every comparison in this suite is unrunnable" exit 0 fi W=${TMPDIR:-/tmp}/bst-blktrace.$$ if ! mkdir -p "$W" 2>/dev/null; then bst_notok blktrace/stage "cannot create working directory $W" exit 0 fi # The manifest and every command line are READ FROM COPIES IN /tmp, 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 it never reached -- a # clean-looking run that is simply shorter. if ! cp "$T/manifest" "$W/manifest" 2>/dev/null; then bst_notok blktrace/stage "cannot copy $T/manifest to $W" rm -rf "$W"; exit 0 fi info_pv=$(sed -n 's/^pv //p' "$T/oracle-info") info_arch=$(sed -n 's/^oracle-arch //p' "$T/oracle-info") info_cases=$(sed -n 's/^cases //p' "$T/oracle-info") info_recsize=$(sed -n 's/^record-size //p' "$T/oracle-info") info_events=$(sed -n 's/^fixture-events //p' "$T/oracle-info") # The ticket's criterion names the oracle's architecture, so the suite asserts it # rather than assuming the build host was one. A corpus generated anywhere else # is still a valid self-consistency check and is NOT the comparison this port was # asked for. check_eq blktrace/oracle-arch x86_64 "$info_arch" # The record size the recording was laid out with. It is the number the target's # own build-time assertions were pinned to, restated here from the artifact, so a # corpus built by some future generator that disagreed would be a named failure # rather than a wall of confusing diffs. check_eq blktrace/record-size 48 "$info_recsize" printf '#diag: blktrace corpus: pv=%s oracle-arch=%s cases=%s fixture-events=%s record-size=%s\n' \ "$info_pv" "$info_arch" "$info_cases" "$info_events" "$info_recsize" # cmp is the byte-exact comparator; 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 BT_CMP=1; else BT_CMP=; fi bt_same() { # bt_same if [ -n "$BT_CMP" ]; then cmp -s "$1" "$2" else [ "$(cat "$1")" = "$(cat "$2")" ] && [ "$(wc -c < "$1")" = "$(wc -c < "$2")" ] fi } # --------------------------------------------------------------------------- # The shipped set. Presence first, because every comparison below is about a # program and a missing one produces a "command not found" diff that reads as an # output difference. # --------------------------------------------------------------------------- bt_missing= for bt_p in blkparse blktrace btt verify_blkparse blkrawverify blkiomon \ btrecord iowatcher btrace bno_plot.py; do command -v "$bt_p" >/dev/null 2>&1 || bt_missing="$bt_missing $bt_p" done if [ -z "$bt_missing" ]; then bst_ok blktrace/tools-present else bst_notok blktrace/tools-present "absent from PATH:$bt_missing" fi # btreplay is EXCLUDED from this arm and its absence is asserted, not tolerated. # The coroutinize pass refuses its two VLA-to-opaque-sink functions # (reclaim_ios, process_bunch) and wasmld refuses the module -- # brintos.io/brintos/hwjs-cc/issues/15, the same defect that blocks oniguruma. # If it ever appears here the refusal has been lifted upstream and the recipe's # exclusion should go with it, so this case is how that news arrives. if command -v btreplay >/dev/null 2>&1; then bst_notok blktrace/btreplay-excluded "btreplay is on the image, but the recipe excludes it for hwjs-cc#15 -- if the coroutinize pass now accepts its VLAs, drop BLKTRACE_TARGET_SUBDIRS/BLKTRACE_INSTALL_ALL from blktrace_%.bbappend" else bst_ok blktrace/btreplay-excluded fi # --------------------------------------------------------------------------- # The comparison. One named result per corpus case, covering stdout, stderr, # exit status and every extra output file that case was told to write. # --------------------------------------------------------------------------- n_run=0 n_bad=0 first_bad= # One pristine copy of the fixture, staged once. Each case gets a fresh copy of # it -- btt alone writes six files into its working directory whatever it was # asked for, so a shared directory would let one case's leftovers decide the # next one's result. if ! cp -a "$T/fixture" "$W/fix" 2>/dev/null; then bst_notok blktrace/stage-fixture "cannot copy $T/fixture to $W" rm -rf "$W"; exit 0 fi while read -r bt_name bt_files; do [ -n "$bt_name" ] || continue C=$T/cases/$bt_name [ -f "$C/cmd" ] || { bst_notok "blktrace/$bt_name" "no cmd in the corpus"; continue; } n_run=$((n_run + 1)) rm -rf "$W/run" cp -a "$W/fix" "$W/run" 2>/dev/null bt_cmd=$(cat "$C/cmd") # A subshell forks before the cd, so a chdir that fails costs this one case # rather than the rest of the file (brintos/linux#73 again -- ash treats a # failed top-level cd as fatal). "$W/o" 2> "$W/e" < /dev/null bt_rc=$? # A case the corpus declares the target may legitimately be unable to run, # with the exact refusal that says so and the issue it belongs to. Today # that is blkiomon and only blkiomon: it needs a thread, and thread-mode # clone is refused by this runtime. The x86-64 answer stays recorded, so # the comparison is already written for the day threads work -- and any # refusal OTHER than the declared one still compares normally, which is what # keeps this from being a blanket excuse for anything that fails. if [ -f "$C/gap" ] && [ "$bt_rc" != 0 ]; then gap_sig=$(sed -n '1s/|.*//p' "$C/gap") gap_url=$(sed -n '1s/^[^|]*|\([^|]*\)|.*/\1/p' "$C/gap") gap_why=$(sed -n '1s/^[^|]*|[^|]*|//p' "$C/gap") if [ -n "$gap_sig" ] && grep -qF -- "$gap_sig" "$W/e" 2>/dev/null; then bst_ok "blktrace/$bt_name" printf '#diag: blktrace %s: not runnable on this platform -- %s (%s)\n' \ "$bt_name" "$gap_why" "$gap_url" continue fi fi bt_diff= bt_same "$C/out" "$W/o" || bt_diff=stdout [ -z "$bt_diff" ] && { bt_same "$C/err" "$W/e" || bt_diff=stderr; } [ -z "$bt_diff" ] && [ "$bt_rc" != "$(cat "$C/rc")" ] && bt_diff="rc(got $bt_rc want $(cat "$C/rc"))" if [ -z "$bt_diff" ]; then for bt_f in $bt_files; do if [ ! -e "$W/run/$bt_f" ]; then bt_diff="missing output $bt_f"; break; fi bt_same "$C/files/$bt_f" "$W/run/$bt_f" || { bt_diff="file $bt_f"; break; } done fi if [ -z "$bt_diff" ]; then bst_ok "blktrace/$bt_name" else n_bad=$((n_bad + 1)) [ -n "$first_bad" ] || first_bad="$bt_name:$bt_diff" bst_notok "blktrace/$bt_name" "$bt_diff differs from the x86-64 oracle: $(_bst_1line "$(cat "$W/o" "$W/e" 2>/dev/null)")" fi done < "$W/manifest" # The count the corpus says it holds, against the count this suite actually ran. # A manifest that lost lines in transit produces fewer green comparisons and no # other symptom. check_eq blktrace/cases-run "$info_cases" "$n_run" printf '#diag: blktrace comparison: %s/%s cases matched the x86-64 oracle%s\n' \ "$((n_run - n_bad))" "$n_run" \ "$([ -n "$first_bad" ] && printf '; first difference %s' "$first_bad")" # --------------------------------------------------------------------------- # The recorder, measured rather than compared. # # blktrace(8) needs CONFIG_BLK_DEV_IO_TRACE, a mounted debugfs and a block # device, and this guest has none of them, so it cannot record here. That is a # PLATFORM gap, not a port defect, and it is asserted the way one should be: # the case passes when the tool refuses, and goes red only if it ever SUCCEEDS # -- which would mean the platform grew block tracing and this suite is now # understating what can be tested. Downgrading a working tool to a green skip # is the failure mode this shape avoids; so is a permanent red that everybody # learns to ignore. # --------------------------------------------------------------------------- bt_debugfs=absent [ -d /sys/kernel/debug ] && bt_debugfs=present bt_blockdev=$(ls /sys/block 2>/dev/null | tr '\n' ' ') [ -n "$bt_blockdev" ] || bt_blockdev='(none)' printf '#diag: blktrace recorder environment: /sys/kernel/debug %s; /sys/block: %s\n' \ "$bt_debugfs" "$bt_blockdev" bt_rec_out=$( ( cd "$W" && blktrace -d /dev/zero -w 1 ) 2>&1 _s=$W/ctl.orig; _m=$W/ctl.mut cp "$2" "$_s" 2>/dev/null sed "$3" "$_s" > "$_m" 2>/dev/null if ! grep -qF -- "$4" "$_m" 2>/dev/null; then bst_notok "blktrace/control-$1" "the mutation ($5) did not apply -- '$4' is absent from the mutated copy, so this control posed no question" elif bt_same "$_s" "$_m"; then bst_notok "blktrace/control-$1" "the comparator called two files differing by $5 equal" else bst_ok "blktrace/control-$1" fi } # Each marker is a string the ORIGINAL does not already contain -- a mutation # whose result was already present would let a no-op sed pass the grep above. # 'C Z' is not a legal rwbs string, which is what makes it a safe one to pose. bt_ctl sector "$T/cases/bp-mc/out" 's/1024 + 8/1025 + 8/' '1025 + 8' "one digit of a sector" bt_ctl action "$T/cases/bp-mc/out" 's/ C W / C Z /' 'C Z' "one action letter" bt_ctl timestamp "$T/cases/bp-mc/out" 's/0\.000010000/0.000010001/' '0.000010001' "one nanosecond of a timestamp" bt_ctl btt-row "$T/cases/btt-all/out" 's/^Q2Q /Q2Q./' 'Q2Q.' "a btt row label" # The trailing-newline case, which is the one a `$(cat)`-based comparator gets # wrong: command substitution strips trailing newlines, so two files differing # only in the final byte compare equal unless the byte count is checked too. # This is the control for the FALLBACK comparator, and it matters most exactly # when cmp is absent. printf 'a\nb\n' > "$W/nl.a" printf 'a\nb' > "$W/nl.b" if bt_same "$W/nl.a" "$W/nl.b"; then bst_notok blktrace/control-trailing-newline "the comparator called a file and the same file without its final newline equal (cmp=${BT_CMP:-absent})" else bst_ok blktrace/control-trailing-newline fi # ...and the same question of the BINARY comparison, where a text-oriented # comparator is likeliest to be fooled: one byte changed inside the record # stream, with no newline anywhere near it. if [ -f "$T/cases/bp-dump/files/dump.bin" ]; then cp "$T/cases/bp-dump/files/dump.bin" "$W/bin.a" 2>/dev/null cp "$W/bin.a" "$W/bin.b" 2>/dev/null printf 'Z' | dd of="$W/bin.b" bs=1 seek=17 count=1 conv=notrunc status=none 2>/dev/null if bt_same "$W/bin.a" "$W/bin.b"; then bst_notok blktrace/control-binary "the comparator called two record streams differing in one byte equal" else bst_ok blktrace/control-binary fi else bst_notok blktrace/control-binary "no dump.bin in the corpus to mutate" fi # A positive control: an unmodified copy must compare EQUAL. Without it every # control above would also pass against a comparator that always answers # "different", which would turn all 47 comparisons green-by-accident into # red-by-accident and be just as wrong. cp "$T/cases/bp-mc/out" "$W/eq.a" 2>/dev/null cp "$W/eq.a" "$W/eq.b" 2>/dev/null if bt_same "$W/eq.a" "$W/eq.b"; then bst_ok blktrace/control-identical else bst_notok blktrace/control-identical "the comparator called a file and its own copy different -- every comparison in this suite is meaningless" fi rm -rf "$W"