. "$BST_LIB" require perl libperlio-gzip-perl/present # PerlIO::gzip 0.20 is an XS PerlIO LAYER over zlib -- gzip streams read and # written through an ordinary filehandle -- and the tenth Perl module on this # image under BRINTOS-999. # # It is the THIRD that is not pure Perl, after libxml-parser-perl (entry 305) and # libfile-fnmatch-perl (entry 308), and it meets the same wall by the same route # as the first of those: gzip.pm is `use XSLoader (); XSLoader::load "PerlIO::gzip"` # and nothing else, so on a perl built -Uusedl -Uuseshrplib there is no gzip.so # for XSLoader to find and it falls through to `require DynaLoader`, which is in # no perl package this arm produces. Ours, not a brintos.io platform defect: # --disable-shared is distro policy and brintOS has a working dynamic arm that # this image deliberately is not. Tracked by BRINTOS-881. # # What is different here, and is why this suite is shaped unlike its two # predecessors: PerlIO::gzip HAS NO CALLABLE API AT ALL. It exports nothing and # installs exactly one XSUB (`bootstrap`); everything it does it does by # registering a PerlIO layer. So there is no battery of return values to compare # -- the measurement is what an open() through `:gzip` puts on disk and reads # back, and whether the image's OWN gzip(1) agrees with it in both directions. # # Four layers, each honest about what it can see: # 1. filesystem facts -- what the port SHIPS. Green on target today. # 2. the XS gap, as NAMED REDS attributed to BRINTOS-881, written in the # direction that FLIPS: they assert what must become TRUE, so they go green # by themselves the day 881 lands rather than going red and reading as a # regression. # 3. the round-trip battery, driven by the payload. # 4. INTEROPERABILITY with the image's own gzip(1), driven from here because # gzip is a shell tool. This is the half of the ticket's criterion that a # perl-only test cannot state: that what the layer writes is a real gzip # stream by an independent implementation's reckoning, and that what that # implementation writes is readable by the layer. # Every expectation in 3 and 4 was measured against a native x86-64 perl built # from the SAME pristine CPAN tarball this image builds (meta-oe carries # PerlIO-gzip-0.20 with no patches at all), so they are demonstrated rather than # guessed. # ------------------------- 1. what the port SHIPS: filesystem facts ---------- # Asserted before anything starts perl, so the packaging deliverable is measured # even on a boot where the interpreter cannot load a module at all. XS modules # land under the ARCH subdirectory of vendor_perl, not beside the pure-Perl ports. V=$(ls -d /usr/lib/perl5/vendor_perl/*/*-linux 2>/dev/null | head -1) check libperlio-gzip-perl/vendor-arch-dir test -n "$V" check libperlio-gzip-perl/module-installed test -f "$V/PerlIO/gzip.pm" # THE XS ARTIFACT ITSELF. This is what the compile produced -- the port did reach # the compiler, did find zlib's headers and did link. Worth asserting separately # from whether it can be loaded, because for this module the .a IS the whole # implementation: gzip.pm is eleven executable lines that do nothing but load it. check libperlio-gzip-perl/xs-archive test -f "$V/auto/PerlIO/gzip/gzip.a" check libperlio-gzip-perl/payload-installed \ test -f "$BST_DATA/libperlio-gzip-perl-selftest.pl" # THE INTEROP PARTNER. Section 4 is meaningless without it, and an absent gzip # would otherwise show up as a wall of confusing reds down there instead of one # clear one here. Not a `require`: gzip is a documented member of this image, so # its absence is a regression to report, not a coverage prompt to skip on. check libperlio-gzip-perl/gzip-tool-present command -v gzip # ------------------- 2. the XS gap, named rather than skipped ---------------- # Tracked by BRINTOS-881 (migrate to shared libraries and dynamic linking, glibc # arm) -- named in prose rather than as a BRINTOS-WORKAROUND: marker, and # deliberately: that marker's grammar is #, a brintos.io issue for # brintos_reconcile.py to chase, and a JIRA key put in its place parses as # MALFORMED, which leaves the artifact reading as tracked while nothing tracks # it. This gap is OURS and there is no forge issue to chase. Same treatment as # 36-libxml-parser-perl.test, 36-libfile-fnmatch-perl.test and 90-intltool.test. # # Both cases are written in the direction that FLIPS: each asserts what must # become true. Asserting the absence instead -- "there is no gzip.so" -- would # pass today and fail on success, which is the wrong way round for a pin whose # whole purpose is to notice the fix. if [ -f "$V/auto/PerlIO/gzip/gzip.so" ]; then bst_ok libperlio-gzip-perl/xs-shared-object else bst_notok libperlio-gzip-perl/xs-shared-object \ "no gzip.so beside gzip.a -- this arm is -Uusedl -Uuseshrplib, so the layer cannot be loaded (BRINTOS-881)" fi DLP=$(ls /usr/lib/perl5/*/DynaLoader.pm /usr/lib/perl5/*/*-linux/DynaLoader.pm 2>/dev/null | head -1) if [ -n "$DLP" ]; then bst_ok libperlio-gzip-perl/dynaloader-present else bst_notok libperlio-gzip-perl/dynaloader-present \ "DynaLoader.pm is in no perl package in the feed, and XSLoader falls back to it when there is no .so (BRINTOS-881)" fi # ------------------ 3. can perl LOAD this module on this boot at all? -------- # BRINTOS-WORKAROUND: linux-in-the-browser#154 remove -- perl aborts in glibc's # allocator while COMPILING a module, so this guard is needed on top of the 881 # gap; delete it and the battery below runs unconditionally again once BOTH # close. Two independent blocks sit between this port and its own battery, and # attributing a red to the wrong one is exactly the error a later reader cannot # detect -- so the guard classifies on the DIAGNOSTIC and says which it met. pout=$(perl -MPerlIO::gzip -e 'print "GZIP-OK\n"' 2>&1); prc=$? case "$pout" in *GZIP-OK*) bst_ok libperlio-gzip-perl/import-load ;; *) # The interpreter's locale warning is a four-line preamble on this image and # is NOT the failure: it precedes the real diagnostic and would otherwise # fill the 100-char detail field, leaving the red reading "Setting locale # failed" -- the wrong diagnosis in the one field a later reader attributes by. pdiag=$(printf '%s\n' "$pout" | awk '/^perl: warning/ { skip=1; next } /^[ \t]/ && skip { next } { skip=0; print }') [ -n "$pdiag" ] || pdiag=$pout case "$pdiag" in *DynaLoader*|*"Can't load"*|*"object file"*|*"Can't locate loadable object"*) why="BRINTOS-881 -- no DynaLoader on this -Uusedl arm, so XSLoader has nothing to fall back to" ;; *mremap_chunk*|*munmap_chunk*|*"corrupted size"*|*"corrupted double-linked list"*|\ *"double free or corruption"*|*"free(): "*|*"malloc(): "*|*"realloc(): "*) # The pattern set is WIDE on purpose and was widened by measurement, not # by guesswork: six verify boots of this image met #154 twice, and the # second of those said `double free or corruption (!prev)`, which an # earlier, narrower set did not match -- so the red read `unclassified` # and would have been reported as a NEW defect. glibc has a dozen # spellings for one abort; missing one of them turns an attributed red # into a false alarm in the single field a later reader attributes by. why="linux-in-the-browser#154 -- perl aborts in glibc's allocator while compiling a module" ;; *) # A bare SIGABRT is reported as its own thing rather than folded into # either block. It is NOT claimed as #154 -- an allocator message is what # identifies #154 and there is none here -- but it is not the same signal # as a clean exit with an unknown diagnostic either, and saying which of # the two it is costs one line and is what a reader needs first. if [ "$prc" = 134 ]; then why="unclassified SIGABRT -- rc=134 with no allocator diagnostic; probably #154 with a spelling this guard has not seen, but not asserted as one" else why="unclassified -- neither BRINTOS-881's DynaLoader signature nor #154's allocator abort" fi ;; esac bst_notok libperlio-gzip-perl/import-load "rc=$prc $(_bst_1line "$pdiag") -- $why" # Named rather than silent. The ticket's headline criteria are the round trip # through the :gzip layer, gzip(1) agreeing with what it wrote, and a damaged # stream refusing rather than passing short data -- and those are exactly what # could not be measured. A suite that merely stopped here would report seven # greens and read as a pass. bst_notok libperlio-gzip-perl/roundtrip-unverified \ "not run: perl cannot load PerlIO::gzip ($why) -- the :gzip round trip, the gzip(1) interop in BOTH directions and the damaged-stream refusal are unverified on wasm32-hwjs" exit 0 ;; esac # ------------------------- 4. the layer's own answers ------------------------ # The payload writes its artifacts into $BST_GZ_DIR and prints one `RNN-tag value` # line per measurement; section 5 then drives the image's gzip against those same # artifacts, which is why the directory is created here and not inside the payload. GZD=${TMPDIR:-/tmp}/bst-perliogzip.$$ mkdir -p "$GZD" || GZD=/tmp export BST_GZ_DIR="$GZD" out=$(perl "$BST_DATA/libperlio-gzip-perl-selftest.pl" 2>&1); rc=$? case "$out" in *ALL-PERLIO-GZIP-OK*) all=1 ;; *) all=0 ;; esac if [ "$rc" -eq 0 ] && [ "$all" -eq 1 ]; then bst_ok libperlio-gzip-perl/all-ok else bst_notok libperlio-gzip-perl/all-ok "rc=$rc $(_bst_1line "$out")"; fi val() { printf '%s\n' "$out" | awk -v t="$1" '$1==t{ $1=""; sub(/^ /,""); print; exit }'; } # --- the module loads, from the image rather than a stray @INC dir ---------- check_eq libperlio-gzip-perl/version 0.20 "$(val R1-version)" case "$(val R2-inc-path)" in /usr/*) bst_ok libperlio-gzip-perl/installed-path ;; *) bst_notok libperlio-gzip-perl/installed-path "resolved to $(val R2-inc-path)" ;; esac # --- the XS layer is REALLY loaded ------------------------------------------ # The census being ONE is the assertion, not an accident of counting: this module # installs no callable API, so `bootstrap` is the only XSUB the package should # ever contain. Two would mean something else got installed; zero, a partial # bootstrap. The layer name on a live handle is the strongest single witness -- # `gzip()` is in PerlIO::get_layers only because the XS BOOT block called # PerlIO_define_layer, and no Perl-side stub can put it there. check_eq libperlio-gzip-perl/xs-bootstrap 1 "$(val R10-xsub-bootstrap)" check_eq libperlio-gzip-perl/xs-census 1 "$(val R11-xsub-census)" check_eq libperlio-gzip-perl/xs-names bootstrap "$(val R12-xsub-names)" check_eq libperlio-gzip-perl/xs-dynaloaded 1 "$(val R13-dl-modules-has)" check_eq libperlio-gzip-perl/xs-object-name gzip.so "$(val R14-dl-basename)" check_eq libperlio-gzip-perl/layer-on-handle 'gzip()' "$(val R15-layer-name)" # --- WHAT THE TICKET ASKS (1/3): the round trip ----------------------------- check_eq libperlio-gzip-perl/write-close 1 "$(val R20-write-close)" check_eq libperlio-gzip-perl/payload-len 10692 "$(val R21-payload-len)" check_eq libperlio-gzip-perl/read-identical 1 "$(val R30-read-identical)" check_eq libperlio-gzip-perl/read-len 10692 "$(val R31-read-len)" check_eq libperlio-gzip-perl/read-close 1 "$(val R32-read-close)" check_eq libperlio-gzip-perl/compressed-smaller 1 "$(val R27-compressed-smaller)" # --- the header the layer writes, byte by byte ------------------------------ # These are what make "a real gzip stream" a measurement rather than an opinion, # and they are read out of the file itself, not reported by the module. # The OS byte is 255 ("unknown", rfc1952) because Makefile.PL emits # -DLAYERGZIP_DEFAULT_OS_TYPE only when the perl it reads reports `unix=` in # cppccsymbols, and the target perl's Config_heavy.pl has cppccsymbols EMPTY -- # so gzip.xs's own default applies. The x86-64 control reads 255 for exactly the # same reason, which is why this is pinned rather than tolerated. check_eq libperlio-gzip-perl/header-magic 1f8b "$(val R22-magic)" check_eq libperlio-gzip-perl/header-cm 8 "$(val R23-cm)" check_eq libperlio-gzip-perl/header-flg 0 "$(val R24-flg)" check_eq libperlio-gzip-perl/header-os 255 "$(val R25-os)" check_eq libperlio-gzip-perl/header-mtime 1 "$(val R26-mtime-tracks-clock)" # --- binary and line-oriented traffic --------------------------------------- # Every byte value, 256 times over: NUL and the high half are where a layer that # had quietly gone through a text transform would show up. check_eq libperlio-gzip-perl/binary-identical 1 "$(val R33-binary-identical)" check_eq libperlio-gzip-perl/binary-len 65536 "$(val R34-binary-len)" check_eq libperlio-gzip-perl/binary-close 1 "$(val R35-binary-close)" check_eq libperlio-gzip-perl/lines-count 1000 "$(val R36-lines-count)" check_eq libperlio-gzip-perl/lines-last 'row 1000' "$(val R37-lines-last)" check_eq libperlio-gzip-perl/lines-close 1 "$(val R38-lines-close)" check_eq libperlio-gzip-perl/empty-member-size 20 "$(val R39-empty-member-size)" check_eq libperlio-gzip-perl/empty-read-empty 1 "$(val R40-empty-read-empty)" check_eq libperlio-gzip-perl/empty-close 1 "$(val R41-empty-close)" # --- the four header modes, each measured for what it CHANGES --------------- check_eq libperlio-gzip-perl/none-not-gzip-magic 1 "$(val R50-none-not-gzip-magic)" check_eq libperlio-gzip-perl/none-roundtrip 1 "$(val R51-none-roundtrip)" check_eq libperlio-gzip-perl/none-close 1 "$(val R52-none-close)" check_eq libperlio-gzip-perl/auto-on-gzip 1 "$(val R53-auto-on-gzip)" check_eq libperlio-gzip-perl/autopop-passthrough 1 "$(val R54-autopop-passthrough)" # `lazy` writes NOTHING when nothing was written, where the default mode writes a # 20-byte empty member. That difference IS the flag, and asserting the pair # (R39 above against R55 here) is what makes it a measurement of `lazy` rather # than of the file happening to be small. check_eq libperlio-gzip-perl/lazy-empty-size 0 "$(val R55-lazy-empty-size)" check_eq libperlio-gzip-perl/append-open 1 "$(val R56-append-open)" check_eq libperlio-gzip-perl/append-close 1 "$(val R57-append-close)" # --- WHAT THE TICKET ASKS (2/3): a damaged stream must not pass silently ---- # The ticket asks for "fail on read rather than return short data silently". The # module's contract is neither half of that phrasing and the difference matters: # cutting only the 8-byte CRC32+ISIZE trailer leaves the deflate stream complete, # so the read returns EVERY byte and does not fail. What refuses is `close`, # which is the module's error channel for a failed integrity check. # # R60/R61 are that pair and they are the strongest cases in the file: the data # comes back whole (R60) and close STILL fails (R61). A read-side check would see # nothing wrong there, so asserting close is strictly stronger than what the # ticket asked for, and it holds at every depth below as well. check_eq libperlio-gzip-perl/trunc-trailer-data-full 1 "$(val R60-trunc-trailer-data-full)" check_eq libperlio-gzip-perl/trunc-trailer-close 0 "$(val R61-trunc-trailer-close-ok)" check_eq libperlio-gzip-perl/trunc-mid-short 1 "$(val R62-trunc-mid-short)" check_eq libperlio-gzip-perl/trunc-mid-close 0 "$(val R63-trunc-mid-close-ok)" check_eq libperlio-gzip-perl/trunc-head-no-data 1 "$(val R64-trunc-head-no-data)" check_eq libperlio-gzip-perl/trunc-head-close 0 "$(val R65-trunc-head-close-ok)" # a flipped byte in the CRC32 field: right length, wrong integrity -- the case # that catches an inflate path which forgot to check the checksum at all. check_eq libperlio-gzip-perl/badcrc-data-full 1 "$(val R66-badcrc-data-full)" check_eq libperlio-gzip-perl/badcrc-close 0 "$(val R67-badcrc-close-ok)" # a corrupt MAGIC is caught at open(), before any data is read... check_eq libperlio-gzip-perl/badmagic-open 0 "$(val R68-badmagic-open-ok)" # ...unless `lazy` was asked for, which is precisely what `lazy` defers. The POD # warns about this and it is pinned as documented behaviour, not as a bug. check_eq libperlio-gzip-perl/badmagic-lazy-open 1 "$(val R69-badmagic-lazy-open-ok)" check_eq libperlio-gzip-perl/badmagic-lazy-no-data 1 "$(val R70-badmagic-lazy-no-data)" check_eq libperlio-gzip-perl/badmagic-lazy-close 0 "$(val R71-badmagic-lazy-close-ok)" check_eq libperlio-gzip-perl/emptyfile-open 0 "$(val R72-emptyfile-open-ok)" # compression and decompression are exclusive; the POD says so and the XS enforces it check_eq libperlio-gzip-perl/readwrite-open 0 "$(val R73-readwrite-open-ok)" # --- the layer-plumbing trap ------------------------------------------------ # binmode(FH) with no argument means ":raw", and ":raw" POPS the gzip layer -- # after which reads return the COMPRESSED bytes with no error anywhere. Pinned as # behaviour, not filed as a defect: it is how PerlIO layers are defined to work, # and it is the single most likely way to misuse this module. check_eq libperlio-gzip-perl/binmode-strips-layer 1 "$(val R80-binmode-strips-layer)" check_eq libperlio-gzip-perl/no-warnings 0 "$(val R99-warnings)" # ------- 5. WHAT THE TICKET ASKS (3/3): the image's OWN gzip agrees --------- # The ticket's criterion is that the file on disk is "a real gzip stream that the # image's own gzip -d decompresses to the same bytes". Everything above is the # module marking its own homework -- a layer that wrote a private format and read # it back would satisfy all of it. THIS section is the independent implementation, # and it is the reason the payload leaves its artifacts behind instead of using # a scratch file it deletes. check_rc libperlio-gzip-perl/gzip-t-accepts 0 gzip -t "$GZD/rt.gz" check libperlio-gzip-perl/gzip-d-identical \ sh -c "gzip -dc '$GZD/rt.gz' | cmp -s - '$GZD/rt.txt'" # the ISIZE trailer, read by the other implementation: gzip -l reports the # uncompressed length from the stream's own trailer, so agreement here is the # trailer being correct rather than merely present. check_eq libperlio-gzip-perl/gzip-l-size 10692 \ "$(gzip -l "$GZD/rt.gz" 2>/dev/null | awk 'NR==2{print $2}')" # binary in both implementations' reckoning check_eq libperlio-gzip-perl/gzip-d-binary-bytes 65536 \ "$(gzip -dc "$GZD/bin.gz" 2>/dev/null | wc -c | tr -d ' ')" # the empty member is well-formed to gzip too, not just to us check_rc libperlio-gzip-perl/gzip-t-empty-member 0 gzip -t "$GZD/empty.gz" # two appended members read as one stream by the other implementation as well check_eq libperlio-gzip-perl/gzip-d-append 'first second' \ "$(gzip -dc "$GZD/app.gz" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')" # ...and the damaged streams are rejected by gzip too, which is what makes the # close-side refusals above a property of the DATA rather than of this module. check_rc libperlio-gzip-perl/gzip-t-rejects-trunc-trailer 1 gzip -t "$GZD/cut-trailer.gz" check_rc libperlio-gzip-perl/gzip-t-rejects-trunc-mid 1 gzip -t "$GZD/cut-mid.gz" check_rc libperlio-gzip-perl/gzip-t-rejects-badcrc 1 gzip -t "$GZD/badcrc.gz" # a `none`-mode stream is deliberately NOT a gzip file, so gzip must refuse it. # This is the negative control for the whole section: it is what proves the # accepts above are gzip actually parsing rather than gzip accepting anything. check_rc libperlio-gzip-perl/gzip-t-rejects-headerless 1 gzip -t "$GZD/nohdr.bin" # THE REVERSE DIRECTION, which nothing above covers: a stream this module did not # write. Everything so far reads back what the layer itself produced, so a # symmetric bug in both halves would survive it. Here GNU gzip compresses and the # layer decompresses. gzip -c "$GZD/rt.txt" > "$GZD/ext.gz" 2>/dev/null check libperlio-gzip-perl/reads-gnu-gzip-stream \ perl -MPerlIO::gzip -e ' open(my $r, "<:gzip", $ARGV[0]) or exit 1; my $got = do { local $/; <$r> }; close($r) or exit 2; open(my $o, "<:raw", $ARGV[1]) or exit 3; my $want = do { local $/; <$o> }; exit(defined $got && $got eq $want ? 0 : 4); ' "$GZD/ext.gz" "$GZD/rt.txt" rm -rf "$GZD"