# BST-TIMEOUT: 600 # ~45 execs of epeg/djpeg over a 320x240 image, plus two # # od+awk passes over 14400-byte planes. Each exec on this arm # # is a fresh wasm module instantiation. . "$BST_LIB" # epeg 0.9.1.042+git (meta-oe recipes-support/epeg) -- Rasterman's fast JPEG # thumbnailer. One file in, one smaller file out. Its whole point is that it never # decodes the resolution it is about to throw away: it asks libjpeg for a SCALED # DCT decode at the smallest scale that still covers the requested thumbnail, then # scales the rest of the way itself. Plain C over autotools; DEPENDS are jpeg and # libexif, both already in this image. # # WHAT THE SHIPPED DELIVERABLE IS. /usr/bin/epeg, /usr/lib/libepeg.a, Epeg.h and # epeg.pc. There is no libepeg.so on a --disable-shared distro, and the absence is # asserted rather than assumed. # # ------------------------------------------------------------------------------ # THE THREE GATES, AND WHY NONE OF THEM SUBSUMES THE OTHERS # # 1. GEOMETRY. epeg_decode_size_set() clamps the requested size into [1, input] # and then uses it verbatim -- so the output is EXACTLY the size asked for, not # the nearest size some scaler found convenient. Every geometry case reads the # dimensions off the PPM djpeg writes (its header text AND its byte count), # never off epeg's own -v chatter, because -v prints what epeg INTENDED and the # question is what it produced. # # 2. CONTENT. Geometry alone would pass for a correctly-sized rectangle of noise, # and "smaller than the input" would pass for a rectangle of grey. So the # 80x60 thumbnail is compared against LIBJPEG'S OWN 1/4 SCALED DECODE of the # same source -- two independent paths to the same picture at the same size, # both produced on this target by this image's own binaries. No host reference # is involved, so nothing here can drift with a compiler or a libjpeg release. # # WHERE THE BOUNDS COME FROM, AND WHAT THEY EXCLUDE. Measured 2026-08-18 on an # x86_64 control of this exact tarball against this exact fixture, over all # 14400 payload bytes: # # epeg -w 80 -h 60 vs libjpeg 1/4 decode max 39 mean 2.010 # # and the same comparison against deliberately wrong images -- the NEGATIVE # CONTROLS that decide whether a bound means anything: # # thumbnail flipped vertically max 253 mean 65.973 # thumbnail mirrored horizontally max 255 mean 66.604 # thumbnail rotated 180 degrees max 255 mean 113.674 # flat grey rectangle max 128 mean 58.697 # # The mean gate is set at 6.000 -- about 3x the measured 2.010, and roughly a # tenth of the NEAREST failure mode. A flip, a mirror, a rotation or a solid # fill each miss it by an order of magnitude, which is what makes it a # statement about the picture rather than a number that happens to hold. The # max of 39 is not slack in the scaler: it is requantization at the single hard # edge the fixture deliberately contains, which is why the gate rests on the # mean and keeps the max only as a coarse guard at 64. # # 3. EXIF. epeg reads the input's Orientation tag and writes it into the output's # own EXIF block, preserving the input's TIFF byte order. IT DOES NOT ROTATE # PIXELS, and there is no command-line option that asks it to -- the CLI has no # orientation flag at all. That is pinned as its own case (epeg/exif-no-pixel- # rotation) rather than left implicit, because "orientation is handled" reads # like a promise about the image and is in fact a promise about six bytes of # metadata. A consumer that trusts the pixels and ignores the tag will display # such a thumbnail sideways, exactly as it would the original. # # ------------------------------------------------------------------------------ # THE EXIF FIXTURES ARE BUILT HERE, NOT SHIPPED, AND THAT IS DELIBERATE. An APP1 # EXIF segment carrying nothing but Orientation is 36 fixed bytes, so the suite # splices them in after the SOI itself. The orientation value is then known BY # CONSTRUCTION rather than by trusting an opaque payload, and both byte orders can # be covered without shipping a second and third image. The segment is: # # ff e1 00 22 APP1, length 34 (the length counts itself) # 45 78 69 66 00 00 "Exif\0\0" # 4d 4d 00 2a 00 00 00 08 TIFF header: big-endian, IFD0 at offset 8 # 00 01 one entry # 01 12 00 03 00 00 00 01 tag 0x0112 Orientation, type 3 SHORT, count 1 # 00 0N 00 00 the value, in the first two bytes of the value field # 00 00 00 00 no next IFD # # ------------------------------------------------------------------------------ # THE ==EPEG-OBSERVED== LINE. Outside the BST protocol, like ==BST-SUITE==, so the # report parsers ignore it. It carries the deviations and output sizes this run # actually produced, which is how the bounds above get RE-DERIVED FROM A # MEASUREMENT rather than re-guessed after a version bump. # # NOT ASSERTED, AND SAID SO RATHER THAN IMPLIED: that epeg is FAST. Its speed # claim rests on decoding only the DCT coefficients it needs, and nothing in the # output records which path produced it; timing on this arm is not a measurement # anyone should gate on. The deviation gate establishes that epeg's result agrees # with libjpeg's own scaled decode, which is consistent with the scaled path but # does not prove it was taken. require epeg epeg/present tmp=${TMPDIR:-/tmp}/bst-epeg.$$ mkdir -p "$tmp" 2>/dev/null fix=$BST_DATA/epeg-fixture.jpg if [ ! -f "$fix" ]; then bst_notok epeg/payload "missing $fix -- 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 ' '; } hdr() { head -c "$2" "$1" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//'; } have_md5=0; command -v md5sum >/dev/null 2>&1 && have_md5=1 have_djpeg=0; command -v djpeg >/dev/null 2>&1 && have_djpeg=1 have_dev=0; command -v od >/dev/null 2>&1 && command -v awk >/dev/null 2>&1 && have_dev=1 # ------------------------------------------------------------------ payload --- # Pinned BEFORE anything reads it. Without this a later mismatch has two # indistinguishable causes -- epeg changed, or the fixture did -- and the wrong # one gets reported. if [ "$have_md5" -eq 1 ]; then check_eq epeg/fixture-digest cea8e932703af9c347a79e585c04d828 "$(md5of "$fix")" else bst_skip epeg/fixture-digest "no md5sum on this image" fi check_eq epeg/fixture-size 6264 "$(size "$fix")" # ------------------------------------------------------------------- shape ---- # The distro is static-only, so the archive is the whole of the library # deliverable. Asserting the absence of a .so keeps a future image that grows one # from doing so unnoticed. check_rc epeg/archive 0 test -f /usr/lib/libepeg.a check_rc epeg/header 0 test -f /usr/include/Epeg.h check_rc epeg/pkgconfig 0 test -f /usr/lib/pkgconfig/epeg.pc bst_lib_shape epeg/static-only "/usr/lib/libepeg.so*" if [ "$have_djpeg" -eq 0 ]; then bst_notok epeg/djpeg "no djpeg on this image -- every geometry and content case needs it" rm -rf "$tmp" exit 0 fi # The fixture's own geometry, read the same way every thumbnail's is, so the # "smaller than its input" and clamp cases below rest on a measured number. check epeg/fixture-decodes djpeg -outfile "$tmp/src.ppm" "$fix" check_eq epeg/fixture-dims "P6 320 240 255" "$(hdr "$tmp/src.ppm" 15)" check_eq epeg/fixture-decode-size 230415 "$(size "$tmp/src.ppm")" insize=$(size "$fix") # --------------------------------------------------------------- geometry ----- # THE TICKET'S CENTRAL CLAIM: a valid JPEG of EXACTLY the requested dimensions, # smaller than its input. Each size gets four geometry cases, and the two that # look redundant are not: the PPM header states the geometry djpeg READ, the byte # count states how many pixels it actually WROTE, and a decoder that reported one # size while emitting another would pass either one alone. # # geom() REPORTS THROUGH A GLOBAL AND REDIRECTS NOTHING, WHICH IS NOT A STYLE # CHOICE. bst_ok and friends write the result protocol to stdout, so a helper # whose size is read back with $(...) has its own case lines captured into the # variable instead of emitted, and a caller that discards the size with # >/dev/null discards the case lines with it. Both were written here first and # both cost cases silently -- the suite still reported a full green over the # cases that survived. GEOM_SIZE= geom() { # $1 id $2 W $3 H $4 header-bytes $5 ppm-size $6.. epeg args id=$1; gw=$2; gh=$3; gn=$4; gs=$5; shift 5 out=$tmp/$id.jpg; dec=$tmp/$id.ppm rm -f "$out" "$dec" if epeg "$@" "$fix" "$out" >/dev/null 2>&1; then bst_ok epeg/$id-rc else bst_notok epeg/$id-rc "epeg exited non-zero for: $*"; fi check epeg/$id-decodes djpeg -outfile "$dec" "$out" check_eq epeg/$id-dims "P6 $gw $gh 255" "$(hdr "$dec" "$gn")" check_eq epeg/$id-pixels "$gs" "$(size "$dec")" GEOM_SIZE=$(size "$out") } # One case per size, and only where a thumbnail was actually asked for -- see the # clamp-up note below for the one geometry where "smaller" says nothing. smaller() { s=$(size "$tmp/$1.jpg") if [ -n "$s" ] && [ "$s" -lt "$insize" ]; then bst_ok epeg/$1-smaller else bst_notok epeg/$1-smaller "thumbnail ${s:-unknown} bytes is not smaller than the ${insize}-byte input"; fi } geom thumb-64x48 64 48 13 9229 -w 64 -h 48; s64=$GEOM_SIZE geom thumb-100x100 100 100 15 30015 -w 100 -h 100; s100=$GEOM_SIZE geom thumb-32x24 32 24 13 2317 -w 32 -h 24; s32=$GEOM_SIZE geom thumb-160x120 160 120 15 57615 -w 160 -h 120; s160=$GEOM_SIZE # 100x100 from a 4:3 source is not a mistake: without -p, epeg gives exactly what # was asked for and does NOT preserve aspect. The case above pins that; the four # below pin the forms that DO preserve it, each computed a different way. geom preserve-w 64 48 13 9229 -w 64 -p geom preserve-h 80 60 13 14413 -h 60 -p geom max-dim 80 60 13 14413 -m 80 geom percent 160 120 15 57615 -w 50% -h 50% # -i covers AT LEAST the requested box instead of fitting inside it, so the SHORT # side takes the bound and the long side overshoots: 133x100, not 100x75. Both # spellings of the request must agree, which no single case can say. geom inset-max 133 100 15 39915 -m 100 -i geom inset-wh 133 100 15 39915 -w 100 -h 100 -i # --------------------------------------------------------------- clamping ----- # epeg_decode_size_set() clamps into [1, input dimension], so it never upscales # and never asks libjpeg for a zero-sized plane. The upper clamp matters most: a # thumbnailer that honoured -w 400 on a 320-wide source would be inventing pixels. # # clamp-up IS THE ONE GEOMETRY WITH NO "smaller" CASE, DELIBERATELY. Nothing is # scaled there, so the output is smaller than the input only because epeg # re-encodes at its default quality 85 against a fixture written at 90. That is a # quality artifact, not the deliverable, and asserting it would pin this suite to # the fixture's encoder settings for no gain. geom clamp-up 320 240 15 230415 -w 400 -h 300 geom clamp-min 1 1 11 14 -w 1 -h 1 for c in thumb-64x48 thumb-100x100 thumb-32x24 thumb-160x120 \ preserve-w preserve-h max-dim percent inset-max inset-wh clamp-min; do smaller "$c" done # ------------------------------------------------------- byte-exact, cross-arm - # THE STRONGEST CLAIM THIS PORT CAN MAKE, AND IT IS PINNED RATHER THAN INFERRED: # the pixels this arm produces are byte-identical to x86-64's. The digests are of # the DECODED thumbnail, not of the JPEG, and that is the whole reason the claim # is portable -- epeg writes the source's path and mtime into APP7 Thumb:: markers, # so the JPEG's own bytes depend on where the fixture happens to live. Measured # 2026-08-18: the same four thumbnails came out 97 bytes shorter on target than on # the control, on every one of them, and 97 is exactly the difference between the # two fixture paths (141 characters against 44). The picture is identical; only the # note about where it came from is shorter. # # A red here with the deviation cases still green is drift in the JPEG pipeline # (a libjpeg-turbo bump, a toolchain change) and wants re-pinning from this run's # own numbers; a red here WITH the deviation cases is a fault. if [ "$have_md5" -eq 1 ]; then check_eq epeg/thumb-64x48-digest 029c30f2f1af7131b9bba7fc8f30c821 "$(md5of "$tmp/thumb-64x48.ppm")" check_eq epeg/thumb-32x24-digest ed1c900527cf1ba2960981ff3a6323ea "$(md5of "$tmp/thumb-32x24.ppm")" else bst_skip epeg/thumb-64x48-digest "no md5sum on this image" bst_skip epeg/thumb-32x24-digest "no md5sum on this image" fi # ---------------------------------------------------------------- content ----- # See "2. CONTENT" in the header. Both sides of this comparison are produced on # this target by this image's own binaries, so it holds no host reference. if [ "$have_dev" -eq 1 ]; then ref=$tmp/ref80.ppm th=$tmp/th80.ppm epeg -w 80 -h 60 "$fix" "$tmp/t80.jpg" >/dev/null 2>&1 djpeg -scale 1/4 -outfile "$ref" "$fix" 2>/dev/null djpeg -outfile "$th" "$tmp/t80.jpg" 2>/dev/null check_eq epeg/scaled-ref-dims "P6 80 60 255" "$(hdr "$ref" 13)" check_eq epeg/scaled-ref-size 14413 "$(size "$ref")" # BOTH SIDES OF THE COMPARISON ARE PINNED, NOT JUST THE ONE UNDER TEST. Without # the reference digest a red in epeg/content-mean has two indistinguishable # causes -- epeg drifted, or libjpeg's own scaled decode did -- and the wrong one # gets reported. With it, exactly one of the two goes red first and says which. if [ "$have_md5" -eq 1 ]; then check_eq epeg/scaled-ref-digest e6d8ce6ca9d5782027a2c0c7ef8f66f1 "$(md5of "$ref")" check_eq epeg/content-thumb-digest 67cb460782f1cbfb18b97c5a03c016f5 "$(md5of "$th")" else bst_skip epeg/scaled-ref-digest "no md5sum on this image" bst_skip epeg/content-thumb-digest "no md5sum on this image" fi # Same header bytes on both sides: the deviation below compares plane to plane, # and would be meaningless if the two planes were different shapes. check epeg/content-same-geometry cmp -s -n 13 "$ref" "$th" od -An -v -tu1 -w16 -j13 "$ref" > "$tmp/a.txt" 2>/dev/null od -An -v -tu1 -w16 -j13 "$th" > "$tmp/b.txt" 2>/dev/null set -- $(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/a.txt" "$tmp/b.txt") cmax=${1:-}; cn=${2:-}; cmean=${3:-}; ctot=${4:-0} echo "==EPEG-OBSERVED max=${cmax:-?} mean_milli=${cmean:-?} ndiff=${cn:-?} of ${ctot} sizes 64x48=${s64:-?} 100x100=${s100:-?} 32x24=${s32:-?} 160x120=${s160:-?} in=${insize}==" check_eq epeg/content-byte-count 14400 "$ctot" if [ -n "$cmean" ] && [ "$cmean" -le 6000 ]; then bst_ok epeg/content-mean else bst_notok epeg/content-mean "mean|delta| ${cmean:-unknown}/1000 > 6000/1000 (max ${cmax:-?}); a flip measures 65973/1000" fi if [ -n "$cmax" ] && [ "$cmax" -le 64 ]; then bst_ok epeg/content-max else bst_notok epeg/content-max "max|delta| ${cmax:-unknown} > 64" fi else for c in scaled-ref-dims scaled-ref-size content-same-geometry content-byte-count \ content-mean content-max; do bst_skip epeg/$c "no od/awk on this image" done fi # ------------------------------------------------------------------- EXIF ----- # See "3. EXIF" and the segment layout in the header. exif_in() { # $1 = "MM"|"II", $2 = orientation 1..8, $3 = output file { head -c 2 "$fix" if [ "$1" = MM ]; then printf '\377\341\000\042Exif\000\000MM\000\052\000\000\000\010\000\001\001\022\000\003\000\000\000\001\000' printf "\\$(printf '%03o' "$2")" printf '\000\000\000\000\000\000' else printf '\377\341\000\042Exif\000\000II\052\000\010\000\000\000\001\000\022\001\003\000\001\000\000\000' printf "\\$(printf '%03o' "$2")" printf '\000\000\000\000\000\000\000' fi tail -c +3 "$fix" } > "$3" } # The flat lowercase hex of the output's first 160 bytes, which is where epeg's # APP1 lives (SOI, then libjpeg's JFIF APP0, then the EXIF epeg writes itself). # Searched rather than indexed, so a structural change shows up as a missing # signature instead of as a silently wrong byte. ehex() { od -An -tx1 -v -N 160 "$1" 2>/dev/null | tr -d ' \n'; } exif_in MM 6 "$tmp/in-mm6.jpg" exif_in II 6 "$tmp/in-ii6.jpg" exif_in MM 8 "$tmp/in-mm8.jpg" # The splice is 36 bytes and nothing else, so the input size is a fact rather than # an observation -- if this is wrong every EXIF case below is testing a file that # is not what it claims to be. check_eq epeg/exif-input-size $((insize + 36)) "$(size "$tmp/in-mm6.jpg")" for f in mm6 ii6 mm8; do epeg -w 64 -h 48 "$tmp/in-$f.jpg" "$tmp/out-$f.jpg" >/dev/null 2>&1 done epeg -w 64 -h 48 "$fix" "$tmp/out-none.jpg" >/dev/null 2>&1 # Every output carries an EXIF block, with or without an orientation to put in it # -- epeg builds one unconditionally. Asserted first, so that the ABSENCE case # below cannot pass merely because the dump was empty. for f in mm6 ii6 mm8 none; do case "$(ehex "$tmp/out-$f.jpg")" in *457869660000*) bst_ok epeg/exif-present-$f ;; *) bst_notok epeg/exif-present-$f "no Exif\\0\\0 in the first 160 bytes of the $f output" ;; esac done # Orientation propagates, value for value. case "$(ehex "$tmp/out-mm6.jpg")" in *01120003000000010006*) bst_ok epeg/exif-orientation-6 ;; *) bst_notok epeg/exif-orientation-6 "orientation 6 did not reach the thumbnail's IFD0" ;; esac case "$(ehex "$tmp/out-mm8.jpg")" in *01120003000000010008*) bst_ok epeg/exif-orientation-8 ;; *) bst_notok epeg/exif-orientation-8 "orientation 8 did not reach the thumbnail's IFD0" ;; esac # And so does the input's TIFF byte order, which is a separate claim: epeg carries # the reader's byte order into the writer through a file-static, so an II source # must produce an II thumbnail and an MM source an MM one. case "$(ehex "$tmp/out-mm6.jpg")" in *4578696600004d4d002a*) bst_ok epeg/exif-byte-order-mm ;; *) bst_notok epeg/exif-byte-order-mm "big-endian source did not produce a big-endian EXIF block" ;; esac case "$(ehex "$tmp/out-ii6.jpg")" in *45786966000049492a00*) bst_ok epeg/exif-byte-order-ii ;; *) bst_notok epeg/exif-byte-order-ii "little-endian source did not produce a little-endian EXIF block" ;; esac case "$(ehex "$tmp/out-ii6.jpg")" in *12010300010000000600*) bst_ok epeg/exif-orientation-ii ;; *) bst_notok epeg/exif-orientation-ii "orientation 6 did not reach the little-endian thumbnail's IFD0" ;; esac # A source with NO EXIF is handled without error, and the thumbnail it produces # carries no orientation tag -- rather than inheriting one from somewhere, or # defaulting to 1, either of which would be a claim about the image that nothing # established. if epeg -w 64 -h 48 "$fix" "$tmp/none2.jpg" >/dev/null 2>&1; then bst_ok epeg/exif-none-rc else bst_notok epeg/exif-none-rc "epeg failed on a source with no EXIF"; fi case "$(ehex "$tmp/out-none.jpg")" in *011200030000000100*|*1201030001000000*) bst_notok epeg/exif-none-absent \ "an orientation tag appeared in a thumbnail whose source had no EXIF" ;; *) bst_ok epeg/exif-none-absent ;; esac # THE ONE THAT MATTERS MOST, AND THE ONE A READER OF THE TICKET WOULD NOT EXPECT: # the tag is metadata and nothing else. The two thumbnails below differ in their # EXIF and are identical in every pixel, so a consumer that ignores the tag sees # the same image either way. if [ "$have_djpeg" -eq 1 ]; then djpeg -outfile "$tmp/px-mm6.ppm" "$tmp/out-mm6.jpg" 2>/dev/null djpeg -outfile "$tmp/px-none.ppm" "$tmp/out-none.jpg" 2>/dev/null check epeg/exif-no-pixel-rotation cmp -s "$tmp/px-mm6.ppm" "$tmp/px-none.ppm" # Not vacuous: a comparison of two empty files would also pass. check_eq epeg/exif-no-pixel-rotation-size 9229 "$(size "$tmp/px-mm6.ppm")" fi # ------------------------------------------------- comment and Thumb:: info --- # -c writes a JPEG COM marker, which this image's own rdjpgcom reads back, and # epeg records the SOURCE's identity and geometry in APP7 Thumb:: markers that it # reads back itself. The second is a round trip through epeg's own reader, so it # exercises code no other case reaches. epeg -w 64 -h 48 -c 'BRINTOS-EPEG-COMMENT' "$fix" "$tmp/com.jpg" >/dev/null 2>&1 if command -v rdjpgcom >/dev/null 2>&1; then check_out epeg/comment-readback "BRINTOS-EPEG-COMMENT" rdjpgcom "$tmp/com.jpg" # An uncommented thumbnail has nothing to report, which is a distinct outcome # from reporting a comment that is not there. out=$(rdjpgcom "$tmp/out-none.jpg" 2>&1) if [ -z "$out" ]; then bst_ok epeg/comment-absent else bst_notok epeg/comment-absent "$(_bst_1line "$out")"; fi else bst_skip epeg/comment-readback "rdjpgcom not on this image" bst_skip epeg/comment-absent "rdjpgcom not on this image" fi v=$(epeg -v -w 32 -h 24 "$tmp/com.jpg" "$tmp/t2.jpg" 2>&1) check_out epeg/thumbinfo-width "Thumb Width: 320" printf '%s\n' "$v" check_out epeg/thumbinfo-height "Thumb Height: 240" printf '%s\n' "$v" check_out epeg/thumbinfo-mime "Thumb Mimetype: image/jpeg" printf '%s\n' "$v" check_out epeg/thumbinfo-comment "Comment: BRINTOS-EPEG-COMMENT" printf '%s\n' "$v" # epeg's own reading of the file it is about to shrink, which is the 64x48 it # wrote a moment ago -- so this is a round trip, not a restatement of the source. check_out epeg/verbose-image-size "Image size: 64x48" printf '%s\n' "$v" check_out epeg/verbose-thumb-size "Thumb size: 32x24" printf '%s\n' "$v" # ----------------------------------------------------------------- quality ---- # -q is the one option whose effect is not visible in the geometry, so it is # asserted as an ORDERING rather than as a pinned size: this arm's byte counts are # libjpeg-turbo's to decide, but more quality must cost more bytes at the same # dimensions or the option did nothing. epeg -w 64 -h 48 -q 30 "$fix" "$tmp/q30.jpg" >/dev/null 2>&1 epeg -w 64 -h 48 -q 95 "$fix" "$tmp/q95.jpg" >/dev/null 2>&1 q30=$(size "$tmp/q30.jpg"); q95=$(size "$tmp/q95.jpg") if [ -n "$q30" ] && [ -n "$q95" ] && [ "$q95" -gt "$q30" ]; then bst_ok epeg/quality-ordering else bst_notok epeg/quality-ordering "-q 95 produced ${q95:-?} bytes, not more than -q 30's ${q30:-?}" fi check_eq epeg/quality-geometry-unchanged "P6 64 48 255" \ "$(djpeg -outfile "$tmp/q95.ppm" "$tmp/q95.jpg" 2>/dev/null; hdr "$tmp/q95.ppm" 13)" # ---------------------------------------------------- refusals worth pinning --- # A thumbnailer that accepts garbage is a security question, not a cosmetic one. printf 'not a jpeg at all\n' > "$tmp/junk.jpg" check_rc epeg/reject-junk 255 epeg -w 64 -h 48 "$tmp/junk.jpg" "$tmp/j.jpg" check_rc epeg/reject-missing 255 epeg -w 64 -h 48 "$tmp/nosuch.jpg" "$tmp/n.jpg" check_out epeg/reject-message "cannot open" \ sh -c "epeg -w 64 -h 48 '$tmp/nosuch.jpg' '$tmp/n.jpg' 2>&1; exit 0" # UPSTREAM EXITS 0 FROM ITS USAGE PATH, INCLUDING FOR AN UNPARSEABLE COMMAND LINE. # usage() ends in exit(0), so `epeg` with no arguments, with one argument, or with # an unknown option all succeed as far as a caller can tell. That is wrong in # upstream's own terms -- a script that tests $? cannot distinguish "thumbnailed" # from "printed help and did nothing" -- and it is pinned AS IT IS rather than # corrected, because an unpinned wrong is indistinguishable from a port defect the # first time someone meets it. Should upstream ever fix it, this case goes red and # someone decides deliberately. check_rc epeg/usage-noargs-rc 0 epeg check_out epeg/usage-noargs-text "Usage:" sh -c "epeg 2>&1; exit 0" check_rc epeg/usage-onearg-rc 0 epeg "$fix" check_out epeg/usage-badopt-text "invalid option" \ sh -c "epeg -Z '$fix' '$tmp/z.jpg' 2>&1; exit 0" # Out-of-range arguments are clamped with a message on stderr rather than refused, # and the clamp is what the next case measures -- -w 0 must yield a 64-wide # thumbnail, not a zero-wide one or a failure. check_out epeg/quality-clamp-message "setting thumb_quality to default of 85" \ sh -c "epeg -q 200 -w 64 -h 48 '$fix' '$tmp/qc.jpg' 2>&1; exit 0" check_out epeg/width-zero-message "default minimum of 64" \ sh -c "epeg -w 0 -h 48 '$fix' '$tmp/w0.jpg' 2>&1; exit 0" check_eq epeg/width-zero-geometry "P6 64 48 255" \ "$(djpeg -outfile "$tmp/w0.ppm" "$tmp/w0.jpg" 2>/dev/null; hdr "$tmp/w0.ppm" 13)" rm -rf "$tmp"