# BST-TIMEOUT: 300 # ~260 cases and a multi-megabyte wasm probe to stage . "$BST_LIB" # libmng 2.0.3 -- the reference decoder for MNG and JNG, the animation and # JPEG-in-PNG siblings of PNG, as meta-oe ships it from recipes-graphics/libmng. # It installs no executable at all: the deliverable is libmng-dev + # libmng-staticdev, an archive, three headers and a .pc. # # --------------------------------------------------------------------------- # WHY THIS SUITE COMPARES EXACTLY, WHERE 78-libspiro NEEDED A TOLERANCE # # libspiro iterates -- Newton's method over doubles -- so its answer inherits # whatever libm on the target does differently from glibc's on x86_64, and an # exact comparison there would go red on a correct port. Nothing of the sort # happens here. MNG decoding is inflate plus integer compositing: the PNG spec # fixes the decoded bytes exactly, and inflate's output is a property of the # committed compressed bytes rather than of the zlib version that expands them. # So byte-exactness IS the right gate, and any disagreement between the two # machines is a defect. # # The one place that reasoning does NOT hold is JPEG, whose IDCT accuracy is # implementation-defined within limits. That is why the two JNG fixtures are # split: mng-solid.jng is a CONSTANT block, which has no AC coefficients and so # decodes to one exact value on any conforming decoder (anchored as a literal # below), while mng-grad.jng has real AC coefficients and is compared against # the control only. Both sides run libjpeg-turbo 3.0.1 with no SIMD -- the image # builds it as a recipe and wasm32 has no SIMD path, and the oracle was built # from the same tarball with WITH_SIMD=0 -- so the gradient comparison is exact # and meaningful; it is kept out of the anchored set because it would go red on # a libjpeg-turbo upgrade, which is not this port's defect to carry. # # WHAT IS ACTUALLY BEING MEASURED. Without a cross-linked consumer this suite # could only assert that an archive and three headers exist -- which would pass # just as happily if every function in the archive trapped on first call. # data/mng-probe is that consumer: src/mng_probe.c cross-linked against the very # archive this port installs, registering the twelve callbacks an image viewer # registers and reading RGBA out of the canvas libmng composites into. It # snapshots the canvas AT EACH REFRESH, so the four animation frames are # compared individually -- a final-canvas-only check would pass an animation # whose middle frames decoded to garbage but which ended on a correct one. # # THE ANCHORED CASES EXIST BECAUSE AN ORACLE CAN BE WRONG. The bulk of this file # compares target output against data/mng-native.txt, which catches any # disagreement between the two machines but would NOT catch a control captured # from an already-broken build. These cases therefore never consult the control: # # * The fixtures whose decoded value follows from the spec alone. Every # fixture is generated by src/mng_mkfixtures.py from the MNG/JNG/PNG specs, # never written by libmng, so "libmng reads it correctly" is a claim about # libmng rather than a self-comparison. mng-solid.mng is one opaque colour, # mng-1x1.mng one opaque pixel, mng-solid.jng one constant JPEG block: each # is asserted here as the literal RGBA it must produce. Animation frames 0 # and 3 are opaque and cover the whole canvas, so whatever is composited # underneath is overwritten and the frame IS its integer recipe -- their # digests are computed from those recipes by mng_mkfixtures.py's # expected_anim_frame_digests() and anchored below. Frames 1 and 2 are a # semi-transparent alpha ramp and a palette with a transparent entry, so # they are genuinely composited and are left to the control. # * The plain-PNG digest, computed from the PNG spec by # mng_mkfixtures.py's expected_png_digest() rather than read back. # * A structural invariant: the final canvas must equal the last frame. # * The error path. mng-bad.mng ends inside IDAT, and a decoder that reported # success there would be the defect -- so the retcode, the severity and the # absence of any refresh are anchored, not compared. # * The header's own constants -- version, canvas styles, retcodes, the # signature-type enum and the type widths every callback signature depends # on. These are what would catch the image having been built against a # DIFFERENT libmng than the control was captured from, in which case every # pixel below could differ for a reason that is not a defect. # --------------------------------------------------------------------------- # A root prefix, empty on the target and set only when this suite is validated # NATIVELY before it ships. Writing /usr/lib and /usr/include needs real root, # so a native run stages them under a temporary tree and points this at it. The # runner never sets it, so on the image these are exactly the absolute paths. R="${BST_MNG_ROOT:-}" ARCHIVE=$R/usr/lib/libmng.a INCDIR=$R/usr/include PCFILE=$R/usr/lib/pkgconfig/libmng.pc PROBE=$BST_DATA/mng-probe NATIVE=$BST_DATA/mng-native.txt if [ ! -f "$ARCHIVE" ]; then bst_skip mng/present "libmng not installed on this image" exit 0 fi bst_ok mng/present # --- the package is whole ---------------------------------------------------- check mng/hdr-libmng test -f "$INCDIR/libmng.h" check mng/hdr-conf test -f "$INCDIR/libmng_conf.h" check mng/hdr-types test -f "$INCDIR/libmng_types.h" check mng/staticlib test -f "$ARCHIVE" check mng/pkgconfig test -f "$PCFILE" # 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 mng/no-shared "$R/usr/lib/libmng.so*" # The .pc must describe what a cross consumer is actually handed, and libdir is # asserted as the RESOLVED literal because that is what the shipping build # writes: Yocto's autotools class passes --libdir explicitly, so the # substitution is already done by the time the file is packaged. A bare native # ./configure instead leaves the reference form ${exec_prefix}/lib -- which is # the trap BRINTOS-827 and BRINTOS-828 both recorded, the second of them by # shipping the reference form and going 360/361 on the first run against a # staged rootfs. Measured here off this port's own staged file, not inherited. check_out mng/pc-name "Name: libmng" cat "$PCFILE" check_out mng/pc-libs "-lmng" cat "$PCFILE" check_out mng/pc-libdir "libdir=/usr/lib" cat "$PCFILE" # THE .pc SAYS 2.0.2 AND THE LIBRARY SAYS 2.0.3, AND BOTH ARE CORRECTLY PINNED. # This is upstream's inconsistency, not the port's: configure.ac carries # `define([REL_NR],[2])` while the release is 2.0.3, so `@VERSION@` expands to # 2.0.2 -- and Yocto's autotools class runs autoreconf, which regenerates # configure from configure.ac and then config.status regenerates libmng.pc from # libmng.pc.in with that value. The tarball's own prebuilt libmng.pc says 2.0.3, # which is why a native build that does NOT autoreconf (no autoconf installed) # reports 2.0.3 and reads as if the port had changed something. # # The LIBRARY's version is unaffected: MNG_VERSION_TEXT is a literal "2.0.3" in # libmng.h, identical in the pristine tarball, the oracle and the shipped # header, and mng/anchor-version pins it. So the two values genuinely disagree # with each other on a correct build, and pinning both is what records that a # pkg-config consumer is told the wrong version by upstream. check_out mng/pc-version "Version: 2.0.2" cat "$PCFILE" # --- the probe and its control ship together --------------------------------- if [ ! -f "$PROBE" ]; then bst_notok mng/probe-present "$PROBE missing -- the port cannot be functionally verified" exit 0 fi bst_ok mng/probe-present if [ ! -f "$NATIVE" ]; then bst_notok mng/native-present "$NATIVE missing -- no oracle to compare against" exit 0 fi bst_ok mng/native-present # --- run the consumer once --------------------------------------------------- # brintos-selftests' do_install stages data/* at 0644, and the deployed rootfs is # read-only at browser runtime, so the probe cannot simply be executed where it # lies -- stage an executable copy under TMPDIR, as every probe-driven suite in # this package does. tmp=${TMPDIR:-/tmp}/bst-mng.$$ _p=$tmp.bin cp "$PROBE" "$_p" 2>/dev/null && chmod 0755 "$_p" 2>/dev/null if [ ! -x "$_p" ]; then bst_notok mng/probe-exec "could not stage an executable copy of the probe" rm -f "$_p" exit 0 fi bst_ok mng/probe-exec "$_p" "$BST_DATA" > "$tmp.out" 2> "$tmp.err"; rc=$? rm -f "$_p" check_eq mng/probe-rc 0 "$rc" # A truncated run must be distinguishable from a run that legitimately produced # these lines, so the probe prints a terminator and this is checked before any # comparison. Without it a trap half way through would silently reduce the # number of cases rather than fail one. if [ "$(tail -n 1 "$tmp.out" 2>/dev/null)" = "PROBE-END" ]; then bst_ok mng/probe-complete else bst_notok mng/probe-complete "no PROBE-END terminator: $(_bst_1line "$(tail -n 3 "$tmp.out" 2>/dev/null)")" fi want_n=$(wc -l < "$NATIVE" | tr -d ' ') got_n=$(wc -l < "$tmp.out" | tr -d ' ') check_eq mng/line-count "$want_n" "$got_n" # --- the anchored cases: the specs and the header, not the control ----------- mng_get() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$tmp.out"; } # The library's own version, compiled into the archive. This is the one case # that would catch the image having been built against a different libmng than # the control was captured from. check_eq mng/anchor-version "2.0.3" "$(mng_get I01-version)" check_eq mng/anchor-version-major 2 "$(mng_get I02-version-major)" check_eq mng/anchor-version-minor 0 "$(mng_get I03-version-minor)" # THE LIBRARY REPORTS ITSELF AS "2.0.3" AND ITS RELEASE NUMBER AS 2, AND BOTH # ARE PINNED BECAUSE BOTH ARE WHAT A CORRECT BUILD PRODUCES HERE. # # The two come from different places. MNG_VERSION_TEXT is a literal in # libmng.h; MNG_VERSION_MAJOR/MINOR/RELEASE are #undef in config.h.in and are # supplied by autoconf from configure.ac's define([REL_NR],[2]). Upstream's # 2.0.3 tarball ships a PRE-GENERATED configure carrying PACKAGE_VERSION=2.0.3 # beside a configure.ac that still says 2 -- so whether this reads 2 or 3 # depends entirely on whether the build ran autoreconf. Yocto's autotools class # does, so the image follows configure.ac and gets 2. # # Asserting 3 here -- the value a casual native ./configure produces -- would be # a false red on a correct port, the same trap as mng/pc-libdir and # mng/pc-version. The oracle is built to the shipping configuration for the same # reason, so the control below agrees rather than needing an exemption. check_eq mng/anchor-version-release 2 "$(mng_get I04-version-release)" # The canvas styles are the API: a consumer ORs these into set_canvasstyle, and # a changed value would silently hand it a differently-ordered raster. check_eq mng/anchor-canvas-rgb8 0 "$(mng_get I05-canvas-rgb8)" check_eq mng/anchor-canvas-rgba8 4096 "$(mng_get I06-canvas-rgba8)" check_eq mng/anchor-canvas-argb8 12288 "$(mng_get I07-canvas-argb8)" check_eq mng/anchor-canvas-bgr8 1 "$(mng_get I08-canvas-bgr8)" # The retcodes a consumer switches on. check_eq mng/anchor-rc-noerror 0 "$(mng_get I09-rc-noerror)" check_eq mng/anchor-rc-needtimerwait 15 "$(mng_get I10-rc-needtimerwait)" check_eq mng/anchor-rc-needmoredata 14 "$(mng_get I11-rc-needmoredata)" check_eq mng/anchor-rc-unexpectedeof 4 "$(mng_get I12-rc-unexpectedeof)" check_eq mng/anchor-rc-invalidsig 1025 "$(mng_get I13-rc-invalidsig)" # The signature-type enum, in declaration order. check_eq mng/anchor-it-unknown 0 "$(mng_get I14-it-unknown)" check_eq mng/anchor-it-png 1 "$(mng_get I15-it-png)" check_eq mng/anchor-it-mng 2 "$(mng_get I16-it-mng)" check_eq mng/anchor-it-jng 3 "$(mng_get I17-it-jng)" # Type widths every callback signature depends on. mng_handle and mng_size_t are # pointer-width, which is 4 on wasm32 against 8 on x86_64 -- so these two are # EXPECTED to differ between the oracle and the target, and they are asserted # here against the target's own arm rather than compared to the control. The # control comparison below tolerates them for the same reason. check_eq mng/anchor-sizeof-uint32 4 "$(mng_get I18-sizeof-uint32)" check_eq mng/anchor-bool-true 1 "$(mng_get I21-bool-true)" check_eq mng/anchor-bool-false 0 "$(mng_get I22-bool-false)" # --- anchored: the fixtures whose value follows from the spec ---------------- # 8x8 of one opaque colour. Nothing is composited, filtered or interpolated, so # the canvas is this colour in every pixel by construction. _solid=""; _i=0 while [ $_i -lt 64 ]; do _solid="${_solid}2a7fc3ff"; _i=$((_i + 1)); done check_eq mng/anchor-solid-hex "$_solid" "$(mng_get S-canvas-hex)" check_eq mng/anchor-solid-size "8x8" "$(mng_get S-canvas)" # The degenerate canvas: one opaque pixel. check_eq mng/anchor-1x1-hex "010203ff" "$(mng_get U-canvas-hex)" check_eq mng/anchor-1x1-size "1x1" "$(mng_get U-canvas)" # 8x8 JNG of one colour. A constant block has no AC coefficients, so its IDCT is # the DC term alone and every conforming decoder returns the same integer -- # which is why a JPEG-bearing fixture can be anchored at all. _jsolid=""; _i=0 while [ $_i -lt 64 ]; do _jsolid="${_jsolid}4080c0ff"; _i=$((_i + 1)); done check_eq mng/anchor-jng-solid-hex "$_jsolid" "$(mng_get J-canvas-hex)" check_eq mng/anchor-jng-sigtype 3 "$(mng_get J-sigtype)" # A bare PNG read through libmng. RGB8, so a correct decoder synthesises a=255. # The digest is computed from the PNG spec by mng_mkfixtures.py, not read back. check_eq mng/anchor-png-digest "34cac2fda6e30f05" "$(mng_get P-canvas-digest)" check_eq mng/anchor-png-sigtype 1 "$(mng_get P-sigtype)" check_eq mng/anchor-png-size "12x9" "$(mng_get P-canvas)" # The two opaque animation frames, against digests computed from their integer # recipes rather than from a previous run of this library. check_eq mng/anchor-frame0-digest "80b58111024b72e5" "$(mng_get A-f0-digest)" check_eq mng/anchor-frame3-digest "81f5c1815d2d24a5" "$(mng_get A-f3-digest)" # Structural invariants of the animation, true of any correct decode: four # frames were declared and four refreshes must arrive, none dropped by the # probe's cap, and the canvas left behind must BE the last frame. check_eq mng/anchor-anim-frames 4 "$(mng_get A-refreshes)" check_eq mng/anchor-anim-framecount 4 "$(mng_get A-framecount)" check_eq mng/anchor-anim-overflow 0 "$(mng_get A-overflow)" check_eq mng/anchor-anim-sigtype 2 "$(mng_get A-sigtype)" check_eq mng/anchor-anim-final-is-last-frame \ "$(mng_get A-canvas-digest)" "$(mng_get A-f3-digest)" # Every frame of the animation must differ from the one before it: four frames # that decoded to the same raster would compare clean against a control captured # from the same fault, and would satisfy every digest above. _prev=""; _dupes=0; _i=0 while [ $_i -lt 4 ]; do _d=$(mng_get "A-f$_i-digest") [ -n "$_d" ] && [ "$_d" = "$_prev" ] && _dupes=$((_dupes + 1)) _prev=$_d; _i=$((_i + 1)) done check_eq mng/anchor-anim-frames-distinct 0 "$_dupes" # --- anchored: the error path ------------------------------------------------ # mng-bad.mng ends inside IDAT. A decoder that reported success here, or that # handed the application a frame it never finished reading, would be the defect. check_eq mng/anchor-bad-rc 4 "$(mng_get B-rc)" check_eq mng/anchor-bad-refreshes 0 "$(mng_get B-refreshes)" _bad=$(mng_get B-lasterror) check_eq mng/anchor-bad-lasterror "4" "${_bad%% *}" # --- the control comparison -------------------------------------------------- # Exact, line for line, keyed by the first field. Two keys are EXPECTED to # differ and are exempted by name rather than by a wildcard: mng_handle and # mng_size_t are pointer-width, 4 on wasm32 against 8 on the x86_64 oracle. # Anything else that differs is a real divergence. # # A mismatched hex raster reports the byte offset of the first difference, not # just "differs": the lines are kilobytes long and an offset localises the # failure to a pixel without a second run. awk -v ctl="$NATIVE" ' # The offset is into the RASTER, so the key and its separator come off first: # measured from the start of the line it would name the wrong pixel, and a # reader following it would look at the wrong part of the image. function first_diff(a, b, i, n) { sub(/^[^ ]* /, "", a); sub(/^[^ ]* /, "", b) n = length(a) < length(b) ? length(a) : length(b) for (i = 1; i <= n; i++) if (substr(a, i, 1) != substr(b, i, 1)) return int((i - 1) / 2) return int(n / 2) } BEGIN { exempt["I19-sizeof-handle"] = 1 # pointer width: 4 on wasm32, 8 on x86_64 exempt["I20-sizeof-size"] = 1 n = 0 while ((getline line < ctl) > 0) { n++; want[n] = line } nwant = n } { got[NR] = $0 } END { lim = (NR > nwant) ? NR : nwant for (i = 1; i <= lim; i++) { g = (i <= NR) ? got[i] : "" w = (i <= nwant) ? want[i] : "" gk = g; sub(/ .*/, "", gk) wk = w; sub(/ .*/, "", wk) id = (wk != "") ? wk : gk if (id == "") continue if (exempt[id]) { print "ok " id " exempt"; continue } if (g == "") { print "notok " id " absent-from-target"; continue } if (w == "") { print "notok " id " absent-from-control"; continue } if (gk != wk) { print "notok " id " line-order want:" wk " got:" gk; continue } if (g == w) { print "ok " id } else if (id ~ /-hex$/) print "notok " id " first differing byte at offset " first_diff(g, w) else { sub(/^[^ ]* /, "", g); sub(/^[^ ]* /, "", w) print "notok " id " want:" w " got:" g } } } ' "$tmp.out" > "$tmp.cmp" while IFS= read -r line; do _v=${line%% *}; _r=${line#* } _id=${_r%% *}; _d=${_r#* } if [ "$_v" = ok ]; then bst_ok "mng/ctl-$_id"; else bst_notok "mng/ctl-$_id" "$_d"; fi done < "$tmp.cmp" # Nothing should reach stderr: the probe writes only to stdout, so anything here # came either from the runtime (a trap message, a loader complaint) or from # libmng's own error reporting, which none of these fixtures is supposed to # trigger on stderr. if [ -s "$tmp.err" ]; then bst_notok mng/clean-stderr "$(_bst_1line "$(cat "$tmp.err")")" else bst_ok mng/clean-stderr fi rm -f "$tmp.out" "$tmp.err" "$tmp.cmp"