. "$BST_LIB" # BST-TIMEOUT: 300 # ~30 execs of a 2-3.5 MB binary plus ~30 coreutils execs, and # # each exec on this arm is a fresh wasm module instantiation. # libwebp 1.3.2 (poky recipes-multimedia/webp) -- the WebP codec. `cwebp` encodes # a PNM to WebP, `dwebp` decodes WebP back to PPM/PAM/PGM/BMP, `webpinfo` walks the # RIFF chunk structure, `webpmux` edits container metadata, and `img2webp` builds an # animation from stills. Everything here is file-in/file-out: no display, no # network, no device, no thread. # # THIS SUITE'S PRIMARY GATE HAS NO TOLERANCE IN IT, WHICH IS RARE FOR A CODEC AND # IS THE WHOLE REASON THE PORT WAS SPECIFIED THIS WAY. WebP's lossless mode makes # the round-trip an IDENTITY: `cwebp -lossless` followed by `dwebp -ppm` returns the # input file byte-for-byte, header included, because imageio/image_enc.c writes # exactly the "P6\n \n255\n" that data/libwebp-fixture.ppm carries (13 bytes). # So the correctness question is settled by `cmp` rather than by a bounded # deviation, and a single flipped bit anywhere in the encoder, the container, or # the decoder fails it. 57-libpng.test gets that from upstream's own ptest suite; # this is the first port on the image to get it from the tool's own semantics. # # THE ENCODER IS ALSO COMPILER-INDEPENDENT, SO THE STORED REFERENCES ARE GROUND # TRUTH AND NOT MERELY A PIN. That question is asked of every codec on this arm and # the answers have differed: 57-mpg123.test could not gate on `cmp` at all, because # mpg123 compiles with -ffast-math and gcc and clang then disagree on identical # source; 57-libjpeg-turbo.test could, but only because its DEFAULT path is the # slow integer DCT. libwebp comes out strongest of the three. VP8 and VP8L are # integer throughout -- integer transforms, integer prediction, integer entropy # coding -- and the recipe passes no fast-math anywhere. Measured 2026-08-09: gcc # 16.1.1 and clang 22.1.8 builds of this exact tarball, configured with the target's # effective options, produce BYTE-IDENTICAL output for every artifact this suite # creates, INCLUDING the lossy q80 file. data/libwebp-ref-lossless.webp and # data/libwebp-ref-lossy-q80.webp are therefore compiler-independent oracles, and # the encode is gated on `cmp` against them rather than on a size or a digest. # # THE FIXTURE IS SYNTHETIC AND EXACTLY REGENERABLE, and its four regions each aim # at a different VP8L coding path -- a lossless codec that only ever sees a smooth # gradient is barely exercised. data/libwebp-fixture.ppm is 64x64 P6, integer # arithmetic only, per pixel (x,y): # x < 32 : r = x*255/31, g = y*255/63, b = 255 - (x+y)*255/94 # -> spatial predictors + the cross-colour transform # x >= 32, y < 32 : flat (200,120,40) # -> long LZ77 backward matches # x >= 32, y >= 32 : ((x/4 + y/4) mod 4) of black/red/green/blue, 4x4 cells # -> the palette transform and the colour cache # rows 8..11, x >= 40 : overwritten flat (0,0,0) # -> one hard edge, and the reason the LOSSY max below is # large while every lossless case stays exact # `cwebp -print_psnr` confirms the first two transforms actually engage rather than # the encoder falling back to a trivial mode -- see the features case. # # WHY THERE IS A LOSSY HALF AT ALL, AND WHAT IT IS FOR. The byte-exact cases say # the output has not DRIFTED from a reference two compilers agree on. On the # lossless path they also say it is CORRECT, because there the reference and the # input are the same bytes. The lossy path has no such identity, so a codec that # consistently produced the same wrong image would pass its `cmp` case and tell us # nothing -- and that is exactly the case the deviation gate answers, by comparing # the q80 decode against the ORIGINAL fixture. Measured 2026-08-09 on the reference # build over all 12288 payload bytes: max|delta| = 172, mean|delta| = 9.208, with # 9408 of 12288 bytes differing at all. The max is large BY DESIGN of the fixture: # a four-colour checker at 4-pixel granularity plus a hard bar is near-worst-case # high-frequency content for a DCT codec at q80, and ringing there is correct # behaviour, not a fault. So the lossy path is gated on the MEAN (bound 20.000, # ~2.2x the measured 9.208) with the max bounded only coarsely at 220 as an "it is # still the image" guard. The sensitivity of this suite lives in the lossless # cases, which carry no tolerance whatsoever. # # READING A RED. A `cmp`/digest case red with every deviation and round-trip case # still green is toolchain or version drift (a new wasmcc, or a libwebp bump) and is # NOT a codec defect: confirm against the round-trip cases and re-pin. A # roundtrip-exact or deviation case red is a genuine fault and must never be # re-pinned. Both payloads are digest-pinned before anything reads them, so neither # red can be blamed on a payload that changed underneath. # # THE ==WEBP-OBSERVED== LINE. Outside the BST protocol, like ==BST-SUITE==, so the # report parsers ignore it. It carries the digests and deviations this run actually # produced, which is how the pins above get RE-DERIVED FROM A MEASUREMENT rather # than re-guessed after a version bump -- one run gives both the failure and the # value to replace it with. # # THREADS ARE ABSENT ON PURPOSE and the suite asserts it rather than trusting the # recipe: the port is built --disable-threading for linux-in-the-browser#85, so # `cwebp -mt` must still succeed (the flag is accepted and ignored) and must produce # the SAME bytes as without it. That is the one case that would notice the bbappend # being dropped before #85 is actually fixed. require cwebp libwebp/present tmp=${TMPDIR:-/tmp}/bst-libwebp.$$ mkdir -p "$tmp" 2>/dev/null fix=$BST_DATA/libwebp-fixture.ppm refll=$BST_DATA/libwebp-ref-lossless.webp reflo=$BST_DATA/libwebp-ref-lossy-q80.webp md5of() { md5sum "$1" 2>/dev/null | cut -d' ' -f1; } size() { wc -c < "$1" 2>/dev/null | tr -d ' '; } have_md5=0; command -v md5sum >/dev/null 2>&1 && have_md5=1 have_dev=0; command -v od >/dev/null 2>&1 && command -v awk >/dev/null 2>&1 && have_dev=1 # PPM header written by both the fixture generator and dwebp: "P6\n64 64\n255\n". PPMHDR=13 # maxdev, ndiff, mean*1000, nbytes for two same-header PPM/PGM files. deviation() { od -An -v -tu1 -w16 -j"$3" "$1" > "$tmp/dev-a.txt" 2>/dev/null od -An -v -tu1 -w16 -j"$3" "$2" > "$tmp/dev-b.txt" 2>/dev/null awk 'NR==FNR{for(i=1;i<=NF;i++)a[++k]=$i; next} {for(i=1;i<=NF;i++){j++; d=a[j]-$i; if(d<0)d=-d; s+=d; if(d>m)m=d; if(d)n++}} END{if(j>0) printf "%d %d %d %d\n", m+0, n+0, (s*1000)/j, j; else print "- - - 0"}' \ "$tmp/dev-a.txt" "$tmp/dev-b.txt" } # ----------------------------------------------------------------- payloads --- # Pinned BEFORE anything reads them. Without this a later mismatch has two # indistinguishable causes -- the codec changed, or the payload did -- and the # wrong one gets reported. if [ "$have_md5" -eq 1 ]; then check_eq libwebp/fixture-digest 3a49d6f24fd05730c97ad01912231444 "$(md5of "$fix")" check_eq libwebp/ref-lossless-digest d25358f9b2a90cc7a755e0d46fddcff4 "$(md5of "$refll")" check_eq libwebp/ref-lossy-digest 18dc6024fd8cb97ea8746fad4a6f6f50 "$(md5of "$reflo")" else bst_skip libwebp/fixture-digest "no md5sum on this image" bst_skip libwebp/ref-lossless-digest "no md5sum on this image" bst_skip libwebp/ref-lossy-digest "no md5sum on this image" fi check_eq libwebp/fixture-size 12301 "$(size "$fix")" check_eq libwebp/ref-lossless-size 262 "$(size "$refll")" check_eq libwebp/ref-lossy-size 528 "$(size "$reflo")" # ------------------------------------------------------------------ present --- for t in cwebp dwebp webpinfo webpmux img2webp; do if command -v "$t" >/dev/null 2>&1; then bst_ok libwebp/tool-$t else bst_notok libwebp/tool-$t "$t is not on this image"; fi done check_out libwebp/version "1.3.2" cwebp -version check_out libwebp/version-sharpyuv "libsharpyuv: 0.2.1" cwebp -version check_out libwebp/dwebp-version "1.3.2" dwebp -version # ------------------------------------------------- lossless: the identity gate --- # The port's reason for existing, in three cases: the encode matches an oracle two # host compilers agree on, the oracle decodes back to the fixture exactly, and the # image's OWN encode decodes back to the fixture exactly. The third is the # end-to-end one and is the case that must never be re-pinned. enc=$tmp/ll.webp check libwebp/encode-lossless cwebp -quiet -lossless "$fix" -o "$enc" check_eq libwebp/encode-lossless-size 262 "$(size "$enc")" check libwebp/encode-lossless-exact cmp -s "$enc" "$refll" check libwebp/riff-magic cmp -s -n 4 "$enc" "$refll" dec=$tmp/ref.ppm check libwebp/decode-reference dwebp -quiet -ppm "$refll" -o "$dec" check_eq libwebp/decode-reference-size 12301 "$(size "$dec")" check libwebp/decode-reference-exact cmp -s "$dec" "$fix" rt=$tmp/rt.ppm check libwebp/roundtrip-decode dwebp -quiet -ppm "$enc" -o "$rt" check libwebp/roundtrip-exact cmp -s "$rt" "$fix" # The PPM header dwebp chose, read off the file it wrote -- P6 is RGB. check_eq libwebp/decode-header "P6 64 64 255" \ "$(head -c 13 "$dec" | tr '\n' ' ' | sed 's/ *$//')" # A codec that is deterministic is what makes a stored reference an oracle at all, # so it is asserted rather than assumed. cwebp -quiet -lossless "$fix" -o "$tmp/ll2.webp" 2>/dev/null check libwebp/encode-determinism cmp -s "$enc" "$tmp/ll2.webp" # The transforms actually engaged, from the encoder's own trace rather than from # the file size -- a fallback to a trivial mode would still round-trip exactly. check_out libwebp/lossless-features "Lossless features used: PREDICTION CROSS-COLOR-TRANSFORM" \ cwebp -lossless -print_psnr "$fix" -o /dev/null # 99.00 is libwebp's cap and is what it reports for a mathematically identical # image, so this is the encoder agreeing that it lost nothing. check_out libwebp/lossless-psnr "Total:99.00" \ cwebp -lossless -print_psnr "$fix" -o /dev/null # ------------------------------------------- lossless at the slowest setting --- # -z 9 picks a different compression-parameter search, so it is a genuinely # different encode; losslessness is a property of the mode, not of the effort, so # the decode must STILL be the fixture exactly. z9=$tmp/z9.webp check libwebp/encode-z9 cwebp -quiet -lossless -z 9 "$fix" -o "$z9" check_eq libwebp/encode-z9-size 254 "$(size "$z9")" check libwebp/encode-z9-differs-from-default sh -c "! cmp -s '$z9' '$enc'" check libwebp/decode-z9 dwebp -quiet -ppm "$z9" -o "$tmp/z9.ppm" check libwebp/roundtrip-z9-exact cmp -s "$tmp/z9.ppm" "$fix" # ------------------------------------------------- threads are gone, on purpose --- # The bbappend builds this --disable-threading for linux-in-the-browser#85. -mt is # then accepted and ignored, so it must succeed AND produce identical bytes. If the # append is ever dropped before #85 is fixed, this is the case that notices. check libwebp/mt-accepted cwebp -quiet -mt -lossless "$fix" -o "$tmp/mt.webp" check libwebp/mt-identical cmp -s "$tmp/mt.webp" "$enc" # ---------------------------------------------------- the other decoder outputs --- # dwebp's PNM/BMP writers are unconditional in imageio -- no libpng, no libjpeg, # no libtiff -- which is what lets this port carry no DEPENDS at all. pam=$tmp/o.pam check libwebp/decode-pam dwebp -quiet -pam "$refll" -o "$pam" check_eq libwebp/decode-pam-size 16451 "$(size "$pam")" check_out libwebp/decode-pam-header "P7" head -c 2 "$pam" check_eq libwebp/decode-pam-depth "DEPTH 4" \ "$(head -c 40 "$pam" | tr '\n' ' ' | sed 's/.*\(DEPTH 4\).*/\1/')" check libwebp/decode-pgm dwebp -quiet -pgm "$refll" -o "$tmp/o.pgm" check_eq libwebp/decode-pgm-size 6157 "$(size "$tmp/o.pgm")" check libwebp/decode-bmp dwebp -quiet -bmp "$refll" -o "$tmp/o.bmp" check_eq libwebp/decode-bmp-size 12342 "$(size "$tmp/o.bmp")" check_out libwebp/decode-bmp-magic "BM" head -c 2 "$tmp/o.bmp" if [ "$have_md5" -eq 1 ]; then check_eq libwebp/decode-pam-digest 210ee18f485b35adf39bccf6e4908441 "$(md5of "$pam")" check_eq libwebp/decode-pgm-digest 0cea2d2a277eb1d3668ed43ee8710880 "$(md5of "$tmp/o.pgm")" check_eq libwebp/decode-bmp-digest e669cbc5e67416f57361fa834bf08065 "$(md5of "$tmp/o.bmp")" else bst_skip libwebp/decode-pam-digest "no md5sum on this image" bst_skip libwebp/decode-pgm-digest "no md5sum on this image" bst_skip libwebp/decode-bmp-digest "no md5sum on this image" fi # -tiff WORKS with PACKAGECONFIG empty, and the reason is worth pinning because it # is easy to assume the opposite: WebPWriteTIFF is UNCONDITIONAL in image_enc.c, # building a 14-entry uncompressed-TIFF header by hand, so it needs no libtiff at # all. PACKAGECONFIG[tiff] buys only the READER (imageio/tiffdec.c, for cwebp). # That asymmetry is asserted from both sides two blocks down. check libwebp/decode-tiff dwebp -quiet -tiff "$refll" -o "$tmp/o.tiff" check_eq libwebp/decode-tiff-size 12498 "$(size "$tmp/o.tiff")" check_eq libwebp/decode-tiff-magic "II*" "$(head -c 3 "$tmp/o.tiff")" if [ "$have_md5" -eq 1 ]; then check_eq libwebp/decode-tiff-digest 6625bb97302b62b6d126039bd5512225 "$(md5of "$tmp/o.tiff")" else bst_skip libwebp/decode-tiff-digest "no md5sum on this image" fi # ----------------------------------------------------------------- lossy path --- lo=$tmp/lo.webp; lodec=$tmp/lo.ppm check libwebp/encode-lossy cwebp -quiet -q 80 "$fix" -o "$lo" check_eq libwebp/encode-lossy-size 528 "$(size "$lo")" check libwebp/encode-lossy-exact cmp -s "$lo" "$reflo" check libwebp/decode-lossy dwebp -quiet -ppm "$reflo" -o "$lodec" check_eq libwebp/decode-lossy-size 12301 "$(size "$lodec")" # The lossy encode must NOT be byte-identical to the lossless one -- a build in # which -q silently produced lossless would pass every exactness case above. check libwebp/lossy-differs-from-lossless sh -c "! cmp -s '$lo' '$refll'" check libwebp/lossy-smaller-than-fixture sh -c "[ $(size "$lo") -lt $(size "$fix") ]" # ---------------------------------------- the question cmp cannot ask (lossy) --- if [ "$have_dev" -eq 1 ]; then set -- $(deviation "$fix" "$lodec" "$PPMHDR") lmax=${1:-}; ln=${2:-}; lmean=${3:-}; ltot=${4:-0} echo "==WEBP-OBSERVED lossymax=${lmax:-?} lossymean_milli=${lmean:-?} lossyndiff=${ln:-?} of ${ltot} enc=$(md5of "$enc") z9=$(md5of "$z9") lossy=$(md5of "$lo") rt=$(md5of "$rt")==" if [ -n "$lmean" ] && [ "$lmean" -le 20000 ]; then bst_ok libwebp/lossy-mean else bst_notok libwebp/lossy-mean "mean|delta| ${lmean:-unknown}/1000 > 20000/1000 (max ${lmax:-?})" fi if [ -n "$lmax" ] && [ "$lmax" -le 220 ]; then bst_ok libwebp/lossy-max else bst_notok libwebp/lossy-max "max|delta| ${lmax:-unknown} > 220" fi # The lossless decode must deviate by EXACTLY zero -- the same measurement path # as the lossy bound, so a broken deviation() cannot make the lossy gate look # good while this one silently passes too. set -- $(deviation "$fix" "$rt" "$PPMHDR") if [ "${1:-x}" = "0" ] && [ "${2:-x}" = "0" ]; then bst_ok libwebp/lossless-zero-deviation else bst_notok libwebp/lossless-zero-deviation "max=${1:-?} ndiff=${2:-?}, expected 0 0"; fi else bst_skip libwebp/lossy-mean "no od/awk on this image" bst_skip libwebp/lossy-max "no od/awk on this image" bst_skip libwebp/lossless-zero-deviation "no od/awk on this image" fi # ------------------------------------------------------------------ webpinfo --- check libwebp/info-lossless webpinfo "$refll" check_out libwebp/info-lossless-format "Format: Lossless (2)" webpinfo "$refll" check_out libwebp/info-lossless-chunk "Chunk VP8L" webpinfo "$refll" check_out libwebp/info-width "Width: 64" webpinfo "$refll" check_out libwebp/info-height "Height: 64" webpinfo "$refll" check_out libwebp/info-clean "No error detected." webpinfo "$refll" check_out libwebp/info-lossy-format "Format: Lossy (1)" webpinfo "$reflo" check_out libwebp/info-lossy-chunk "Chunk VP8 " webpinfo "$reflo" # ------------------------------------------------------------------- webpmux --- # The container half of the port: attach EXIF, read it back, and strip it. The # strip case is the strong one -- it must restore the ORIGINAL FILE byte-for-byte, # which says the muxer rewrote the container without touching the image payload. printf 'BRINTOS-594' > "$tmp/e.bin" mux=$tmp/mux.webp check libwebp/mux-set webpmux -set exif "$tmp/e.bin" "$refll" -o "$mux" check_eq libwebp/mux-set-size 300 "$(size "$mux")" check libwebp/mux-get webpmux -get exif "$mux" -o "$tmp/g.bin" check_eq libwebp/mux-get-exact "BRINTOS-594" "$(cat "$tmp/g.bin" 2>/dev/null)" check_out libwebp/mux-info "Features present: EXIF metadata" webpmux -info "$mux" check_out libwebp/mux-info-size "Size of the EXIF metadata: 11" webpmux -info "$mux" check libwebp/mux-strip webpmux -strip exif "$mux" -o "$tmp/st.webp" check libwebp/mux-strip-exact cmp -s "$tmp/st.webp" "$refll" # A chunk that is not there is an error, not an empty success. check_rc libwebp/mux-get-absent 1 webpmux -get exif "$refll" -o /dev/null # ------------------------------------------------------------------ img2webp --- # Two DISTINCT frames, because the animation encoder collapses identical ones into # a still image -- feeding it the same file twice produces a 262-byte VP8L with # Animation: 0, which would assert nothing about animation at all. anim=$tmp/anim.webp check libwebp/anim-build img2webp -o "$anim" -lossless "$fix" "$lodec" check_out libwebp/anim-animation "Animation: 1" webpinfo "$anim" check_out libwebp/anim-anim-chunk "Chunk ANIM" webpinfo "$anim" check_out libwebp/anim-frame "Chunk ANMF" webpinfo "$anim" check_out libwebp/anim-duration "Duration: 100" webpinfo "$anim" check_out libwebp/anim-canvas "Canvas size 64 x 64" webpinfo "$anim" check_out libwebp/anim-clean "No error detected." webpinfo "$anim" # Two frames really are in there: the still is 262 bytes, this must be far larger. check libwebp/anim-two-frames sh -c "[ $(size "$anim") -gt 1000 ]" # ------------------------------------------------------------ error handling --- # A codec's rejection path matters as much as its accept path: a decoder that # returned success on rubbish would make every case above meaningless. check_rc libwebp/dwebp-rejects-ppm 255 dwebp -quiet "$fix" -o /dev/null : > "$tmp/empty" check_rc libwebp/webpinfo-rejects-empty 6 webpinfo "$tmp/empty" head -c 100 "$refll" > "$tmp/trunc.webp" 2>/dev/null check_rc libwebp/dwebp-rejects-truncated 255 dwebp -quiet "$tmp/trunc.webp" -o /dev/null check_rc libwebp/webpinfo-rejects-truncated 1 webpinfo "$tmp/trunc.webp" check_rc libwebp/dwebp-rejects-missing 255 dwebp -quiet "$tmp/no-such-file.webp" -o /dev/null # The READER side of the PACKAGECONFIG decision, and the only place on the image # where that decision is visible at runtime. cwebp reads PNM and WebP and nothing # else here: a TIFF is refused by name, which is the message that would change if # someone enabled PACKAGECONFIG tiff, and a BMP is refused because libwebp has no # BMP reader in any configuration. Both must fail cleanly rather than produce a # garbage encode. check_rc libwebp/cwebp-rejects-tiff 255 cwebp -quiet "$tmp/o.tiff" -o /dev/null # check_out cannot express this one: the command must FAIL and carry the message, so # the needle is matched by hand on a non-zero exit. m=$(cwebp "$tmp/o.tiff" -o /dev/null 2>&1) case "$m" in *"TIFF support not compiled"*) bst_ok libwebp/cwebp-tiff-message ;; *) bst_notok libwebp/cwebp-tiff-message "$(_bst_1line "$m")" ;; esac check_rc libwebp/cwebp-rejects-bmp 255 cwebp -quiet "$tmp/o.bmp" -o /dev/null # ...and the input format it DOES have, from the other direction: the fixture is a # P6, and cwebp reading its own decoder's PAM output closes the PNM loop. check libwebp/cwebp-reads-pam cwebp -quiet -lossless "$pam" -o "$tmp/frompam.webp" check libwebp/cwebp-reads-webp cwebp -quiet -lossless "$refll" -o "$tmp/fromwebp.webp" # ------------------------------------------------------- shape on this image --- # The distro is static-only, so ${PN} (FILES = ${libdir}/lib*.so.*) is empty, no # libwebp package is emitted, and the archives are the whole library deliverable. # Asserting the absence keeps a future image that grows a .so from doing so # unnoticed. so=$(ls /usr/lib/libwebp.so* /usr/lib/libwebpmux.so* /usr/lib/libwebpdemux.so* \ /usr/lib/libsharpyuv.so* 2>/dev/null) if [ -z "$so" ]; then bst_ok libwebp/static-only else bst_notok libwebp/static-only "$(_bst_1line "$so")" fi for a in libwebp libwebpdemux libwebpmux libsharpyuv; do if [ -f "/usr/lib/$a.a" ]; then bst_ok libwebp/archive-$a else bst_notok libwebp/archive-$a "/usr/lib/$a.a is missing"; fi done for h in decode encode mux demux types mux_types; do if [ -f "/usr/include/webp/$h.h" ]; then bst_ok libwebp/header-$h else bst_notok libwebp/header-$h "/usr/include/webp/$h.h is missing"; fi done # vwebp needs GL and gif2webp needs giflib; neither is on this image, and both must # be absent rather than present-and-broken. for t in vwebp gif2webp; do if command -v "$t" >/dev/null 2>&1; then bst_notok libwebp/absent-$t "$t is on the image but its dependency stack is not" else bst_ok libwebp/absent-$t fi done rm -rf "$tmp"