. "$BST_LIB" # BST-TIMEOUT: 300 # PENDING measurement on target; replaced with the observed # # ==BST-SUITE elapsed= figure and a note on the exec count # # before this lands. # libmad 0.15.1b (meta-oe recipes-multimedia/libmad) -- the MAD MPEG audio # decoder: Layer I, Layer II and Layer III in fixed-point C, shipped here as # /usr/lib/libmad.a, /usr/include/mad.h and mad.pc. It is a LIBRARY port: the # recipe installs no program, so this suite is driven entirely by two consumers # the bbappend builds against this image's own archive and packages as # libmad-tests: # # /usr/libexec/libmad-tests/minimad upstream's own example, unmodified # /usr/libexec/libmad-tests/libmad-probe ours, for the API minimad cannot see # # THE ORACLE IS A STORED x86-64 REFERENCE DECODE, AND THAT IS NOT THE # CONCESSION IT IS NEXT DOOR. 57-libvorbis.test and 57-speexdsp.test both refuse # a cross-machine digest and 57-mpg123.test can only use one after neutralising # -ffast-math; every one of them is right about its own subject, because every # one of them decodes in floating point. libmad does not. The argument here is # structural rather than measured-and-hoped: # # * mad_fixed_t is `signed int`. fixed.h picks it on `# if SIZEOF_INT >= 4`, # and the mad.h this port installs BAKES SIZEOF_INT in, so the decoder # computes in 32 bits on a machine whose long is 8 bytes and on one whose # long is 4. Had upstream written `signed long` -- the other arm of that # same #if -- the two machines would differ by construction and no reference # could be shipped at all. # * mad_f_mul under FPM_DEFAULT with OPT_SPEED is `((x) >> 12) * ((y) >> 16)`: # int times int, no widening, no rounding mode, no libm. # * there is NO floating point on the decode path. `double` occurs twice in # the built sources and both are inert -- mad_f_todouble inside a # `# if defined(DEBUG)` fprintf, and a macro pair nothing calls. The # `struct fixedfloat` requantisation table in layer3.c is two integer # bitfields with a misleading name. # * the synthesis window and every requantisation table are MAD_F() integer # literals in .dat includes, and configure selects the hand-written # ASO_*/IMDCT paths only for i?86, arm and mips, so this arm gets none. # # So the two machines MUST agree byte for byte, `cmp` is the right gate, and a # mismatch is an arithmetic defect rather than last-bit drift to be re-referenced. # That is the opposite instruction from 57-openjpeg's irreversible-wavelet pin: # do not re-pin these references to whatever the arm printed. # # THE REFERENCE IS ONLY THE RIGHT ANSWER FOR ONE BUILD CONFIGURATION, so the # suite asserts the configuration too, from the target binary. libmad reports it # at runtime through `mad_build`, and libmad/build-fingerprint requires exactly # "FPM_DEFAULT OPT_SPEED OPT_SSO". FPM_DEFAULT is the portable C multiply the # bbappend asks for by name (configure would otherwise reach it only by # fallthrough from `case $host`); OPT_SPEED comes from the stock recipe's # -enable-speed and drops the pre-rounding from that multiply; OPT_SSO is # implied by OPT_SPEED in global.h and selects the subband synthesis # optimisation. Change any of the three and every PCM case below goes red for a # reason that is not a port defect -- which is exactly why the fingerprint is a # case of its own and is listed first. # # WHAT THE SIX FIXTURES ARE, AND WHY THEY ARE NOT ALL MP3. # # libmad-l3-mono44k Layer III mono 44100 Hz 128 kbps # libmad-l3-stereo44k Layer III stereo 44100 Hz 192 kbps, `lame -m s` # libmad-l3-joint44k Layer III joint 44100 Hz 128 kbps, `lame -m j` # libmad-l3-lsf22k Layer III mono 22050 Hz 64 kbps (MPEG-2 LSF) # libmad-l1-mono44k Layer I mono 44100 Hz 128 kbps (synthesised) # libmad-l2-mono48k Layer II mono 48000 Hz 64 kbps (synthesised) # # libmad's whole subject is the three layers, and there is no MPEG audio encoder # on this image or on the build host that can produce Layer I or Layer II -- # lame does Layer III only, and Layer I encoders are extinct. An MP3-only suite # would have left the whole of layer12.c unmeasured, so src/libmad_mkfixtures.py # SYNTHESISES those two, bit field by bit field, from ISO/IEC 11172-3's frame # layout: a valid layer-1/2 stream does not require an analysis filterbank, # because the subband samples are just numbers the decoder's synthesis # filterbank will turn into PCM. The Layer II allocation tables are read out of # libmad's own layer12.c so the generator cannot disagree with the decoder about # which quantisation class an allocation index names, and both streams were # validated by requiring the native decode to report not one MAD_ERROR_*. The # result is not music. It does not need to be: it is deterministic and reachable # only through the real decode path, and it is quiet by construction (the # scalefactors are held in a band that keeps every sample off the 16-bit clip, # because a saturated sample agrees with the reference no matter what the # decoder computed). # # The four Layer III streams cover the paths that differ from each other rather # than four spellings of the same one: mono, true stereo, joint stereo (the # MS/intensity decode in layer3.c), and MPEG-2 LSF, whose granule is 576 samples # rather than 1152 and whose scalefactor bands are a separate table. # # minimad MMAPS ITS INPUT, AND THAT IS THE POINT OF libmad/mmap-usable. main() # does `mmap(0, size, PROT_READ, MAP_SHARED, STDIN_FILENO, 0)` and returns 3 # without decoding if it fails. brintos/linux#30 is file-backed MAP_SHARED # failing with ENODEV when PROT_WRITE is requested -- the PROT_WRITE bit, not the # filesystem -- so it should not reach this call, and this suite establishes that # from the target instead of from the reading. The probe reads the same fixtures # with plain stdio, so "every minimad case red and every probe case green" reads # as mmap and not as libmad. That legibility is why the mmap was left in place # rather than patched out for a read() loop. # # MAD_BUFFER_GUARD COSTS THE LAST FRAME OF EVERY FIXTURE, ON BOTH MACHINES. # minimad hands libmad the whole file in one buffer and frame.c refuses a frame # ending within MAD_BUFFER_GUARD (8) bytes of the buffer end, so the eight-frame # synthetic streams decode SEVEN frames and every fixture is one frame short of # its byte length. That is upstream's documented contract for a non-streaming # caller, it is identical on x86-64, and the frame counts and sizes below pin it # so a reader comparing durations does not file it as a port defect. # # READING A RED. # * libmad/build-fingerprint red -> every PCM red below is explained by it and # is not independent evidence. Fix the recipe, regenerate the references. # * libmad/mmap-usable red (minimad rc 3) -> brintos/linux#30 widened to # PROT_READ. Every minimad case follows it; the probe cases should not. # * a *-pcm-exact red with libmad/mutation-detected and the probe cases green # -> a real arithmetic difference in the decoder. Not drift; do not re-pin. # * libmad/mutation-detected or libmad/junk-lostsync red -> the # suite's own gate is not discriminating and NOTHING else here can be # trusted, whatever colour it is. # * a *-headers red with *-pcm-exact green -> mad_header_decode's fields, not # the samples: a packaging or type-width fault rather than arithmetic. # * libmad/parent-survives-virtiofs-mmap GREEN -> brintos/linux#92 is fixed. # That is the one case here whose green is a to-do: delete the tmpfs staging # in decode_one and decode straight from $BST_DATA again. Do not weaken what # that case runs to get it there -- see the note at the case itself. MADDIR=/usr/libexec/libmad-tests MINIMAD=$MADDIR/minimad PROBE=$MADDIR/libmad-probe if [ ! -x "$MINIMAD" ] || [ ! -x "$PROBE" ]; then bst_skip libmad/present "libmad-tests not installed on this image" exit 0 fi tmp=${TMPDIR:-/tmp}/bst-libmad.$$ mkdir -p "$tmp" 2>/dev/null size() { wc -c < "$1" 2>/dev/null | tr -d ' '; } md5of() { md5sum "$1" 2>/dev/null | cut -d' ' -f1; } have_md5=0; command -v md5sum >/dev/null 2>&1 && have_md5=1 # unsigned little-endian 16-bit words at a byte offset, whitespace-normalised so # od's column padding cannot decide a case. u16at() { od -An -tu2 -N "$3" -j "$2" "$1" 2>/dev/null | tr -s ' \n' ' ' \ | sed -e 's/^ *//' -e 's/ *$//'; } # ------------------------------------------------------------ what shipped --- check libmad/lib-static test -f /usr/lib/libmad.a check libmad/header test -f /usr/include/mad.h check libmad/pkgconfig test -f /usr/lib/pkgconfig/mad.pc check_out libmad/pkgconfig-version "Version: 0.15.1b" cat /usr/lib/pkgconfig/mad.pc # 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 libmad/no-shared "/usr/lib/libmad.so" check libmad/minimad-present test -x "$MINIMAD" check libmad/probe-present test -x "$PROBE" # The generated mad.h is what pins the arithmetic for every future consumer of # this port, not just for the two shipped here. check_out libmad/header-fpm "define FPM_DEFAULT" cat /usr/include/mad.h check_out libmad/header-optspeed "define OPT_SPEED" cat /usr/include/mad.h check_out libmad/header-sizeof "define SIZEOF_INT 4" cat /usr/include/mad.h # --------------------------------------------------- the build fingerprint --- self=$("$PROBE" self 2>&1); rc=$? if [ "$rc" -ne 0 ]; then if bst_execfail "$self"; then bst_unrun libmad/probe-self "$self" else bst_notok libmad/probe-self "rc=$rc $(_bst_1line "$self")"; fi else bst_ok libmad/probe-self fi check_eq libmad/version "MPEG Audio Decoder 0.15.1 (beta)" \ "$(printf '%s\n' "$self" | awk '/^V version /{sub(/^V version /,"");print;exit}')" check_eq libmad/build-fingerprint "FPM_DEFAULT OPT_SPEED OPT_SSO" \ "$(printf '%s\n' "$self" | awk '/^V release /{sub(/^V release /,"");sub(/ *$/,"");print;exit}')" # The three identities the stored PCM reference rests on, named individually so # a red says which one moved rather than only that the control differs. check_eq libmad/fixed-width "4" "$(printf '%s\n' "$self" | awk '/^F sizeof-fixed /{print $3;exit}')" check_eq libmad/fixed-one "10000000" "$(printf '%s\n' "$self" | awk '/^F one /{print $3;exit}')" check_eq libmad/fixed-mul "fff78001" "$(printf '%s\n' "$self" | awk '/^F max-times-max /{print $3;exit}')" check_eq libmad/timer-res "352800000" "$(printf '%s\n' "$self" | awk '/^T resolution /{print $3;exit}')" check_eq libmad/timer-string "00:00:03.500" "$(printf '%s\n' "$self" | awk '/^T threeandahalf-str /{print $3;exit}')" check_eq libmad/errstr "lost synchronization" \ "$(printf '%s\n' "$self" | awk '/^S 0101 /{sub(/^S 0101 /,"");print;exit}')" # ------------------------------------------------------- the fixture decode -- # name streamfile pcmbytes pcmmd5 pinoffset pinbytes pinvalue decode_one() { _n=$1; _f=$2; _bytes=$3; _md5=$4; _off=$5; _nb=$6; _pin=$7 _src=$BST_DATA/$_f if [ ! -f "$_src" ]; then bst_notok libmad/$_n-payload "missing $_src" return fi # BRINTOS-WORKAROUND: brintos/linux#92 remove -- minimad mmaps its stdin # MAP_SHARED, and over the virtiofs root that derails the shell that fork+ # wait4s it: this suite stopped dead after case 19 and reported 19/19 green. # The same bytes on tmpfs are clean, and the decode is identical either way -- # libmad never learns where the file came from. Drop this copy when # libmad/parent-survives-virtiofs-mmap goes green. _stg=$tmp/stg-$_f cp "$_src" "$_stg" 2>/dev/null || _stg=$_src _out=$tmp/$_n.pcm _err=$tmp/$_n.err ( "$MINIMAD" < "$_stg" > "$_out" 2> "$_err" ); _rc=$? # rc 3 is minimad's own "mmap failed" exit and is the one failure that is not # about libmad at all, so it gets its own case rather than a generic red. if [ "$_rc" -eq 3 ]; then bst_notok libmad/$_n-mmap-usable "minimad rc=3 -- mmap(PROT_READ, MAP_SHARED) of stdin failed (brintos/linux#30)" else bst_ok libmad/$_n-mmap-usable fi if [ "$_rc" -eq 0 ]; then bst_ok libmad/$_n-rc elif bst_execfail "$(cat "$_err" 2>/dev/null)"; then bst_unrun libmad/$_n-rc "$(cat "$_err")" else bst_notok libmad/$_n-rc "rc=$_rc $(_bst_1line "$(cat "$_err" 2>/dev/null)")"; fi # A clean decode prints nothing at all: every `decoding error 0x....` line is # a MAD_ERROR_* libmad recovered from, and these fixtures are known to produce # none on x86-64. if [ -s "$_err" ]; then bst_notok libmad/$_n-no-errors "$(_bst_1line "$(cat "$_err")")" else bst_ok libmad/$_n-no-errors fi check_eq libmad/$_n-pcm-size "$_bytes" "$(size "$_out")" if cmp -s "$_out" "$BST_DATA/$_n.ref.pcm" 2>/dev/null; then bst_ok libmad/$_n-pcm-exact else bst_notok libmad/$_n-pcm-exact "decode differs from the x86-64 reference ($(size "$_out") B vs $(size "$BST_DATA/$_n.ref.pcm") B)" fi if [ "$have_md5" -eq 1 ]; then check_eq libmad/$_n-pcm-md5 "$_md5" "$(md5of "$_out")" else bst_skip libmad/$_n-pcm-md5 "no md5sum on this image" fi # One interior sample, read straight out of the decode. On the two-channel # fixtures this is the pair (L, R) at one frame, so it fails a channel SWAP # and a channel DUPLICATION distinctly -- neither of which cmp can name even # though it catches both. check_eq libmad/$_n-sample "$_pin" "$(u16at "$_out" "$_off" "$_nb")" # The probe reads the same file with stdio and reports the header fields the # PCM cannot show. Its section of the shipped native control is compared as a # block; the fields worth naming are named above in the self section. _got=$tmp/$_n.hdr "$PROBE" headers "$_src" > "$_got" 2>&1 _want=$tmp/$_n.want awk -v s="== $_n" '$0==s{p=1;next} /^== /{p=0} p' "$BST_DATA/libmad-native.txt" > "$_want" if [ ! -s "$_want" ]; then bst_notok libmad/$_n-headers "no '== $_n' section in libmad-native.txt" elif cmp -s "$_got" "$_want"; then bst_ok libmad/$_n-headers else bst_notok libmad/$_n-headers "$(_bst_1line "$(diff "$_want" "$_got" 2>/dev/null || cat "$_got")")" fi rm -f "$_out" "$_err" "$_got" "$_want" "$_stg" } # --------------------------------------------- why the decodes are staged --- # brintos/linux#92: a child that mmaps a file on the VIRTIOFS root MAP_SHARED # derails the shell that fork+wait4s it -- the shell stops executing, silently, # with no red and no diagnostic. minimad does exactly that with its stdin, so # every decode below reads a tmpfs copy instead (see decode_one). # # THE DETECTOR IS minimad, AND THAT IS NOT A CONVENIENCE. The first cut of this # case used the probe's `mapshared` mode, on the reasoning that the trigger is # the mmap and a libmad-free reproducer is the better one. It went GREEN while # the defect was fully present, because `mapshared` open()s the path itself and # returns in a millisecond. Measured 2026-08-18: minimad over the virtiofs root # derails the parent at EVERY size from 1 kB to 20 kB, minimad over tmpfs is # clean at every size, and probe `mapshared` over virtiofs is clean at every # size -- so neither size nor duration is the variable, and mapping alone is not # sufficient. What is left between them is that minimad maps fd 0 as the shell's # redirect left it. Until that is settled upstream, the case that decides # whether the workaround can go is the construct the workaround exists for. # # It runs in a NESTED shell -- that shell is the casualty, and this suite only # reads whether it reached its echo. minimad_parent_survives() { sh -c "$MINIMAD < $1 > /dev/null 2>&1; echo ALIVE" 2>/dev/null; } cp "$BST_DATA/libmad-l3-mono44k.mp3" "$tmp/mmapctl.mp3" 2>/dev/null if [ "$(minimad_parent_survives "$tmp/mmapctl.mp3")" = "ALIVE" ]; then bst_ok libmad/parent-survives-tmpfs-mmap else bst_notok libmad/parent-survives-tmpfs-mmap "a tmpfs-backed decode derails the waiting shell too -- brintos/linux#92 has widened past the backing store, the staging in decode_one no longer avoids it, and every decode case below is unreliable" fi if [ "$(minimad_parent_survives "$BST_DATA/libmad-l3-mono44k.mp3")" = "ALIVE" ]; then bst_ok libmad/parent-survives-virtiofs-mmap else bst_skip libmad/parent-survives-virtiofs-mmap "brintos/linux#92: the waiting shell stops executing after a virtiofs MAP_SHARED decode; decodes are staged on tmpfs. When this goes green, drop the staging in decode_one" fi decode_one libmad-l3-mono44k libmad-l3-mono44k.mp3 25344 6af0b90f41b16b259fd7d6ab07821651 11364 2 "11411" decode_one libmad-l3-stereo44k libmad-l3-stereo44k.mp3 50688 7174947ec877b0c9d946e252bfafbbc3 20000 4 "3711 61058" decode_one libmad-l3-joint44k libmad-l3-joint44k.mp3 50688 42a98c920959a1d98ffd75abd939dda8 20000 4 "3636 61152" decode_one libmad-l3-lsf22k libmad-l3-lsf22k.mp3 13824 540625ede541a173a761dd806e646ce3 4690 2 "11128" decode_one libmad-l1-mono44k libmad-l1-mono44k.mp1 5376 91a122e600d323ef30801ad7aa9d2a2d 2784 2 "884" decode_one libmad-l2-mono48k libmad-l2-mono48k.mp2 16128 1fee35b5961c4572f704e7a11870d1f3 10304 2 "719" # ----------------------------------------------- the probe's own self block --- # Everything the probe prints for `self`, compared as a block against the same # x86-64 control -- the fixed-point identities, the whole mad_timer_t arithmetic # and the entire error-string table, which are far more lines than it is worth # naming one case each. Diagnostic lines (tag D) are dropped on both sides: the # width of `long` differs between the two machines by construction and is # reported rather than compared. selfgot=$tmp/self.got selfwant=$tmp/self.want printf '%s\n' "$self" | grep -v '^D ' > "$selfgot" awk '$0=="== self"{p=1;next} /^== /{p=0} p' "$BST_DATA/libmad-native.txt" > "$selfwant" if [ ! -s "$selfwant" ]; then bst_notok libmad/native-control "no '== self' section in libmad-native.txt" elif cmp -s "$selfgot" "$selfwant"; then bst_ok libmad/native-control else bst_notok libmad/native-control "$(_bst_1line "$(diff "$selfwant" "$selfgot" 2>/dev/null || cat "$selfgot")")" fi # ------------------------------------------------------- negative controls --- # A guard adopted because it passes is not evidence. These four establish that # the byte comparisons above would actually go red if the decoder were wrong, # and that minimad's non-zero exits mean what the cases above assume. # Corrupt eight bytes of the first fixture's Layer III main data. The decode # must still run -- libmad recovers -- and must NOT match the reference. # # OFFSET 1000 IS MEASURED, NOT ARBITRARY, AND FINDING THAT OUT IS THE POINT. # The first four offsets tried for this control (25, 30, 100, 2000) all left the # decode BYTE-IDENTICAL, so the control passed by asserting nothing and would # have shipped that way. Not every byte of a CBR MP3 is load-bearing: lame # writes a Xing/Info tag as frame 0, and `--nores` leaves slack at the end of # each frame's main_data that no granule points into. Only a byte a granule # actually reads changes a sample. If this fixture is ever regenerated, this # offset has to be re-measured with it -- a green libmad/mutation-detected on a # stale offset means only that the file changed, not that the decoder noticed. mut=$tmp/mutated.mp3 cp "$BST_DATA/libmad-l3-mono44k.mp3" "$mut" 2>/dev/null if [ -f "$mut" ] && command -v dd >/dev/null 2>&1; then dd if=/dev/zero bs=1 count=8 2>/dev/null | tr '\0' 'U' \ | dd of="$mut" bs=1 seek=1000 count=8 conv=notrunc 2>/dev/null if cmp -s "$mut" "$BST_DATA/libmad-l3-mono44k.mp3" 2>/dev/null; then bst_notok libmad/mutation-detected "the mutated copy is identical to the fixture -- dd did not write" else "$MINIMAD" < "$mut" > "$tmp/mut.pcm" 2> "$tmp/mut.err" if cmp -s "$tmp/mut.pcm" "$BST_DATA/libmad-l3-mono44k.ref.pcm" 2>/dev/null; then bst_notok libmad/mutation-detected "a corrupted stream decoded to the reference -- the byte comparisons above prove nothing" else bst_ok libmad/mutation-detected fi fi else bst_notok libmad/mutation-detected "cannot stage the mutated copy" fi # Non-MPEG input: libmad must lose sync, decode nothing, and minimad must still # exit 0 -- the error callback returns MAD_FLOW_CONTINUE rather than BREAK. junk=$tmp/junk.bin dd if=/dev/zero bs=1024 count=4 2>/dev/null | tr '\0' 'Z' > "$junk" "$MINIMAD" < "$junk" > "$tmp/junk.pcm" 2> "$tmp/junk.err"; jrc=$? check_eq libmad/junk-rc "0" "$jrc" check_eq libmad/junk-empty "0" "$(size "$tmp/junk.pcm")" # ...and the error channel every *-no-errors case above reads must be LIVE. A # suite whose "no errors" cases are green because nothing can ever print is # measuring nothing, and this is the cheapest way to establish otherwise: the # junk stream must produce libmad's own lost-synchronization diagnostic. check_out libmad/junk-lostsync "lost synchronization" cat "$tmp/junk.err" # Empty stdin is minimad's rc 2 (fstat says zero), and an argument is rc 1. : > "$tmp/empty" "$MINIMAD" < "$tmp/empty" > /dev/null 2>&1; check_eq libmad/empty-rc "2" "$?" ( "$MINIMAD" x < "$BST_DATA/libmad-l3-mono44k.mp3" > /dev/null 2>&1 ); check_eq libmad/arg-rc "1" "$?" rm -rf "$tmp"