. "$BST_LIB" # BST-TIMEOUT: 300 # ~22 execs of a 3-4 MB binary plus ~25 coreutils execs, and # # each exec on this arm is a fresh wasm module instantiation. # libjpeg-turbo 3.0.1 (poky recipes-graphics/jpeg) -- the JPEG codec. `cjpeg` # compresses a PPM, `djpeg` decompresses it again, `jpegtran` transforms the # coefficients without decoding, and `rdjpgcom`/`wrjpgcom` read and write the COM # marker. tjbench drives the same codec through the TurboJPEG API instead of the # libjpeg one. Everything here is file-in/file-out: no display, no network, no # device. # # THE ORACLE IS BYTE-EXACT, AND THAT IS A MEASURED CLAIM RATHER THAN A HOPEFUL ONE. # 57-mpg123.test could not gate on `cmp` against a host reference: mpg123 compiles # with -ffast-math, under which gcc and clang disagree on the identical source, so # any single-compiler reference would have asserted which compiler built it. The # equivalent question was asked here before this suite was written, and it comes # out the other way. cjpeg and djpeg both default to the SLOW INTEGER DCT # (JDCT_ISLOW), colour conversion runs off integer tables, and downsampling, # quantization and Huffman coding are integer throughout -- so the whole default # pipeline is integer and has no reassociation for a compiler to license. Measured # 2026-08-08: gcc 15 and clang 20 builds of this exact tarball produce # BYTE-IDENTICAL output for every artifact this suite creates -- the q90 4:2:0 # JPEG, the q100 4:4:4 JPEG, the grayscale JPEG, both jpegtran rotations, all three # decodes and the BMP. So data/libjpeg-turbo-ref-q90.jpg is a compiler-independent # ground truth, and `cmp` against it is the strongest gate available. # # THE FIXTURE IS SYNTHETIC AND EXACTLY REGENERABLE. data/libjpeg-turbo-fixture.ppm # is 64x64 P6, built from integer arithmetic alone so anyone can reproduce it: # per pixel (x,y), r = x*255/63, g = y*255/63, b = 255 - (x+y)*255/126; then a soft # radial blob centred at (20,44) with s = 255 - min(255, ((x-20)^2 + (y-44)^2)*3) # is blended toward (255,200,40) by (v*(255-s) + c*s)/255; then rows 8..15 from # x=40 rightward are overwritten flat (16,16,16). The three ramps give the DCT # smooth low-frequency content in all three channels, the blob gives a curved # chroma feature that 4:2:0 subsampling has to carry, and the flat bar gives ONE # hard edge -- which is what the max-deviation number below is about. # # WHY THERE ARE TWO KINDS OF GATE AND NEITHER SUBSUMES THE OTHER. The `cmp`/digest # cases say the output HAS NOT DRIFTED from a reference two compilers agree on. # They cannot say the output is a picture of the input: a codec that consistently # produced the same wrong image would pass every one of them. The round-trip # deviation cases answer that instead -- they compare the decode against the # ORIGINAL PPM and bound how far a lossy codec is allowed to have moved it. # # WHERE THE BOUNDS COME FROM. Measured 2026-08-08 on the reference build against # the fixture, over all 12288 payload bytes: # q90 4:2:0 max|delta| = 41, mean|delta| = 1.729 # q100 4:4:4 max|delta| = 4, mean|delta| = 0.366 # The q90 max of 41 is not slack in the codec: it is ringing plus chroma # subsampling at the single hard edge the fixture deliberately contains, and it is # why the q90 gate is on the MEAN (bounded at 4.000, ~2.3x the measured 1.729) # rather than the max. The max is bounded at 64 anyway, as a coarse "the image is # still the image" guard. The q100 4:4:4 path has no subsampling and near-unity # quantization, so its max is genuinely tight and is bounded at 8, 2x the measured # 4. A real fault -- a broken IDCT, a wrong upsampler, a colour-conversion sign # error -- moves numbers by hundreds, not by twos. # # READING A RED. A digest or `cmp` case red on its own, with every deviation case # still green, is toolchain drift (a new wasmcc reassociating something, or a # libjpeg-turbo version bump) and is NOT a defect: confirm against the deviation # cases and re-pin. A deviation case red is a genuine codec fault and must not be # re-pinned. Both payloads are digest-pinned before anything reads them, so neither # red can ever be blamed on a payload that changed underneath. # # THE ==JPEG-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. require cjpeg libjpeg-turbo/present tmp=${TMPDIR:-/tmp}/bst-libjpeg-turbo.$$ mkdir -p "$tmp" 2>/dev/null fix=$BST_DATA/libjpeg-turbo-fixture.ppm ref=$BST_DATA/libjpeg-turbo-ref-q90.jpg if [ ! -f "$fix" ] || [ ! -f "$ref" ]; then bst_notok libjpeg-turbo/payloads "missing $fix or $ref -- remaining cases not run" rm -rf "$tmp" exit 0 fi 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 generator and djpeg: "P6\n64 64\n255\n", 13 bytes. 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 libjpeg-turbo/fixture-digest fa16d4e21c95b4937f53c04a967b6e6d "$(md5of "$fix")" check_eq libjpeg-turbo/reference-digest f84ebe36487ca8fe733f7ecca04238e6 "$(md5of "$ref")" else bst_skip libjpeg-turbo/fixture-digest "no md5sum on this image" bst_skip libjpeg-turbo/reference-digest "no md5sum on this image" fi check_eq libjpeg-turbo/fixture-size 12301 "$(size "$fix")" check_out libjpeg-turbo/version "libjpeg-turbo version 3.0.1" \ cjpeg -verbose -outfile /dev/null "$fix" # ------------------------------------------------------------- cjpeg encode --- enc=$tmp/enc.jpg check libjpeg-turbo/encode cjpeg -quality 90 -optimize -outfile "$enc" "$fix" check_eq libjpeg-turbo/encode-size 888 "$(size "$enc")" # The byte-exact encode gate: identical to a reference two host compilers agree on. check libjpeg-turbo/encode-exact cmp -s "$enc" "$ref" check libjpeg-turbo/soi-marker cmp -s -n 2 "$enc" "$ref" # ------------------------------------------------------------- djpeg decode --- dec=$tmp/dec.ppm check libjpeg-turbo/decode djpeg -outfile "$dec" "$ref" check_eq libjpeg-turbo/decode-size 12301 "$(size "$dec")" # The colour space djpeg chose, read off the file it wrote: P6 is RGB, P5 is gray. check_eq libjpeg-turbo/decode-header "P6 64 64 255" "$(head -c 13 "$dec" | tr '\n' ' ' | sed 's/ *$//')" if [ "$have_md5" -eq 1 ]; then check_eq libjpeg-turbo/decode-digest 8854e71827725f5203dcefa9d6cb4d66 "$(md5of "$dec")" else bst_skip libjpeg-turbo/decode-digest "no md5sum on this image" fi # A codec that is deterministic is what makes a stored reference an oracle at all, # so it is asserted rather than assumed. cjpeg -quality 90 -optimize -outfile "$tmp/enc2.jpg" "$fix" 2>/dev/null check libjpeg-turbo/encode-determinism cmp -s "$enc" "$tmp/enc2.jpg" # ------------------------------------------- what the decoder reports it saw --- # The ticket's "reported dimensions and colour space", taken from the decoder's own # trace rather than from the file it wrote. `2hx2v` on component 1 with `1hx1v` on # the chroma pair IS the 4:2:0 report; components=3 is the colour space. v=$(djpeg -verbose -outfile /dev/null "$ref" 2>&1) check_out libjpeg-turbo/report-dimensions "width=64, height=64, components=3" printf '%s\n' "$v" case "$v" in *"Component 1: 2hx2v q=0"*) bst_ok libjpeg-turbo/report-subsampling ;; *) bst_notok libjpeg-turbo/report-subsampling "$(_bst_1line "$v")" ;; esac case "$v" in *"Component 2: 1hx1v q=1"*) bst_ok libjpeg-turbo/report-chroma-tables ;; *) bst_notok libjpeg-turbo/report-chroma-tables "$(_bst_1line "$v")" ;; esac check_out libjpeg-turbo/report-encoder-input "64x64 PPM image" \ cjpeg -verbose -quality 90 -optimize -outfile /dev/null "$fix" # ------------------------------------------------ 4:4:4 at maximum quality ---- hi=$tmp/hi.jpg; hidec=$tmp/hidec.ppm check libjpeg-turbo/encode-444 cjpeg -quality 100 -optimize -sample 1x1 -outfile "$hi" "$fix" check_eq libjpeg-turbo/encode-444-size 3095 "$(size "$hi")" check libjpeg-turbo/decode-444 djpeg -outfile "$hidec" "$hi" h=$(djpeg -verbose -outfile /dev/null "$hi" 2>&1) case "$h" in *"Component 1: 1hx1v q=0"*) bst_ok libjpeg-turbo/report-no-subsampling ;; *) bst_notok libjpeg-turbo/report-no-subsampling "$(_bst_1line "$h")" ;; esac if [ "$have_md5" -eq 1 ]; then check_eq libjpeg-turbo/encode-444-digest d0d77bd8bc037e69490780e3a7e6aa73 "$(md5of "$hi")" check_eq libjpeg-turbo/decode-444-digest 0356eb5355ac9ff31a2dc61e890fbb84 "$(md5of "$hidec")" else bst_skip libjpeg-turbo/encode-444-digest "no md5sum on this image" bst_skip libjpeg-turbo/decode-444-digest "no md5sum on this image" fi # ------------------------------------------------------- grayscale, and P5 ---- gr=$tmp/gray.jpg; grdec=$tmp/gray.pgm check libjpeg-turbo/encode-gray cjpeg -grayscale -quality 90 -optimize -outfile "$gr" "$fix" check_eq libjpeg-turbo/encode-gray-size 510 "$(size "$gr")" check libjpeg-turbo/decode-gray djpeg -outfile "$grdec" "$gr" check_eq libjpeg-turbo/decode-gray-size 4109 "$(size "$grdec")" # One component in, one plane out: the colour-space change is visible in both the # decoder's trace and the magic number of the file it wrote. check_eq libjpeg-turbo/decode-gray-header "P5 64 64 255" "$(head -c 13 "$grdec" | tr '\n' ' ' | sed 's/ *$//')" g=$(djpeg -verbose -outfile /dev/null "$gr" 2>&1) check_out libjpeg-turbo/report-gray-components "width=64, height=64, components=1" printf '%s\n' "$g" # ----------------------------------------------------- the round-trip bounds --- # The gate that says the decode is a picture of the input, not merely of the # reference. See WHERE THE BOUNDS COME FROM above. if [ "$have_dev" -eq 1 ]; then check libjpeg-turbo/roundtrip-same-geometry cmp -s -n "$PPMHDR" "$fix" "$dec" set -- $(deviation "$fix" "$dec" "$PPMHDR") qmax=${1:-}; qn=${2:-}; qmean=${3:-}; qtot=${4:-0} set -- $(deviation "$fix" "$hidec" "$PPMHDR") hmax=${1:-}; hn=${2:-}; hmean=${3:-}; htot=${4:-0} echo "==JPEG-OBSERVED q90max=${qmax:-?} q90mean_milli=${qmean:-?} q90ndiff=${qn:-?} of ${qtot} hi444max=${hmax:-?} hi444mean_milli=${hmean:-?} enc=$(md5of "$enc") dec=$(md5of "$dec")==" check_eq libjpeg-turbo/roundtrip-byte-count 12288 "$qtot" if [ -n "$qmean" ] && [ "$qmean" -le 4000 ]; then bst_ok libjpeg-turbo/roundtrip-mean else bst_notok libjpeg-turbo/roundtrip-mean "mean|delta| ${qmean:-unknown}/1000 > 4000/1000 (max ${qmax:-?})" fi if [ -n "$qmax" ] && [ "$qmax" -le 64 ]; then bst_ok libjpeg-turbo/roundtrip-max else bst_notok libjpeg-turbo/roundtrip-max "max|delta| ${qmax:-unknown} > 64" fi if [ -n "$hmax" ] && [ "$hmax" -le 8 ]; then bst_ok libjpeg-turbo/roundtrip-444-max else bst_notok libjpeg-turbo/roundtrip-444-max "max|delta| ${hmax:-unknown} > 8 at q100 4:4:4" fi # 4:4:4 at q100 must be strictly closer to the original than 4:2:0 at q90 -- the # one relation between the two paths that no single pinned number can express. if [ -n "$hmean" ] && [ -n "$qmean" ] && [ "$hmean" -lt "$qmean" ]; then bst_ok libjpeg-turbo/quality-ordering else bst_notok libjpeg-turbo/quality-ordering "q100 4:4:4 mean ${hmean:-?} not below q90 4:2:0 mean ${qmean:-?}" fi else for c in roundtrip-same-geometry roundtrip-byte-count roundtrip-mean roundtrip-max \ roundtrip-444-max quality-ordering; do bst_skip libjpeg-turbo/$c "no od/awk on this image" done fi # ------------------------------------------------------------------ jpegtran --- # The coefficient-domain path: jpegtran transforms without ever running the IDCT, # so a rotation composed with its inverse must return the ORIGINAL BYTES. -optimize # on each leg is what makes that exact -- without it jpegtran writes the standard # Huffman tables rather than the reference's optimized ones, and the file differs # in the DHT segments while the coefficients agree. 64x64 is a whole number of # 16x16 MCUs, so no edge blocks are dropped or invented. if command -v jpegtran >/dev/null 2>&1; then check libjpeg-turbo/jpegtran-rot90 jpegtran -optimize -rotate 90 -outfile "$tmp/r90.jpg" "$ref" check_eq libjpeg-turbo/jpegtran-rot90-size 900 "$(size "$tmp/r90.jpg")" check libjpeg-turbo/jpegtran-rot270 jpegtran -optimize -rotate 270 -outfile "$tmp/r360.jpg" "$tmp/r90.jpg" check libjpeg-turbo/jpegtran-rot-identity cmp -s "$tmp/r360.jpg" "$ref" jpegtran -optimize -flip horizontal -outfile "$tmp/fh.jpg" "$ref" 2>/dev/null jpegtran -optimize -flip horizontal -outfile "$tmp/fh2.jpg" "$tmp/fh.jpg" 2>/dev/null check libjpeg-turbo/jpegtran-flip-identity cmp -s "$tmp/fh2.jpg" "$ref" # A rotation is not a no-op: asserting the identity alone would also pass if # jpegtran silently copied its input. if cmp -s "$tmp/r90.jpg" "$ref"; then bst_notok libjpeg-turbo/jpegtran-rot90-changes-file "rotated output is byte-identical to its input" else bst_ok libjpeg-turbo/jpegtran-rot90-changes-file fi else for c in jpegtran-rot90 jpegtran-rot90-size jpegtran-rot270 jpegtran-rot-identity \ jpegtran-flip-identity jpegtran-rot90-changes-file; do bst_skip libjpeg-turbo/$c "jpegtran not on this image" done fi # ----------------------------------------------------------- the COM marker ---- if command -v wrjpgcom >/dev/null 2>&1 && command -v rdjpgcom >/dev/null 2>&1; then wrjpgcom -comment 'brintOS libjpeg-turbo port' "$ref" > "$tmp/com.jpg" 2>/dev/null check_eq libjpeg-turbo/wrjpgcom-size 918 "$(size "$tmp/com.jpg")" check_out libjpeg-turbo/rdjpgcom "brintOS libjpeg-turbo port" rdjpgcom "$tmp/com.jpg" # The comment is metadata, so the pixels must be untouched by it. djpeg -outfile "$tmp/comdec.ppm" "$tmp/com.jpg" 2>/dev/null check libjpeg-turbo/comment-preserves-image cmp -s "$tmp/comdec.ppm" "$dec" # An uncommented file has nothing to report, and saying so is a distinct outcome # from reporting a comment that is not there. out=$(rdjpgcom "$ref" 2>&1) if [ -z "$out" ]; then bst_ok libjpeg-turbo/rdjpgcom-empty else bst_notok libjpeg-turbo/rdjpgcom-empty "$(_bst_1line "$out")"; fi else for c in wrjpgcom-size rdjpgcom comment-preserves-image rdjpgcom-empty; do bst_skip libjpeg-turbo/$c "rdjpgcom/wrjpgcom not on this image" done fi # ------------------------------------------------- the other output formats ---- # BMP, GIF and Targa are compiled into djpeg by the recipe's default flags, so they # cost nothing to have and are worth asserting rather than assuming. check libjpeg-turbo/decode-bmp djpeg -bmp -outfile "$tmp/out.bmp" "$ref" check_eq libjpeg-turbo/decode-bmp-size 12342 "$(size "$tmp/out.bmp")" check_out libjpeg-turbo/decode-bmp-magic "BM" head -c 2 "$tmp/out.bmp" if [ "$have_md5" -eq 1 ]; then check_eq libjpeg-turbo/decode-bmp-digest 5098722765b164076c432763b82c5cf3 "$(md5of "$tmp/out.bmp")" else bst_skip libjpeg-turbo/decode-bmp-digest "no md5sum on this image" fi # --------------------------------------------------------- the TurboJPEG API --- # tjbench is the image's ONLY consumer of libturbojpeg.a -- everything above goes # through the libjpeg API instead -- so without it half the port ships unexercised. # It is run as a benchmark of one iteration (-warmup 0 -benchtime 0.01 -nowrite), # not for its throughput number, which is meaningless here and is not asserted. if command -v tjbench >/dev/null 2>&1; then cp "$ref" "$tmp/tjin.jpg" 2>/dev/null t=$(cd "$tmp" && tjbench tjin.jpg -warmup 0 -benchtime 0.01 -nowrite 2>&1) case "$t" in *"Image size: 64 x 64"*) bst_ok libjpeg-turbo/tjbench-dimensions ;; *) bst_notok libjpeg-turbo/tjbench-dimensions "$(_bst_1line "$t")" ;; esac # The TurboJPEG API's own reading of the colour space, independent of djpeg's. case "$t" in *"8-bit JPEG (YCbCr 4:2:0)"*) bst_ok libjpeg-turbo/tjbench-colorspace ;; *) bst_notok libjpeg-turbo/tjbench-colorspace "$(_bst_1line "$t")" ;; esac case "$t" in *Decompress*) bst_ok libjpeg-turbo/tjbench-decompress ;; *) bst_notok libjpeg-turbo/tjbench-decompress "$(_bst_1line "$t")" ;; esac else for c in tjbench-dimensions tjbench-colorspace tjbench-decompress; do bst_skip libjpeg-turbo/$c "tjbench not on this image" done fi # ---------------------------------------------------- refusals worth pinning --- # A codec that accepts garbage is a security question, not a cosmetic one, so the # refusals are pinned as tightly as the successes. printf 'not a jpeg at all\n' > "$tmp/junk.jpg" check_rc libjpeg-turbo/djpeg-rejects-junk 1 djpeg -outfile /dev/null "$tmp/junk.jpg" check_rc libjpeg-turbo/cjpeg-rejects-junk 1 cjpeg -outfile /dev/null "$tmp/junk.jpg" # TRUNCATION HAS TWO DISTINCT OUTCOMES AND THE DISTINCTION IS THE POINT. Cut after # the headers but inside the entropy data and libjpeg RECOVERS: it emits the # "Premature end of JPEG file" warning, decodes what it has, and exits 2 # (EXIT_WARNING -- djpeg returns it whenever num_warnings is non-zero). Cut before # the SOS and there is nothing to recover, so it is a fatal error and exit 1. A # suite that asserted one code for both would pass against a build that had lost # the recovery path entirely. head -c 400 "$ref" > "$tmp/trunc.jpg" 2>/dev/null check_rc libjpeg-turbo/djpeg-truncated-warns 2 djpeg -outfile /dev/null "$tmp/trunc.jpg" check_out libjpeg-turbo/djpeg-truncated-message "Premature end of JPEG file" \ sh -c "djpeg -outfile /dev/null '$tmp/trunc.jpg' 2>&1; exit 0" head -c 100 "$ref" > "$tmp/headless.jpg" 2>/dev/null check_rc libjpeg-turbo/djpeg-rejects-headless 1 djpeg -outfile /dev/null "$tmp/headless.jpg" # ------------------------------------------------------- shape on this image --- # The distro is static-only and this recipe's ENABLE_SHARED is off, so no runtime # library is installed and the archives are the whole of the library deliverable. # Asserting the absence keeps a future image that grows a .so from doing so # unnoticed. so=$(ls /usr/lib/libjpeg.so* /usr/lib/libturbojpeg.so* 2>/dev/null) if [ -z "$so" ]; then bst_ok libjpeg-turbo/static-only else bst_notok libjpeg-turbo/static-only "$(_bst_1line "$so")" fi for a in /usr/lib/libjpeg.a /usr/lib/libturbojpeg.a; do n=$(echo "$a" | sed 's#.*/lib##; s#\.a$##') if [ -f "$a" ]; then bst_ok libjpeg-turbo/archive-$n else bst_notok libjpeg-turbo/archive-$n "$a is missing"; fi done if [ -f /usr/include/jpeglib.h ] && [ -f /usr/include/turbojpeg.h ]; then bst_ok libjpeg-turbo/headers else bst_notok libjpeg-turbo/headers "jpeglib.h or turbojpeg.h missing from /usr/include" fi rm -rf "$tmp"