# BST-TIMEOUT: 600 # ~60 cases over 12 execs, two of which read the WHOLE # # payload -- one gawk pass over 28 files / 470,100 bytes and # # one sha256sum of the stream that pass writes. That is five # # times 99-usb-modeswitch-data's bytes at a comparable exec # # count, and the virtiofs root is where this arm is slowest, # # so 600 s is declared rather than that suite's 300 s. # # NOTHING HERE MAY PUT THE PAYLOAD'S FILE NAMES ON A COMMAND LINE. 35 entries is # a small argv today, but an overrun of this platform's 8192 B # __wasm32_args_buf does not fail the exec -- IT WEDGES THE GUEST and the whole # suite reports no cases at all (brintos/hardwarejs#15). The file list is built # once by find and handed to gawk AS A FILE, and the manifest digest is taken # over a stream gawk writes rather than over `sha256sum `. Same idiom and # same reason as 99-usb-modeswitch-data. # # sound-theme-freedesktop 0.8 -- the freedesktop.org reference sound theme. # meta-openembedded/meta-oe, recipes-multimedia/sound-themes. # # WHAT SHIPS: /usr/share/sounds/freedesktop, holding index.theme (77 bytes) and # stereo/ with 27 Ogg Vorbis files and 8 symlinks -- 5 directories, 28 regular # files, 470,100 bytes. This is the offline data the XDG Sound Theme # specification's lookup algorithm resolves against. # # NO BBAPPEND AND NO PATCH. The meta-oe recipe is taken exactly as written and # built 1854 tasks with zero ERROR: lines on the first attempt. Recorded # explicitly because most neighbours in this block carry an override, and a # reader finding none here should know it was checked rather than forgotten: # the package compiles nothing for wasm32 -- `inherit autotools gettext' runs a # configure and an install and no compiler ever sees a target source file -- so # there is nothing on this arm for an override to reach. Its DEPENDS are # glib-2.0 (for glib-gettext.m4) and intltool-native / glib-2.0-native, all of # which serve autoconf and none of which reaches the target. # # 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 -- plus one # thing that is specific to this package and is the real reason the suite is # more than a digest. See the alias block below. # # THE PAYLOAD IS UPSTREAM'S OWN BYTES, and that is a measurement rather than an # assumption. `diff -rq' between the SRC_URI tarball's stereo/ directory # (sha256 cb518b20...8a14) and this package's .deb reports NO content # differences across all 27 sound files -- the only entries the .deb adds are # the 8 symlinks. So the digests below are pinned against upstream's artifact # reached by upstream's own route, not against a re-hash of our own build. # # THE ALIASES ARE THE INTERESTING HALF, and they are not in the tarball. Eight # spec-named sounds -- dialog-error, window-attention, window-question, # power-plug, power-unplug, network-connectivity-established, # network-connectivity-lost, screen-capture -- exist only as symlinks that # upstream's stereo/Makefile.am creates in an `install-data-hook'. A hook is # make-time work outside the install rules automake generates, and it is exactly # the kind of step a cross build can drop while every file list still looks # complete: Directories would still name stereo, the 27 real files would still # be there, and the theme would silently stop resolving a quarter of the names # the specification asks it for. So the aliases are asserted three ways -- that # they are symlinks and not copies, that each points at the target upstream # declared, and that each RESOLVES to a readable Ogg through the link. # # CHANNEL COUNTS ARE ASSERTED, AND 12 OF THE 27 ARE MONO. That looks wrong under # a directory named `stereo' and is upstream's payload as it stands: in the XDG # specification `stereo' is an OUTPUT PROFILE -- which speaker layout the sound # was authored for -- not a channel count, and a one-channel file is a perfectly # ordinary member of a stereo-profile theme. It is pinned so that nobody # re-encodes the payload to `fix' it, which would change every digest here while # changing nothing anyone can hear. # # THE INDEX CARRIES NO TRANSLATIONS AND THAT IS UPSTREAM TOO. The recipe inherits # gettext, the index is generated from index.theme.in through INTLTOOL_MERGE, # and the shipped file still has a bare `Name=Default' with no Name[xx] lines -- # because the 0.8 tarball's po/ directory contains only Makefile.in.in and # POTFILES.in and NOT ONE .po file. Asserted as a zero (`no-translations') so # the absence reads as the measured upstream fact it is, rather than as evidence # of an intltool run that failed on this arm. # # NO AUDIO DEVICE IS OPENED AND NONE NEEDS TO EXIST. Nothing here plays # anything: the theme is data, and every assertion is a function of local files. # The consumer block at the bottom measures that there is no player and no # device rather than assuming it. . "$BST_LIB" S=sound-theme-freedesktop # THE CANARY, which asserts nothing about sound-theme-freedesktop. # # 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. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $S/suite-canary D=/usr/share/sounds/freedesktop I=$D/index.theme tmp=${TMPDIR:-/tmp} W=$tmp/bst-sound-theme-freedesktop.$$ require find $S/find-present require gawk $S/gawk-present require sha256sum $S/sha256sum-present # ------------------------------------------------------------- payload shape # ONE find over everything this suite has an expectation about, including the # three paths the package must NOT have produced. %y catches a symlink where a # regular file is meant, %m the mode -- neither of which `test -f' can see. find "$D" "$I" "$D/stereo" /usr/share/doc/sound-theme-freedesktop \ "$I.dpkg-new" "$I.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.shape" for pair in "theme-dir:$D" "index:$I" "stereo-dir:$D/stereo"; 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 "$I" ]; then bst_notok $S/scan-prereq "a payload path is missing; the scan below cannot run" rm -f "$W.top" "$W.shape" return 2>/dev/null || exit 0 fi check_eq $S/theme-dir-is-dir "$D d 755" "$(grep "^$D " "$W.top")" check_eq $S/index-is-file "$I f 644" "$(grep "^$I " "$W.top")" check_eq $S/stereo-dir-is-dir "$D/stereo d 755" "$(grep "^$D/stereo " "$W.top")" # THE THREE ABSENCES, asserted as one empty string rather than three `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 index 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 a theme reader opens 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")" # The whole payload as three (type, mode) classes. One line per class means the # mode is uniform across all 28 regular files, so a single stray 0755 or a # regular file where a symlink belongs fails BY CAUSE rather than only shifting # a digest. check_eq $S/shape-dirs '1 d 755' "$(grep ' d ' "$W.shape")" check_eq $S/shape-files '28 f 644' "$(grep ' f ' "$W.shape")" check_eq $S/shape-links '8 l 777' "$(grep ' l ' "$W.shape")" check_eq $S/shape-classes 3 "$(wc -l < "$W.shape")" # --------------------------------------------------------------- the index # EVERY read of the index goes through a fork -- $( ) or a tool that opens the # path itself -- and never through the top-level shell's stdin. `cmd < $I' makes # dup2(2) return 513 on the virtiofs root, ash prints `Unknown error 513' and # EXITS, which ends the suite while the runner counts every case already emitted # as a pass (brintos/linux#73). check_eq $S/index-size 77 "$(find "$I" -printf '%s' 2>/dev/null)" check_eq $S/index-sha256 \ 0dd956b7a0d9f600a5a2e1c2799abb4dff8b1aeb752e41b02405ff8bc46adf29 \ "$(sha256sum "$I" | cut -d' ' -f1)" # The file entire, newlines collapsed to '|'. This is the one assertion that # cannot be satisfied by a file with the right keys in the wrong sections. check_eq $S/index-verbatim \ '[Sound Theme]|Name=Default|Directories=stereo||[stereo]|OutputProfile=stereo' \ "$(sed 's/$/|/' "$I" | tr -d '\n' | sed 's/|$//')" # The [Sound Theme] section and its keys, read as the specification names them. check_eq $S/index-section '[Sound Theme]' "$(head -1 "$I")" check_eq $S/index-name 'Name=Default' "$(grep '^Name=' "$I")" check_eq $S/index-directories 'Directories=stereo' "$(grep '^Directories=' "$I")" # THE BASE THEME INHERITS NOTHING. Every other freedesktop sound theme carries # `Inherits=freedesktop'; this one is the root of that chain, so an Inherits key # appearing here would mean the file is not the theme it claims to be. check_eq $S/index-no-inherits '' "$(grep '^Inherits' "$I")" # NO Name[xx] TRANSLATIONS -- upstream's own state, see the header. A number # rather than an emptiness, so a future release that ships po/ files turns this # into a re-measurement with a count in the message instead of a bare red. check_eq $S/index-no-translations 0 "$(grep -c '^Name\[' "$I")" # The per-directory section the Directories key points at. check_eq $S/index-stereo-section '[stereo]' "$(grep '^\[stereo\]' "$I")" check_eq $S/index-output-profile 'OutputProfile=stereo' \ "$(grep '^OutputProfile=' "$I")" # EVERY DIRECTORY THE INDEX DECLARES EXISTS, derived from the file rather than # from the constant above it -- this is the cross-check that a hand-written # `Directories=stereo' expectation cannot make. Semicolon-separated per the # specification, even where today's value is a single name. dirs=$(grep '^Directories=' "$I" | cut -d= -f2- | tr ';' ' ') check_eq $S/declared-dir-count 1 "$(echo $dirs | wc -w)" declared_missing= for sub in $dirs; do [ -d "$D/$sub" ] || declared_missing="$declared_missing $sub" done check_eq $S/declared-dirs-exist '' "$declared_missing" # AND NOTHING ELSE IS THERE. A subdirectory the index does not declare is # unreachable by the lookup algorithm, so it would be payload no reader can see. check_eq $S/no-undeclared-dirs 'stereo' \ "$(find "$D" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | LC_ALL=C sort | tr '\n' ' ' | sed 's/ $//')" # ------------------------------------------------ the payload, as Ogg Vorbis # One gawk pass reads all 28 files, emits the counters 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 -- and holds names RELATIVE to $D, which is # what makes the digest reproducible by the command quoted in the parser. ( cd "$D" && find . -type f | sed 's|^\./||' | LC_ALL=C sort ) > "$W.names" LC_ALL=C gawk -v NAMES="$W.names" -v ROOT="$D" -v CATOUT="$W.cat" \ -f "$BST_DATA/sound-theme-freedesktop-ogg.awk" > "$W.vars" 2>/dev/null . "$W.vars" # THE SCAN RAN AT ALL. Several cases below assert a zero, and zero is exactly # what a gawk that died before its 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' "${STF_SCAN:-}" # find's count against gawk's. Two independent readers of the same tree, and # they disagree in exactly one interesting case: a file find lists that gawk # cannot open is a permission or an I/O fault rather than a missing file, and # nothing else in this suite would notice it. check_eq $S/scan-names 28 "${STF_NAMES:-}" check_eq $S/scan-files 28 "${STF_FILES:-}" check_eq $S/scan-readfail 0 "${STF_READFAIL:-}" check_eq $S/scan-bytes 470100 "${STF_BYTES:-}" check_eq $S/scan-oga 27 "${STF_OGA:-}" # NON-EMPTY AND READABLE, which is the promise a data package makes. A file # truncated to nothing still satisfies every count above it. check_eq $S/oga-none-empty 0 "${STF_EMPTY:-}" check_eq $S/oga-smallest 4792 "${STF_MIN:-}" check_eq $S/oga-largest 73696 "${STF_MAX:-}" # THE CONTAINER, file by file. Every .oga carries the Ogg capture pattern AND a # Vorbis identification header as its first packet -- the second is the stronger # statement, since `OggS' alone is satisfied by an Ogg stream carrying anything. check_eq $S/oga-all-ogg 0 "${STF_NOTOGG:-}" check_eq $S/oga-all-vorbis 0 "${STF_NOTVORBIS:-}" # THE STREAM PARAMETERS, read out of that header. See the header block for why # 12 of the 27 being mono is upstream's payload and not a defect. check_eq $S/oga-channels-valid 0 "${STF_BADCH:-}" check_eq $S/oga-mono 12 "${STF_MONO:-}" check_eq $S/oga-stereo 15 "${STF_STEREO:-}" # The sample-rate census. Pinned as a dated snapshot of 0.8 rather than as an # invariant: a later release moves it, and moving it is a version bump to be # re-measured. It is here because a payload that arrived with the right byte # count and re-encoded content fails these by cause. check_eq $S/oga-rate-8000 2 "${STF_SR_8000:-}" check_eq $S/oga-rate-22050 2 "${STF_SR_22050:-}" check_eq $S/oga-rate-44100 11 "${STF_SR_44100:-}" check_eq $S/oga-rate-48000 11 "${STF_SR_48000:-}" check_eq $S/oga-rate-96000 1 "${STF_SR_96000:-}" # THE MANIFEST DIGEST pins the file set, their names, their ORDER and their # contents in one value -- see the parser for the shell command that reproduces # it by hand. The reference is upstream's tarball, not our own .deb: `diff -rq' # between the two reports no content differences across all 27 sound files # (2026-08-31). check_eq $S/payload-manifest-sha256 \ 0d22baa13d768baf7b5c3b838cbc99a69789fe3e838bf8ad78fc699f64cbdf17 \ "$(sha256sum "$W.cat" | cut -d' ' -f1)" # ------------------------------------------------------------- the aliases # The eight install-data-hook symlinks. See the header for why these are the # part of this package most likely to go missing while everything else looks # right. Each is asserted THREE ways, and the three fail differently: a copy # instead of a link fails is-link; a link to the wrong file fails target; a link # to a name that is not there fails resolves. alias_spec='dialog-error.oga:dialog-warning.oga network-connectivity-established.oga:device-added.oga network-connectivity-lost.oga:device-removed.oga power-plug.oga:device-added.oga power-unplug.oga:device-removed.oga screen-capture.oga:camera-shutter.oga window-attention.oga:dialog-warning.oga window-question.oga:dialog-warning.oga' check_eq $S/alias-count 8 \ "$(find "$D/stereo" -maxdepth 1 -type l -printf 'x\n' | wc -l)" # The link set as one string, so an alias that appeared and is not in the list # above fails here rather than going unnoticed by the per-alias loop. check_eq $S/alias-set \ 'dialog-error.oga network-connectivity-established.oga network-connectivity-lost.oga power-plug.oga power-unplug.oga screen-capture.oga window-attention.oga window-question.oga' \ "$(find "$D/stereo" -maxdepth 1 -type l -printf '%f\n' | LC_ALL=C sort | tr '\n' ' ' | sed 's/ $//')" for spec in $alias_spec; do a=${spec%%:*}; t=${spec#*:} p=$D/stereo/$a if [ -L "$p" ]; then bst_ok "$S/alias-is-link-$a" else bst_notok "$S/alias-is-link-$a" "$p is not a symlink; the install-data-hook's link was replaced by a copy or by nothing"; fi check_eq "$S/alias-target-$a" "$t" "$(find "$p" -maxdepth 0 -printf '%l' 2>/dev/null)" # RESOLVES, which is the only one of the three that reads through the link. # A relative link with a correct-looking target still dangles if the target # landed in a different package. if [ -f "$p" ] && [ -r "$p" ]; then bst_ok "$S/alias-resolves-$a" else bst_notok "$S/alias-resolves-$a" "$p does not resolve to a readable regular file"; fi done # --------------------------------------------- the specification's own lookup # WHAT THE PACKAGE IS FOR, exercised the way a sound-theme reader would: for # each subdirectory the index declares, in the order it declares them, try # .disabled then .oga, .ogg, .wav and take the first hit. Nothing # below hard-codes stereo/ -- the search path comes from the index read above, # so this fails if the index and the tree ever stop agreeing. stf_lookup() { for _sub in $dirs; do for _ext in disabled oga ogg wav; do if [ -f "$D/$_sub/$1.$_ext" ]; then printf '%s\n' "$_sub/$1.$_ext"; return 0; fi done done return 1 } # A real file, an alias, and a name the theme does not carry. check_eq $S/lookup-real 'stereo/bell.oga' "$(stf_lookup bell)" check_eq $S/lookup-alias 'stereo/dialog-error.oga' "$(stf_lookup dialog-error)" check_eq $S/lookup-absent '' "$(stf_lookup no-such-sound-here)" # THE ALIAS AND ITS TARGET ARE THE SAME BYTES, read through the lookup's answer. # This is the end-to-end statement the three alias cases above only approach: # resolving `dialog-error' through the specification's algorithm yields the # audio upstream meant it to yield. check_eq $S/lookup-alias-content \ "$(sha256sum "$D/stereo/dialog-warning.oga" | cut -d' ' -f1)" \ "$(sha256sum "$D/$(stf_lookup dialog-error)" | cut -d' ' -f1)" # --------------------------------------------------------------- the consumer # THE THEME IS INERT ON THIS IMAGE, BY CONSTRUCTION, and everything below is a # measurement of why rather than an excuse for it. A sound theme is played by a # libcanberra-style event-sound daemon against an audio device; this image has # neither, and the port never claimed to. Each case is derived from what is # installed, so none goes false the day any of it changes -- a player or a # device appearing turns its case into a SKIP naming what arrived, never into a # red, because that would be a finding about the image rather than about this # package. for p in canberra-gtk-play pactl paplay aplay ogg123; do if command -v "$p" >/dev/null 2>&1; then bst_skip "$S/no-player-$p" "$p is now on PATH -- an end-to-end play of a theme sound is possible; it is not covered here" else bst_ok "$S/no-player-$p" fi done for n in /dev/snd /proc/asound; do id=$S/no-audio-dev$(echo "$n" | tr '/' '-') if [ -e "$n" ]; then bst_skip "$id" "$n exists; this kernel now has a sound stack and the theme could be played rather than only read" else bst_ok "$id" fi done # THIS PACKAGE IS THE ONLY THING UNDER /usr/share/sounds, which is what makes # the tree counts above statements about it alone. A second theme landing there # turns this into a skip rather than a red: it would be a fact about the image. sounds_n=$(find /usr/share/sounds -mindepth 1 -maxdepth 1 -printf 'x\n' 2>/dev/null | wc -l) if [ "$sounds_n" = 1 ]; then bst_ok $S/sole-theme else bst_skip $S/sole-theme "/usr/share/sounds now holds $sounds_n entries; this package was the only theme on the image" fi rm -f "$W.top" "$W.shape" "$W.names" "$W.cat" "$W.vars"