# BST-TIMEOUT: 300 # 45 cases. Five execs, two of which read the WHOLE payload # # -- one gawk pass over 513 database files plus the rules # # file, and one sha256sum of the stream that pass writes, # # ~88 kB each. That is far more I/O per exec than # # 99-fbset-modes (2 kB) at a comparable exec count, and the # # virtiofs root is where this arm is slowest, so the same # # 300 s is declared rather than the 60 s default. Same value # # and reason as 99-fbset-modes and 99-libjs-sizzle. # # NOTHING HERE MAY PUT THE 513 FILE NAMES ON A COMMAND LINE. That is a 21 kB # argv block against this platform's 8192 B __wasm32_args_buf, and the exec does # not fail cleanly -- IT WEDGES THE GUEST, losing the whole suite and reporting # no cases at all (brintos/hardwarejs#15). The first draft of this file did # exactly that with a `"$D"/*` glob and measured 21173 B. So the file list is # built once by find and passed to gawk AS A FILE, and the manifest digest is # taken over a stream gawk writes rather than over `sha256sum *`. Keep it that # way: the payload grows with every upstream release, so a spelling that merely # fits today is not safe. # # usb-modeswitch-data 20251207 -- the usb_modeswitch device database. # meta-openembedded/meta-oe, recipes-support/usb-modeswitch. # # WHAT SHIPS: 513 config files under /usr/share/usb_modeswitch named # :, each saying how to switch one multi-mode USB device out of the # storage personality it presents at plug-in, plus the udev rules file # /lib/udev/rules.d/40-usb_modeswitch.rules that dispatches on them, plus an # empty /etc/usb_modeswitch.d for local overrides. 514 files, 88,585 bytes. # # NOTHING ON THIS IMAGE CONSUMES ANY OF IT, and that is deliberate rather than # an oversight this suite is papering over. The consumer is usb_modeswitch(1), # which issues a USB control transfer to a device; this kernel HAS a USB core # (see the consumer block -- /sys/bus/usb exists and four drivers are # registered) but NO host controller and therefore no devices at all, so the # binary is INAPPLICABLE here rather than merely missing. The port's bbappend # clears upstream's `RDEPENDS:${PN} = "usb-modeswitch (>= 2.4.0)"` for that # reason -- and because leaving it in makes the data package UNBUILDABLE: the # runtime dependency is resolved at do_build, and three recipes along it reaches # eudev, which the HWJS-3 coro pass refuses to compile # (setjmp-2b-unwind-through-non-coroutinizable-frame). The consumer block at the # bottom asserts that absence rather than assuming it, and asserts the reason # too, so "no consumer" is a measurement here and not a shrug. # # SO WHAT IS THIS SUITE A STATEMENT ABOUT? The install and packaging path, which # is the only part that can break for a recipe that compiles nothing. That the # payload reached a virtiofs root intact; that ${datadir} and # ${nonarch_base_libdir} resolved; that it landed in the package the image # installs rather than in the -dev or -dbg package it does not; that no other # recipe claims the same paths. That was the ticket's stated risk. # # BYTE IDENTITY IS EXPECTED BY CONSTRUCTION AND ASSERTED ANYWAY. The recipe # inherits allarch and the .deb is Architecture: all, so the wasm32 package # SHOULD equal what x86-64 builds. The reference below is upstream's tarball put # through upstream's own `make install` on x86-64 -- not a re-hash of our own # .deb, which would have made the comparison tautological. # # A DIGEST ALONE WOULD BE A WEAK STATEMENT, because one wrong byte and one # missing file produce the same red with the same message. So # data/usb-modeswitch-parse.awk reads the database AND the rules file on one # pass and reports by cause -- file count, byte count, malformed names, malformed # lines, unknown keys -- and computes the one CROSS-HALF invariant a digest # cannot express: every : the rules file dispatches on has a database # entry (UMS_ORPHAN). The parser was mutation-tested against eight deliberate # defects (a dropped file, a truncated file, an unknown key, a malformed line, a # malformed filename, an orphan rule, a missing rules file, an added final # newline); each fires its own counter. # # THE REVERSE DIRECTION IS NOT AN ERROR and is deliberately not asserted: 83 # database entries have no per-device rule because they are reached through the # blanket per-vendor rules instead. A suite demanding symmetry would go red on a # correct payload. . "$BST_LIB" S=usb-modeswitch-data # THE CANARY, which asserts nothing about usb-modeswitch-data. # # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and NOT a defect in the host tool # (BRINTOS-1149 established that and filed it as linux-in-the-browser#177). The # harness reports the deficit but computes its pass count as total-minus- # failures, so THE DROPPED LINE IS COUNTED AS A PASS. A nothing-case therefore # stands in that position and the eaten line costs nothing. It sits ahead of the # `require` guards deliberately, because a `require` that skips would emit its # skip as the first line and that line would be eaten too. Same idiom and reason # as 99-fbset-modes and 99-mailcap. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $S/suite-canary D=/usr/share/usb_modeswitch R=/lib/udev/rules.d/40-usb_modeswitch.rules E=/etc/usb_modeswitch.d tmp=${TMPDIR:-/tmp} W=$tmp/bst-usb-modeswitch-data.$$ require gawk $S/gawk-present require sha256sum $S/sha256sum-present require find $S/find-present # ------------------------------------------------------------- payload shape # ONE find over everything this suite has an expectation about, including the # four paths the package must NOT have produced. %y catches a symlink where a # regular file is meant, %m the mode, %l a link target -- none of which # `test -f` can see. Counting types and modes here rather than per-file is what # keeps 513 files inside one exec. find "$D" "$R" "$E" /usr/share/doc/usb-modeswitch-data \ "$R.dpkg-new" "$R.dpkg-old" \ -maxdepth 0 -printf '%p %y %m\n' 2>/dev/null | LC_ALL=C sort > "$W.top" find "$D" -mindepth 1 -printf '%y %m\n' 2>/dev/null | LC_ALL=C sort | uniq -c \ | LC_ALL=C sed 's/^ *//' > "$W.db" # The file list gawk will read, in one deterministic order. LC_ALL=C fixes the # collation -- the names carry ':' and a locale that sorted differently would # reorder the canonical stream and change the digest from identical bytes. find "$D" -type f 2>/dev/null | LC_ALL=C sort > "$W.list" for pair in "db-dir:$D" "rules:$R" "etc-dir:$E"; do nm=${pair%%:*}; f=${pair#*:} if [ -e "$f" ]; then bst_ok "$S/present-$nm" else bst_notok "$S/present-$nm" "$f does not exist"; fi done if [ ! -d "$D" ] || [ ! -f "$R" ]; then bst_notok $S/parse-prereq "a payload path is missing; the scan below cannot run" rm -f "$W.top" "$W.db" "$W.list" return 2>/dev/null || exit 0 fi # The three paths that must exist, by type. A -doc directory or a dpkg conffile # leftover would have shown up in the same find and is asserted absent below. check_eq $S/db-dir-is-dir "$D d 755" "$(grep "^$D " "$W.top")" check_eq $S/rules-is-file "$R f 644" "$(grep "^$R " "$W.top")" check_eq $S/etc-dir-is-dir "$E d 755" "$(grep "^$E " "$W.top")" # THE FOUR ABSENCES, asserted as one empty string rather than four `test -e` # forks. The recipe installs nothing to ${docdir}, so no -doc package is built # at all; a .dpkg-new or .dpkg-old beside the rules file would mean the install # resolved a conflict rather than placing the file -- which is also the only way # the digest below could be right while the file udev would read is a different # one. check_eq $S/no-doc-dir '' "$(grep '^/usr/share/doc/' "$W.top")" check_eq $S/no-dpkg-new '' "$(grep -- '-new ' "$W.top")" check_eq $S/no-dpkg-old '' "$(grep -- '-old ' "$W.top")" # EVERY database entry is a regular file at 0644 -- one line of output means one # (type, mode) pair across all 513, so a single symlink or a stray 0755 makes # this two lines and fails by cause rather than only shifting a digest. check_eq $S/db-uniform '513 f 644' "$(cat "$W.db")" check_eq $S/db-one-class 1 "$(wc -l < "$W.db")" # /etc/usb_modeswitch.d is upstream's hook for local per-device overrides and # ships EMPTY. Asserted because a file appearing there would change what # usb_modeswitch reads without changing anything this suite digests. check_eq $S/etc-dir-empty '' "$(find "$E" -mindepth 1 2>/dev/null)" # ------------------------------------------------- the payload, as a database # One pass reads all 513 entries and the rules file, emits the counts below, and # writes the canonical byte stream the digest is taken over. The list comes in # as a FILE -- see the argv note in the header. LC_ALL=C gawk -v LIST="$W.list" -v RULES="$R" -v CATOUT="$W.cat" \ -f "$BST_DATA/usb-modeswitch-parse.awk" > "$W.f1" 2>/dev/null . "$W.f1" # ------------------------------------------ content, against x86-64 reference # THE MANIFEST DIGEST pins the file set, their names, their ORDER and their # contents in one value. The stream is each entry's basename on its own line # followed by that entry's bytes verbatim, in sorted order -- which is exactly # what this reproduces on any host, so the constant stays checkable by hand: # # ( cd /usr/share/usb_modeswitch \ # && for f in $(ls | LC_ALL=C sort); do printf '%s\n' "$f"; cat "$f"; done \ # ) | sha256sum # # The reference was produced by extracting usb-modeswitch-data-20251207.tar.bz2 # on x86-64, running upstream's `make install`, and applying that command to the # result (2026-08-23) -- upstream's artifact by upstream's own route, not a # re-hash of our own .deb. `diff -rq` between that tree and this package's .deb # reports no differences across all 514 files. db_manifest=$(sha256sum "$W.cat" | cut -d' ' -f1) check_eq $S/db-manifest-sha256 \ d92276721c05ec40f6e7e114870fb34bbb0c4c7b09ff525f4be51e8f4c8c7e5f "$db_manifest" rules_sha=$(sha256sum "$R" | cut -d' ' -f1) check_eq $S/rules-sha256 \ 28bab372dadfe131f9c3ee2542b19b1b89eaa014ee7e7bb554330199865bd93d "$rules_sha" # THE SCAN RAN AT ALL. Several cases below assert a zero, and zero is exactly # what a gawk that died before END hands back -- which is what an unreadable # input produces, gawk being fatal on one. Asserted first, so those zeros rest # on something. check_eq $S/scan-ran 'ok' "${UMS_SCAN:-}" # gawk's file count against find's. These are two independent readers of the # same directory and they disagree in exactly one interesting case: a ZERO-BYTE # entry never fires gawk's FNR==1 rule, so it is invisible to the parser while # find still counts it. Comparing them is the only thing in this suite that # notices a file truncated to nothing. check_eq $S/db-files 513 "${UMS_DB_FILES:-}" check_eq $S/db-bytes 45595 "${UMS_DB_BYTES:-}" check_eq $S/db-ids 498 "${UMS_DB_IDS:-}" # 513 files but 498 distinct :: 21 entries carry a discriminator # suffix (:uPr=..., :uMx=...) so one device id can have several config files # selected by product or manufacturer string. Pinned because a reader that # dropped the suffix would report 498 files and look merely off-by-a-few. check_eq $S/db-name-shape 0 "${UMS_BADNAME:-}" check_eq $S/db-line-shape 0 "${UMS_BADLINE:-}" check_eq $S/db-known-keys 0 "${UMS_BADKEY:-}" # FOUR of the 513 ship with no final newline. That is upstream's payload as it # stands and it is asserted rather than tolerated: anything that re-encoded the # database in transit -- a text-mode copy, a line-ending fixup -- would most # likely normalise these four, which changes the digest above without changing a # single character anyone can see. check_eq $S/db-no-final-nl 4 "${UMS_NO_FINAL_NL:-}" # The rules half, by the same reader. check_eq $S/rules-seen 1 "${UMS_RULES_SEEN:-}" check_eq $S/rules-lines 1262 "${UMS_RULES_LINES:-}" check_eq $S/rules-bytes 42990 "${UMS_RULES_BYTES:-}" check_eq $S/rules-ids 415 "${UMS_RULES_IDS:-}" # THE HEADLINE, and the reason the parser reads both halves on one pass: every # device the rules file dispatches on by : has a config file to # dispatch TO. A database truncated anywhere breaks this, and unlike the digest # it says WHICH SIDE went missing. The reverse surplus (83 db entries with no # per-device rule) is legitimate -- see the header -- and is not asserted. check_eq $S/rules-have-db 0 "${UMS_ORPHAN:-}" # The key census. These are what the database IS -- the switching methods it # knows -- and they are pinned as a dated snapshot of 20251207 rather than as # invariants: a later release moves them, and moving them is a version bump to # be re-measured, not a regression. They are here because a payload that arrived # with the right byte count and the wrong content fails these by cause. check_eq $S/key-targetvendor 341 "${UMS_KEY_TargetVendor:-}" check_eq $S/key-targetproduct 256 "${UMS_KEY_TargetProduct:-}" check_eq $S/key-standardeject 182 "${UMS_KEY_StandardEject:-}" check_eq $S/key-messagecontent 115 "${UMS_KEY_MessageContent:-}" check_eq $S/key-targetclass 97 "${UMS_KEY_TargetClass:-}" check_eq $S/key-targetproductlist 85 "${UMS_KEY_TargetProductList:-}" check_eq $S/key-configuration 75 "${UMS_KEY_Configuration:-}" check_eq $S/key-huaweinewmode 62 "${UMS_KEY_HuaweiNewMode:-}" check_eq $S/key-optionmode 61 "${UMS_KEY_OptionMode:-}" check_eq $S/key-needresponse 19 "${UMS_KEY_NeedResponse:-}" # --------------------------------------------------------------- the consumer # THE DATABASE IS INERT ON THIS IMAGE, BY CONSTRUCTION. Everything below is a # measurement of why, not an excuse for it -- and every case is derived from # what is installed, so none goes false the day any of it changes. # # usb_modeswitch(1) is absent because the port's bbappend cleared the RDEPENDS # that would have pulled it in. Were it present it still could not work -- but # NOT for the reason one would guess, and the difference was measured on target # (2026-08-23) after an earlier draft of this block asserted the guess and went # red on it. # # THE KERNEL'S USB CORE IS BUILT IN. /sys/bus/usb exists, carrying devices/, # drivers/, drivers_autoprobe, drivers_probe and uevent; four drivers are # registered -- usb, hub, usbfs and ftdi_sio -- and /sys/bus lists usb-serial # beside usb. So "this platform has no USB code" is simply false, and a suite # saying so would be asserting a fiction that happens to give the right verdict. # # WHAT IS ACTUALLY ABSENT IS EVERY DEVICE. /sys/bus/usb/devices is EMPTY: there # is no host controller, so nothing is ever enumerated. And /dev/bus is absent # entirely, so the usbfs node tree libusb walks does not exist -- libusb_init # would succeed and find zero devices. That is why a mode switch is impossible # here, and it is a statement about the hardware this platform models rather # than about what was compiled. # # `usb-core-present` is therefore the ORDERING CONTROL, and it is asserted # POSITIVELY. Without it, the three absences below are compatible with USB # having been configured out, which would make them uninteresting; with it, they # say the stack is there and has nothing to talk to. If it ever goes red the # other three stop meaning what this comment claims. if command -v usb_modeswitch >/dev/null 2>&1; then bst_skip $S/consumer-absent \ "usb_modeswitch is now on PATH -- the binary package has landed and an end-to-end switch attempt is possible; it is not covered here" else bst_ok $S/consumer-absent fi if [ -d /sys/bus/usb ]; then bst_ok $S/usb-core-present else bst_notok $S/usb-core-present \ "/sys/bus/usb is gone; USB was configured out of this kernel, and the three absences below no longer establish what they were written to establish" fi usb_dev=$(ls -A /sys/bus/usb/devices 2>/dev/null | tr '\n' ',') check_eq $S/no-usb-devices '' "$usb_dev" for p in /dev/bus/usb /proc/bus/usb; do id=$S/no-usb$(echo "$p" | tr '/' '-') if [ -e "$p" ]; then bst_notok "$id" "$p exists; a USB node tree appeared and libusb could now enumerate through it" else bst_ok "$id" fi done # NOTHING READS THE RULES FILE EITHER, for the same kind of reason one level up: # udev rules are read by a udev daemon and there is none here. This package is # in fact the FIRST thing in this image to create /lib/udev/rules.d at all -- # checked across poky, meta-openembedded and meta-brintos, 27 recipes ship # something under udev/rules.d and none of them is installed here. So the # directory holding exactly this one file is the derived expectation, and it # turns into a skip rather than a red the day a second package lands one. if command -v udevd >/dev/null 2>&1 || [ -e /sbin/udevd ] || [ -e /lib/systemd/systemd-udevd ]; then bst_skip $S/no-udevd "a udev daemon is now installed; the rules file is no longer inert and is not covered here" else bst_ok $S/no-udevd fi rules_dir_n=$(find /lib/udev/rules.d -type f 2>/dev/null | wc -l) if [ "$rules_dir_n" = 1 ]; then bst_ok $S/rules-dir-sole-owner else bst_skip $S/rules-dir-sole-owner \ "/lib/udev/rules.d now holds $rules_dir_n rules files; this package was the only one shipping one" fi rm -f "$W.top" "$W.db" "$W.list" "$W.cat" "$W.f1"