# BST-TIMEOUT: 300 # ~120 cases, 13 tiffcp runs and an 8.7 MB probe # # BRINTOS-595 -- libtiff 4.6.0 on wasm32-hwjs. # # THE GATE IS ROUND-TRIP IDENTITY THROUGH EACH LOSSLESS CODEC, and it is built # that way on purpose. Comparing a codec's COMPRESSED bytes against a stored # reference would gate zip on the target's zlib version and lzma on its xz # version -- neither of which this port owns, and either of which would go red on # an unrelated upgrade. So each codec is driven fixture -> C -> none and the # result is compared with the TARGET'S OWN canonical uncompressed form, which is # version- and toolchain-independent and tests exactly the thing that matters: # that what the encoder wrote, the decoder reads back unchanged. # # THE CROSS-TOOLCHAIN CLAIM IS THEN ISOLATED TO ITS OWN CASES so a divergence # says which half moved. data/tiff-ref-none.tif and the four `pin-*` digests were # produced on x86-64 by libtiff's own writer and its three INTERNAL codecs (none, # PackBits, LZW, LZW+predictor -- no external library in the path), which were # measured byte-identical between gcc 16.1.1 and clang 22.1.8 before this suite # was written. zip, lzma and jpeg digests are REPORTED in ==TIFF-OBSERVED== and # deliberately not gated, because their bytes belong to zlib, liblzma and # libjpeg-turbo rather than to libtiff. # # data/tiff-fixture.tif is a 64x64 RGB8 baseline TIFF, 4 strips of 16 rows, # BUILT BY HAND FROM THE TIFF SPEC rather than written by libtiff, so "libtiff # reads it correctly" is a claim about libtiff and not a self-comparison. Its # four bands each aim at a different codec path: a horizontal ramp (no runs at # all), a flat block (one long run), a 4x4 four-colour checker (high frequency) # and a hard black/white edge. Exactly regenerable -- see # src/tiff_probe.c's fixture_pixel(), which restates the same integer recipe. # # tiff2rgba IS NOT IN 4.6.0: upstream moved it to archive/tools with thirteen # other tools. The rasterising it did is covered through TIFFReadRGBAImageOriented # in data/tiff-probe, and the `no-*` cases below pin the removal so a future # re-appearance is noticed rather than absorbed. . "$BST_LIB" require tiffinfo tiff/present tmp=${TMPDIR:-/tmp}/bst-tiff.$$ rm -rf "$tmp"; mkdir -p "$tmp" || { bst_notok tiff/tmpdir "cannot mkdir $tmp"; exit 0; } FIX="$BST_DATA/tiff-fixture.tif" REF="$BST_DATA/tiff-ref-none.tif" # ---- 1. the shipped shape ------------------------------------------------ for t in tiffcp tiffinfo tiffdump tiffset tiffsplit; do check tiff/tool-$t command -v $t done # 4.6.0 moved these to archive/tools; tiff2ps/tiff2pdf need the # --enable-tools-unsupported the release note tells packagers not to set. for t in tiff2rgba tiffcrop tiffcmp tiff2bw ppm2tiff raw2tiff rgb2ycbcr \ tiffmedian tiffdither fax2tiff fax2ps pal2rgb thumbnail tiffgt \ tiff2ps tiff2pdf; do if command -v $t >/dev/null 2>&1; then bst_notok tiff/absent-$t "$t is present; 4.6.0 does not build it" else bst_ok tiff/absent-$t fi done check tiff/archive-libtiff test -f /usr/lib/libtiff.a check tiff/pkgconfig test -f /usr/lib/pkgconfig/libtiff-4.pc for h in tiff.h tiffio.h tiffconf.h tiffvers.h; do check tiff/header-$h test -f /usr/include/$h done # Static-only arm: no shared object of either half, and no C++ half at all # (PACKAGECONFIG drops cxx -- CXX here is the C driver and there is no C++ # standard library in the sysroot, so tif_stream.cxx cannot compile). if ls /usr/lib/libtiff.so* >/dev/null 2>&1; then bst_notok tiff/static-only "a libtiff shared object is installed" else bst_ok tiff/static-only fi if ls /usr/lib/libtiffxx.* >/dev/null 2>&1; then bst_notok tiff/no-libtiffxx "libtiffxx is installed; the cxx half is meant to be off" else bst_ok tiff/no-libtiffxx fi # ---- 2. version ----------------------------------------------------------- # Every tool prints its banner to stderr and exits 1 when given no arguments. ver=$(tiffinfo 2>&1 | head -1) case "$ver" in *"Version 4.6.0"*) bst_ok tiff/version ;; *) bst_notok tiff/version "$(_bst_1line "$ver")" ;; esac check_rc tiff/usage-rc 1 tiffinfo check tiff/payload-fixture test -f "$FIX" check tiff/payload-ref test -f "$REF" # ---- 3. reading the fixture ---------------------------------------------- info=$(tiffinfo "$FIX" 2>&1); rc=$? if [ $rc -eq 0 ]; then bst_ok tiff/info-rc; else bst_notok tiff/info-rc "rc=$rc"; fi for pair in \ "width:Image Width: 64" \ "length:Image Length: 64" \ "bps:Bits/Sample: 8" \ "spp:Samples/Pixel: 3" \ "photometric:RGB color" \ "compression:Compression Scheme: None" \ "rowsperstrip:Rows/Strip: 16" \ "planar:single image plane" ; do id=${pair%%:*}; needle=${pair#*:} case "$info" in *"$needle"*) bst_ok tiff/info-$id ;; *) bst_notok tiff/info-$id "no '$needle'" ;; esac done dump=$(tiffdump "$FIX" 2>&1) case "$dump" in *"Magic: 0x4949 "*) bst_ok tiff/dump-endian ;; *) bst_notok tiff/dump-endian "$(_bst_1line "$dump")" ;; esac case "$dump" in *""*) bst_ok tiff/dump-classic ;; *) bst_notok tiff/dump-classic "not reported as ClassicTIFF" ;; esac case "$dump" in *"ImageWidth (256)"*) bst_ok tiff/dump-tag-256 ;; *) bst_notok tiff/dump-tag-256 "ImageWidth tag not dumped" ;; esac case "$dump" in *"StripOffsets (273) LONG (4) 4<"*) bst_ok tiff/dump-4-strips ;; *) bst_notok tiff/dump-4-strips "StripOffsets is not 4 LONGs" ;; esac # ---- 4. the canonical uncompressed form, and the cross-toolchain claim ----- base="$tmp/base.tif" if tiffcp -c none "$FIX" "$base" 2>"$tmp/err"; then bst_ok tiff/canon-write else bst_notok tiff/canon-write "$(_bst_1line "$(cat "$tmp/err")")"; fi # ISOLATED cross-toolchain case: libtiff's writer and its `none` codec produce # byte-identical output on wasm32 as on x86-64. Everything below compares # target-against-target, so only THIS case moves if the writer diverges. if cmp -s "$base" "$REF"; then bst_ok tiff/canon-matches-x86 else bst_notok tiff/canon-matches-x86 "target tiffcp -c none differs from the x86-64 reference"; fi md5of() { md5sum "$1" 2>/dev/null | cut -d' ' -f1; } # ---- 5. every lossless codec, gated on round-trip identity ---------------- OBS="" for spec in "none:5f78dcfa8f4147881fc7997e1e56923d" \ "packbits:1c16dc5ad2af35eb0406c9973ffd76b5" \ "lzw:6722e5fb490f2cb9774045e7c497ed93" \ "lzw_2:929bfd58bd52918624f63d501ea276e1" \ "zip:-" "zip_2:-" "lzma:-" ; do key=${spec%%:*}; want=${spec#*:} codec=$(printf '%s' "$key" | tr '_' ':') enc="$tmp/e-$key.tif"; back="$tmp/b-$key.tif" if tiffcp -c "$codec" "$FIX" "$enc" 2>"$tmp/err"; then bst_ok tiff/enc-$key else bst_notok tiff/enc-$key "$(_bst_1line "$(cat "$tmp/err")")"; continue; fi if tiffcp -c none "$enc" "$back" 2>"$tmp/err"; then bst_ok tiff/dec-$key else bst_notok tiff/dec-$key "$(_bst_1line "$(cat "$tmp/err")")"; continue; fi # THE GATE: what the encoder wrote, the decoder reads back unchanged. if cmp -s "$back" "$base"; then bst_ok tiff/rt-$key else bst_notok tiff/rt-$key "round-trip through $codec is not an identity"; fi d=$(md5of "$enc"); sz=$(wc -c < "$enc" | tr -d ' ') OBS="$OBS $key=$d/$sz" # Only libtiff's OWN codecs are digest-pinned; zip and lzma belong to zlib # and liblzma, whose versions this port does not control. if [ "$want" != "-" ]; then check_eq tiff/pin-$key "$want" "$d" fi done # Compression must actually have happened -- a codec that silently stored would # round-trip perfectly and mean nothing. sz_none=$(wc -c < "$tmp/e-none.tif" 2>/dev/null | tr -d ' ') for key in packbits lzw lzw_2 zip zip_2 lzma; do s=$(wc -c < "$tmp/e-$key.tif" 2>/dev/null | tr -d ' ') if [ -n "$s" ] && [ -n "$sz_none" ] && [ "$s" -lt "$sz_none" ]; then bst_ok tiff/smaller-$key else bst_notok tiff/smaller-$key "$key produced $s bytes vs none $sz_none"; fi done # The horizontal predictor must beat plain LZW on this fixture's ramps. s1=$(wc -c < "$tmp/e-lzw.tif" 2>/dev/null | tr -d ' ') s2=$(wc -c < "$tmp/e-lzw_2.tif" 2>/dev/null | tr -d ' ') if [ -n "$s1" ] && [ -n "$s2" ] && [ "$s2" -lt "$s1" ]; then bst_ok tiff/predictor-helps else bst_notok tiff/predictor-helps "lzw:2 $s2 not smaller than lzw $s1"; fi # ---- 6. the JPEG codec ---------------------------------------------------- jpg="$tmp/e-jpeg.tif" if tiffcp -c jpeg "$FIX" "$jpg" 2>"$tmp/err"; then bst_ok tiff/enc-jpeg else bst_notok tiff/enc-jpeg "$(_bst_1line "$(cat "$tmp/err")")"; fi if [ -f "$jpg" ]; then OBS="$OBS jpeg=$(md5of "$jpg")/$(wc -c < "$jpg" | tr -d ' ')" jinfo=$(tiffinfo "$jpg" 2>&1) case "$jinfo" in *"Compression Scheme: JPEG"*) bst_ok tiff/jpeg-scheme ;; *) bst_notok tiff/jpeg-scheme "compression tag is not JPEG" ;; esac # tiffcp's default JPEG path converts RGB to YCbCr; the JPEGTables tag is # what makes each strip decodable on its own. case "$jinfo" in *"Photometric Interpretation: YCbCr"*) bst_ok tiff/jpeg-ycbcr ;; *) bst_notok tiff/jpeg-ycbcr "not YCbCr" ;; esac case "$jinfo" in *"JPEG Tables:"*) bst_ok tiff/jpeg-tables ;; *) bst_notok tiff/jpeg-tables "no JPEGTables tag" ;; esac # Lossy: it must NOT round-trip byte-exactly. A jpeg that did would mean the # codec silently stored instead of compressing. if tiffcp -c none "$jpg" "$tmp/b-jpeg.tif" 2>/dev/null; then if cmp -s "$tmp/b-jpeg.tif" "$base"; then bst_notok tiff/jpeg-is-lossy "JPEG round-tripped byte-exactly" else bst_ok tiff/jpeg-is-lossy; fi else bst_notok tiff/jpeg-is-lossy "could not decode the JPEG TIFF"; fi fi # ---- 7. negative cases ---------------------------------------------------- # G3/G4 are bilevel codecs; libtiff must refuse them for 8-bit RGB by name. out=$(tiffcp -c g4 "$FIX" "$tmp/g4.tif" 2>&1); rc=$? if [ $rc -ne 0 ]; then bst_ok tiff/g4-refused else bst_notok tiff/g4-refused "tiffcp accepted G4 for an RGB image"; fi case "$out" in *"Bits/sample must be 1"*) bst_ok tiff/g4-diagnostic ;; *) bst_notok tiff/g4-diagnostic "$(_bst_1line "$out")" ;; esac check_rc tiff/missing-file 1 tiffinfo "$tmp/does-not-exist.tif" head -c 400 "$FIX" > "$tmp/trunc.tif" 2>/dev/null check_rc tiff/truncated 1 tiffinfo "$tmp/trunc.tif" printf 'not a tiff at all\n' > "$tmp/garbage.tif" check_rc tiff/garbage 1 tiffinfo "$tmp/garbage.tif" # ---- 8. tiffset, multipage, tiffsplit ------------------------------------- cp "$FIX" "$tmp/set.tif" 2>/dev/null if tiffset -s 315 "Emcraft" "$tmp/set.tif" 2>"$tmp/err"; then bst_ok tiff/set-rc else bst_notok tiff/set-rc "$(_bst_1line "$(cat "$tmp/err")")"; fi check_out tiff/set-artist "Artist: Emcraft" tiffinfo "$tmp/set.tif" # The rewrite must not disturb the image itself. if tiffcp -c none "$tmp/set.tif" "$tmp/set-none.tif" 2>/dev/null && \ ! cmp -s "$tmp/set-none.tif" "$base"; then bst_ok tiff/set-changes-file else bst_notok tiff/set-changes-file "tiffset did not change the file" fi if tiffcp "$FIX" "$FIX" "$tmp/multi.tif" 2>"$tmp/err"; then bst_ok tiff/multipage-write else bst_notok tiff/multipage-write "$(_bst_1line "$(cat "$tmp/err")")"; fi n=$(tiffinfo "$tmp/multi.tif" 2>/dev/null | grep -c 'TIFF Directory at') check_eq tiff/multipage-count 2 "$n" ( cd "$tmp" && rm -f xa*.tif && tiffsplit multi.tif ) 2>/dev/null check tiff/split-page-1 test -f "$tmp/xaaa.tif" check tiff/split-page-2 test -f "$tmp/xaab.tif" if tiffcp -c none "$tmp/xaaa.tif" "$tmp/split-none.tif" 2>/dev/null && \ cmp -s "$tmp/split-none.tif" "$base"; then bst_ok tiff/split-page-content else bst_notok tiff/split-page-content "split page 1 is not the original image" fi # ---- 9. the library, through the cross-linked probe ----------------------- # The probe covers what no CLI here can: the rasteriser tiff2rgba was removed # with, whether the mmap'd read agrees with the read() one, and the libjpeg # longjmp recovery. probe="$tmp/tiff-probe" if [ -f "$BST_DATA/tiff-probe" ] && cp "$BST_DATA/tiff-probe" "$probe" 2>/dev/null \ && chmod +x "$probe" 2>/dev/null; then pout=$("$probe" "$FIX" "$jpg" "$tmp" 2>&1); prc=$? for id in version-is-460 tags-width tags-length tags-bps tags-spp \ tags-photometric tags-compression tags-planar tags-rowsperstrip \ tags-absent-returns-0 geom-strips geom-stripsize geom-scanline \ strip-read strip-payload mmap-agrees-with-read \ rgba-read rgba-pixels rgba-botleft-mirror \ write-scanlines write-predictor-is-destructive \ write-compression-lzw write-predictor-2 write-roundtrip-exact \ jpeg-compression-tag jpeg-decode jpeg-mean-bounded \ jpeg-max-bounded jpeg-corrupt-survived ; do case "$pout" in *"TI-$id OK"*) bst_ok tiff/probe-$id ;; *"TI-$id FAIL"*) det=$(printf '%s\n' "$pout" | grep "TI-$id FAIL" | head -1) bst_notok tiff/probe-$id "$(_bst_1line "$det")" ;; *) bst_notok tiff/probe-$id "probe emitted no TI-$id line" ;; esac done # The payload digest the probe computes from libtiff's decode must equal the # one it computes from the fixture recipe -- the same number twice, from two # independent paths. fx=$(printf '%s\n' "$pout" | awk '/^TI-fixture-fnv1a OK/{print $3}') sp=$(printf '%s\n' "$pout" | awk '/^TI-strip-fnv1a OK/{print $3}') check_eq tiff/probe-fnv1a-recipe 801227461 "$fx" check_eq tiff/probe-fnv1a-decoded 801227461 "$sp" check_eq tiff/probe-rc 0 "$prc" OBS="$OBS $(printf '%s\n' "$pout" | awk '/^TI-jpeg-deviation OK/{print $3" "$4" "$5}')" else bst_notok tiff/probe-stage "cannot stage $BST_DATA/tiff-probe" fi rm -f "$probe" printf '==TIFF-OBSERVED%s==\n' "$OBS" rm -rf "$tmp"