. "$BST_LIB" # BST-TIMEOUT: 600 # MEASURED on target: 100-104 s for 260 cases, against # # 0.10 s on the x86 reference build. Phase A predicted this # # from the x86 figure and was wrong by three orders; the # # bound it guessed is nevertheless right, at ~6x headroom. # libmikmod 3.3.11.1 -- the module-player library, as meta-oe ships it from # meta-oe/recipes-multimedia/mikmod, with a meta-brintos bbappend that turns # threads and every device driver off. # # WHAT THIS IMAGE HAS. libmikmod is a library and an autoconf helper script; the # tarball's only program is the `mikmod` player, which lives in a different # tarball and is not here. So the deliverable is /usr/lib/libmikmod.a, # /usr/include/mikmod.h, the .pc and libmikmod-config, and there is nothing to # drive it with. /usr/libexec/libmikmod-tests/libmikmod-probe is the consumer # that stands in for one: it GENERATES three modules -- a 31-sample ProTracker # "M.K.", the same music as a 15-sample Soundtracker module, and a # ScreamTracker 3 S3M -- renders each through the wav and raw disk-writer # drivers at six settings, and digests the PCM. data/libmikmod-native.txt is # THAT SAME SOURCE built against THAT SAME release on x86, AT THE IMAGE'S # POINTER WIDTH (`gcc -m32`), so a disagreement is a disagreement between the # two machines and nothing else. # # THE CONTROL IS ILP32, AND THAT IS NOT A DETAIL. libmikmod picks a different # HQ mixer by pointer width, so an LP64 control would compare the wasm32 build # against code it does not run -- see "THE ONE THING POINTER WIDTH DECIDES" # below. 134 of the control's 140 lines are identical either way; the six that # are not are exactly the six the HQ mixer produces. # # NO AUDIO DEVICE IS INVOLVED, AND THE RECIPE IS WHAT MAKES THAT TRUE. The # bbappend passes --disable-alldrv and re-enables wav and raw only; configure # answers `Drivers to be compiled: wav raw nosound` on both machines. ALSA and # OSS -- which the stock recipe enables -- are off because drv_alsa.c reaches # libasound through dlopen(3) on a distro with no shared objects, and OSS wants # a /dev/dsp this image has not got. # # THE CORPUS IS GENERATED, NOT SHIPPED, which is why there is no .mod or .s3m in # data/. libmikmod's tarball carries no test module, and a binary fixture # committed beside the probe could drift from the code that reads it. Both # machines build the three modules from the same constants in libmikmod_probe.c, # so R0-corpus below is the check that they are decoding the same bytes -- read # it FIRST on any red, because every later line is meaningless if it disagrees. # # WHY THE COMPARISON IS EXACT. libmikmod mixes in fixed point: playercode/ # virtch.c and virtch2.c carry the sample position as a 32.16 SLONGLONG, the # volume ramp and both resamplers are integer multiplies, and there is no libm # call anywhere in playercode/. There is nothing here for an arm to round # differently, so a correct port is not close to the reference, it is identical # to it. A tolerance would be measuring nothing. # # THE ONE THING POINTER WIDTH DECIDES is the HQ mixer, and it is why the # control is built -m32. include/mikmod_internals.h defines NATIVE_64BIT_INT # only for LP64 (_LP64, __LP64__, __arch64__, __alpha, __powerpc64__). # playercode/virtch2.c then reads that macro to choose between two DIFFERENT # implementations -- the SLONGLONG MixStereoNormal/MixMonoNormal, or # Mix32StereoNormal/Mix32MonoNormal with 32-bit intermediates at FRACBITS 28, # under upstream's own comment "use the 32 bit mixers as often as we can # (they're much faster)". wasm32 is ILP32, so DMODE_HQMIXER runs the Mix32 # path, and so does an i686 or an armv7 build. Measured, not reasoned: an # x86 -m32 build of this same probe against this same release reproduces the # wasm32 PCM digest for all three modules byte for byte, while an x86-64 build # disagrees on exactly those six lines and no others. A red in # R2-{wav,pcm}-*-s16st44hq against an LP64 control is therefore a measurement # of the control's pointer width, not of the port. # # THE ONE PASS THAT IS NOT COMPARED is tagged R7 by the probe and skipped by the # diff below: DMODE_FLOAT routes the mix through Mix32ToFP() (virtch.c:775), and # configure appends -ffast-math to CFLAGS unconditionally, which licenses either # compiler to reassociate that scaling. Excluding it is a decision recorded here # rather than a tolerance hidden in a comparison; its structure is still # asserted below. # # READ A RED IN TAG ORDER -- the first tag to disagree localises the defect: # # R0 the generated corpus both machines built the same three inputs # R1 metadata and sample table the three loaders parsed their headers # R2 wav container + PCM digests the whole mixer, six settings, per format # R3 raw driver PCM the second output path agrees with the first # R4 MikMod_Update() call counts the player loop ran the same length # R5 nosound run, malformed input the player without an output, and refusals # R6 driver ordinals and version registration order and the library identity D=/usr/libexec/libmikmod-tests PROBE=$D/libmikmod-probe NATIVE=$BST_DATA/libmikmod-native.txt ARCHIVE=/usr/lib/libmikmod.a if [ ! -f "$ARCHIVE" ]; then bst_skip libmikmod/present "libmikmod not installed on this image" exit 0 fi bst_ok libmikmod/present # --- the package is whole ---------------------------------------------------- check libmikmod/staticlib test -f "$ARCHIVE" check libmikmod/header test -f /usr/include/mikmod.h check libmikmod/pkgconfig test -f /usr/lib/pkgconfig/libmikmod.pc check libmikmod/aclocal test -f /usr/share/aclocal/libmikmod.m4 check libmikmod/config-bin test -x /usr/bin/libmikmod-config check_out libmikmod/pc-libs "-lmikmod" cat /usr/lib/pkgconfig/libmikmod.pc check_out libmikmod/pc-private "Libs.private: -lm" cat /usr/lib/pkgconfig/libmikmod.pc check_out libmikmod/pc-version "Version: 3.3.11" cat /usr/lib/pkgconfig/libmikmod.pc # THE PACKAGE VERSION AND THE LIBRARY VERSION ARE NOT THE SAME STRING, and both # are pinned so a future bump cannot quietly change one of them. PV is 3.3.11.1 # (upstream's fourth field is a packaging revision); LIBMIKMOD_VERSION, which is # what the .pc and the -config script report and what MikMod_GetVersion() # encodes, is 3.3.11. check_eq libmikmod/config-version "3.3.11" "$(libmikmod-config --version 2>&1)" check_out libmikmod/config-libs "-lmikmod" libmikmod-config --libs check_eq libmikmod/config-prefix "/usr" "$(libmikmod-config --prefix 2>&1)" # --ldadd IS THE ON-TARGET EVIDENCE THAT THREADS ARE OFF. configure.ac:1283 sets # LIB_LDADD="-pthread" and only when HAVE_PTHREAD is defined; with the bbappend's # --disable-threads it stays empty, and this script echoes it verbatim. An image # where this case goes red is one where the append stopped reaching the recipe -- # which matters because wasm32-hwjs cannot run pthreads at all # (linux-in-the-browser#85), not merely because it would be slower. check_eq libmikmod/config-ldadd-empty "" "$(libmikmod-config --ldadd 2>&1)" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libmikmod/no-shared "/usr/lib/libmikmod*.so*" # --- the fixtures ship ------------------------------------------------------- check libmikmod/probe-present test -f "$PROBE" check libmikmod/native-present test -f "$NATIVE" if [ ! -f "$PROBE" ] || [ ! -f "$NATIVE" ]; then bst_notok libmikmod/probe-run "fixtures missing; cannot drive the player" exit 0 fi # --- run the probe ----------------------------------------------------------- # The probe is installed 0755 into libexecdir by the recipe, so unlike a data/ # payload it runs where it sits; nothing has to be staged into /tmp. The RENDERS # do go to /tmp: the rootfs is virtiofs, and what the disk-writer drivers are # being asked about is the write path rather than which filesystem it lands on. # The probe takes that directory as argv[1] and removes both files itself. out=${TMPDIR:-/tmp}/bst-libmikmod-out.$$ "$PROBE" "${TMPDIR:-/tmp}" > "$out" 2>&1 rc=$? if [ "$rc" -eq 0 ]; then bst_ok libmikmod/probe-rc else bst_notok libmikmod/probe-rc "rc=$rc $(_bst_1line "$(cat "$out" 2>/dev/null)")"; fi if grep -q '^ALL-LIBMIKMOD-OK$' "$out" 2>/dev/null; then bst_ok libmikmod/probe-allok else bst_notok libmikmod/probe-allok "$(_bst_1line "$(cat "$out" 2>/dev/null)")" fi lines=$(grep -c '^R[0-9]' "$out" 2>/dev/null || echo 0) if [ "$lines" -gt 0 ]; then bst_ok libmikmod/probe-output else bst_notok libmikmod/probe-output "probe emitted no tagged lines"; fi # --- compare every tagged line against the native control -------------------- # One case per reference line. The key is the tag alone for whole-library facts, # `tag format` for per-module ones and `tag format setting` for per-render ones, # so a red names the exact thing that disagreed rather than reporting one giant # diff. R7 lines are dropped on both sides -- see the header. awk ' function keyof(f1, f2, f3) { if (f1 ~ /^(R2-wavhdr|R2-wav|R2-pcm|R3-raw|R4-updates|R1-sample)$/) return f1 " " f2 " " f3 if (f1 ~ /^(R0-corpus|R1-load|R1-title|R1-modtype|R1-songname|R1-num|R1-init|R5-err)/) return f1 " " f2 return f1 } function fieldsof(k, n, i) { n = 1 for (i = 1; i <= length(k); i++) if (substr(k, i, 1) == " ") n++ return n } function valof(k, i, s, start) { start = fieldsof(k) + 1 s = "" for (i = start; i <= NF; i++) s = s (s == "" ? "" : " ") $i return s } FNR == NR { if ($1 ~ /^R[0-6]/) { k = keyof($1,$2,$3); want[k] = valof(k); order[++n] = k } next } { if ($1 ~ /^R[0-6]/) { k = keyof($1,$2,$3); got[k] = valof(k); seen[k] = 1 } } END { for (i = 1; i <= n; i++) { k = order[i] id = k; gsub(/[^A-Za-z0-9+-]+/, "-", id) if (!seen[k]) printf "FAIL %s missing-from-probe\n", id else if (got[k] == want[k]) printf "PASS %s\n", id else printf "FAIL %s want=[%s] got=[%s]\n", id, want[k], got[k] } } ' "$NATIVE" "$out" | while read -r verdict id detail; do case "$verdict" in PASS) bst_ok "libmikmod/$id" ;; *) bst_notok "libmikmod/$id" "$detail" ;; esac done # --- anchored: properties of the generated corpus ---------------------------- # These consult no control. Each is true by construction of the modules the # probe builds -- a 31-sample header is 1084 bytes before the patterns, a # 15-sample one is 600, an "M.K." module is four channels -- so a control # captured from an already-broken build cannot make any of them pass. _f() { awk -v T="$1" -v F="$2" '$1 == T && $2 == F { $1=""; $2=""; sub(/^ +/,""); print }' "$out"; } check_eq libmikmod/anchor-mod-size 3516 "$(_f R0-corpus mod | awk '{print $1}')" check_eq libmikmod/anchor-m15-size 3032 "$(_f R0-corpus m15 | awk '{print $1}')" check_eq libmikmod/anchor-s3m-size 1648 "$(_f R0-corpus s3m | awk '{print $1}')" for f in mod m15 s3m; do check_eq "libmikmod/anchor-numchn-$f" 4 "$(_f R1-numchn "$f")" check_eq "libmikmod/anchor-numpos-$f" 2 "$(_f R1-numpos "$f")" check_eq "libmikmod/anchor-numpat-$f" 2 "$(_f R1-numpat "$f")" check_eq "libmikmod/anchor-numtrk-$f" 8 "$(_f R1-numtrk "$f")" check_eq "libmikmod/anchor-load-$f" ok "$(_f R1-load "$f")" done # The three loaders do NOT agree about how many sample slots the format has, and # that is the point of carrying three: 31 is the ProTracker header, 15 the # Soundtracker one, and S3M declares its instrument count rather than reserving # slots. A port where all three answered the same number would be running one # loader three times. check_eq libmikmod/anchor-numins-mod 31 "$(_f R1-numins mod)" check_eq libmikmod/anchor-numins-m15 15 "$(_f R1-numins m15)" check_eq libmikmod/anchor-numins-s3m 2 "$(_f R1-numins s3m)" check_eq libmikmod/anchor-modtype-mod "Protracker" "$(_f R1-modtype mod)" check_eq libmikmod/anchor-modtype-m15 "Soundtracker" "$(_f R1-modtype m15)" check_eq libmikmod/anchor-modtype-s3m "Screamtracker 3.20" "$(_f R1-modtype s3m)" check_eq libmikmod/anchor-title-mod "BRINTOS-MIKMOD" "$(_f R1-title mod)" check_eq libmikmod/anchor-title-m15 "BRINTOS-MIK15" "$(_f R1-title m15)" check_eq libmikmod/anchor-title-s3m "BRINTOS-MIKS3M" "$(_f R1-title s3m)" # Player_LoadTitleMem() and the full Player_LoadMem() are separate entry points # that read the same header field; they must agree, or one of the two is reading # the wrong offset. for f in mod m15 s3m; do check_eq "libmikmod/anchor-title-agrees-$f" "$(_f R1-title "$f")" "$(_f R1-songname "$f")" done # The S3M header carries speed and tempo; the ProTracker one does not, so its # initial values are the format's defaults and the F-command in the pattern is # what changes them at playback. Both are asserted so a loader that started # inventing header values would be caught. check_eq libmikmod/anchor-speed-mod 6 "$(_f R1-initspeed mod)" check_eq libmikmod/anchor-tempo-mod 125 "$(_f R1-inittempo mod)" check_eq libmikmod/anchor-speed-s3m 3 "$(_f R1-initspeed s3m)" check_eq libmikmod/anchor-tempo-s3m 250 "$(_f R1-inittempo s3m)" # --- anchored: properties of the render -------------------------------------- _r() { awk -v T="$1" -v F="$2" -v C="$3" '$1 == T && $2 == F && $3 == C { print $4, $5, $6 }' "$out"; } _u() { awk -v F="$1" -v C="$2" '$1 == "R4-updates" && $2 == F && $3 == C { print $4 }' "$out"; } # The disk writers request a fixed 32768-byte block per MikMod_Update() (drv_wav.c # BUFFERSIZE), so the bytes written are the call count times the block size -- # arithmetic, not a measurement, and it holds for every setting including the # float one whose digests are not compared. for f in mod m15 s3m; do for c in s16st44 s16st44i s16st44hq u8mono22 s16rev float44; do n=$(_r R2-wav "$f" "$c" | awk '{print $1}') [ -n "$n" ] || n=$(_r R7-wav "$f" "$c" | awk '{print $1}') u=$(_u "$f" "$c") hdr=44; [ "$c" = float44 ] && hdr=58 if [ -n "$n" ] && [ -n "$u" ] && [ "$n" -eq $((u * 32768 + hdr)) ]; then bst_ok "libmikmod/anchor-blocksize-$f-$c" else bst_notok "libmikmod/anchor-blocksize-$f-$c" "wav=$n updates=$u header=$hdr" fi done done # Not silence. A port that mixed nothing would still agree on every LENGTH above # and would still write a file; only the non-zero count says sound came out. for f in mod m15 s3m; do for c in s16st44 s16st44i s16st44hq u8mono22 s16rev; do nz=$(_r R2-pcm "$f" "$c" | awk '{print $3}') if [ -n "$nz" ] && [ "$nz" -gt 0 ]; then bst_ok "libmikmod/anchor-nonsilent-$f-$c" else bst_notok "libmikmod/anchor-nonsilent-$f-$c" "$f/$c decoded $nz non-zero bytes"; fi done done # The raw driver's whole output must equal the wav driver's data chunk: two # output paths, one mixer. A disagreement localises to the container writer # rather than to the arithmetic, which is why the probe digests them separately. for f in mod m15 s3m; do w=$(_r R2-pcm "$f" s16st44) r=$(_r R3-raw "$f" s16st44) check_eq "libmikmod/anchor-raw-equals-pcm-$f" "$w" "$r" done # THE TWO MOD LOADERS MUST CONVERGE. `mod` and `m15` carry the same patterns and # the same samples in two different container layouts, so once loaded they are # the same UNIMOD and must render byte for byte identically. This is the case # that says format identification is choosing a loader rather than changing the # music, and it consults no control at all. for c in s16st44 s16st44i s16st44hq u8mono22 s16rev; do check_eq "libmikmod/anchor-mod-eq-m15-$c" "$(_r R2-pcm mod "$c")" "$(_r R2-pcm m15 "$c")" done # S3M is a different arrangement -- different sample encoding, different panning, # a header speed and tempo of its own -- so it must NOT render the same bytes. If # it did, the S3M loader would be producing the ProTracker module. check_eq_not() { if [ "$2" != "$3" ]; then bst_ok "$1" else bst_notok "$1" "the two renders are identical: $2"; fi } check_eq_not libmikmod/anchor-s3m-differs "$(_r R2-pcm mod s16st44)" "$(_r R2-pcm s3m s16st44)" # The four 44.1 kHz stereo 16-bit passes must not agree with each other BYTE FOR # BYTE: if any pair did, that setting would not be reaching the mixer and the R2 # digests would be pinning one code path twice. INTERP is the resampler, # HQMIXER swaps virtch.c for virtch2.c, and md_reverb turns on the reverb buffers. for f in mod s3m; do base=$(_r R2-pcm "$f" s16st44 | awk '{print $2}') for c in s16st44i s16st44hq s16rev; do h=$(_r R2-pcm "$f" "$c" | awk '{print $2}') if [ -n "$h" ] && [ -n "$base" ] && [ "$h" != "$base" ]; then bst_ok "libmikmod/anchor-setting-reaches-mixer-$f-$c" else bst_notok "libmikmod/anchor-setting-reaches-mixer-$f-$c" "$c produced the same PCM as s16st44 ($h)" fi done done # The RIFF header the wav driver writes, read back field by field. These are # properties of what was ASKED for -- 44.1 kHz stereo 16-bit PCM, or 22.05 kHz # mono 8-bit -- so a container that disagreed would be mislabelling correct PCM, # which no digest can catch. _hdr() { awk -v F="$1" -v C="$2" '$1 == "R2-wavhdr" && $2 == F && $3 == C { print $4, $5, $6, $7 }' "$out"; } for f in mod m15 s3m; do check_eq "libmikmod/anchor-wavhdr-$f-s16st44" "1 2 44100 16" "$(_hdr "$f" s16st44)" check_eq "libmikmod/anchor-wavhdr-$f-u8mono22" "1 1 22050 8" "$(_hdr "$f" u8mono22)" check_eq "libmikmod/anchor-wavhdr-$f-float44" "3 2 44100 32" "$(_hdr "$f" float44)" done # --- anchored: the player without an output ---------------------------------- # The nosound driver mixes nothing, so a module played through it exercises the # player loop alone. It must run the same number of updates as the wav render of # the same module, and it must reach the end of the order table. check_eq libmikmod/anchor-nosound-updates "$(_u mod s16st44)" \ "$(awk '$1 == "R5-nosound-updates" { print $2 }' "$out")" check_eq libmikmod/anchor-nosound-order 2 \ "$(awk '$1 == "R5-nosound-order" { print $2 }' "$out")" # --- anchored: malformed input ------------------------------------------------ # Every cut here removes part of a header, so refusing is the only correct # answer; what is asserted is that libmikmod says so through MikMod_errno rather # than crashing or returning a half-built module. The empty buffer is refused # with errno 0 -- Player_LoadMem() rejects it before any loader sets one -- and # that is pinned as it stands rather than tidied into something more plausible. for b in trunc-mod-64 trunc-mod-900 trunc-mod-1083 trunc-s3m-16 trunc-s3m-64 junk empty; do check_eq "libmikmod/anchor-refused-$b" refused \ "$(awk -v B="$b" '$1 == "R5-err" && $2 == B { print $3 }' "$out")" done # --- the driver registry ------------------------------------------------------ # Three drivers are registered, in this order, and MikMod_DriverFromAlias() # returns a 1-based rank. A fourth appearing, or an ordinal moving, means the # bbappend's --disable-alldrv stopped reaching configure and a device driver the # guest cannot use has been compiled back in. check_eq libmikmod/anchor-ordinal-wav 1 "$(awk '$1 == "R6-wav-ordinal" { print $2 }' "$out")" check_eq libmikmod/anchor-ordinal-raw 2 "$(awk '$1 == "R6-raw-ordinal" { print $2 }' "$out")" check_eq libmikmod/anchor-ordinal-nos 3 "$(awk '$1 == "R6-nos-ordinal" { print $2 }' "$out")" # MikMod_GetVersion() packs the version as (major<<16)|(minor<<8)|revision, so # 197387 is 3.3.11 arithmetically and not merely by lookup. v=$(awk '$1 == "R6-version" { print $2 }' "$out") if [ -n "$v" ] && [ "$v" -eq $((3 * 65536 + 3 * 256 + 11)) ]; then bst_ok libmikmod/anchor-version-encoding else bst_notok libmikmod/anchor-version-encoding "MikMod_GetVersion()=$v is not 3.3.11" fi # --- the pass that is NOT compared, made visible ----------------------------- # R7 (DMODE_FLOAT) is excluded from the digest diff above because the mix is # scaled in float under -ffast-math -- see the header. Excluded is not # unobserved: the length and the non-silence are asserted with the others above, # and the note line carries the digest itself into the console log, where a # reader can compare it against data/libmikmod-native.txt by eye. The note is # outside the BST protocol, so the report parsers ignore it and it counts as no # case. for f in mod m15 s3m; do nz=$(_r R7-pcm "$f" float44 | awk '{print $3}') if [ -n "$nz" ] && [ "$nz" -gt 0 ]; then bst_ok "libmikmod/r7-nonsilent-$f" else bst_notok "libmikmod/r7-nonsilent-$f" "float44 decoded $nz non-zero bytes"; fi printf '==BST-NOTE libmikmod r7 %s float44 %s==\n' "$f" "$(_r R7-pcm "$f" float44)" done rm -f "$out"