. "$BST_LIB" require perl libdev-checklib-perl/present # BST-TIMEOUT: 600 # # Devel::CheckLib 1.16 (meta-oe recipes-devtools/perl/libdev-checklib-perl_1.16.bb) # -- the build-time probe a CPAN distribution runs from its Makefile.PL to decide # whether a C library and its headers are usable before it commits to building # against them. Thirteenth Perl module on this image under BRINTOS-999, sixth # that is pure Perl. # # THIS SUITE'S PASS CRITERION IS THE DISTRIBUTION'S OWN t/, NOT A HAND-WRITTEN # BATTERY -- the ticket's own wording. The port therefore installs the suite # (libdev-checklib-perl-ptest, from ptest-perl enabled for this recipe alone) # and section 5 runs `prove -r t/` over it. # # WHAT THE TICKET EXPECTED, AND WHAT IS ACTUALLY TRUE HERE. The ticket predicted # the t/ cases would "compile and link small probes against libraries known to be # present and known to be absent, so the suite exercises both the success and the # failure path of the probe on target." They do not, and cannot: THIS IMAGE SHIPS # NO C COMPILER. Target perl's $Config{cc} is `wasm32-hwjs-linux/wasmcc-glibc # -flto`, a cross-compiler path that exists on the build host and not on the # rootfs, so Devel::CheckLib's _findcc() walks $ENV{PATH}, finds nothing, and the # module dies "Couldn't find your C compiler." at import. # # That is not a defect and it is not a failure of the criterion. It is the exact # condition NINE of the twelve .t files test for and `plan skip_all` on, and # which 00-load.t accepts as a pass outright. The criterion is met with three # tests running. Section 4 recovers what it can of the ticket's intent by driving # _findcc()'s SUCCESS path directly -- the thing nine skips would otherwise leave # entirely unexercised on this arm. # # EVERY COUNT BELOW WAS MEASURED, NOT READ OFF THE TARBALL. The control is an # x86-64 run of the IDENTICAL patched 1.16 tree (meta-oe's # 0001-CheckLib.pm-don-t-execute-the-binary.patch applied) with an EMPTY PATH -- # the arm that reproduces this image's no-compiler condition -- driven exactly as # section 5 drives it. Repeated three times, byte-identical each time: `All tests # successful.`, Files=12, Tests=3, nine files reporting `skipped: Couldn't find # your C compiler`, Result: PASS. # # The compiler-present arms are recorded here because they are what a reader # would otherwise assume section 5 compares against, and they do NOT agree with # it: pristine 1.16 against blib is Result: PASS at 12 files / 42 tests, and the # same tree with meta-oe's patch is Result: FAIL -- the patch turns a probe # binary that runs and fails into a reported success, costing custom-function.t # and flags.t a test each. Neither arm is the target's condition, and neither is # what this suite asserts. # # Five layers: # 1. filesystem facts -- what the port SHIPS, asserted before anything starts # perl, so they hold on a boot where the interpreter cannot load a module. # 2. the test-time dependency this port had to bring with it. # 3. can perl load the module on this boot, classified on the diagnostic -- # where the EXPECTED answer is the compiler die, not a clean import. # 4. the probe's compiler-discovery, both directions, driven directly. # 5. the criterion: prove -r t/ over the installed suite. # The VERSION dir, not the arch dir: Devel::CheckLib and Capture::Tiny are # pure Perl, so cpan installs them to vendor_perl// and NOT to the # vendor_perl//-linux/ that 36-libdbi-perl.test globs for its XS # module. Verified against the built .deb, not assumed: the package ships # ./usr/lib/perl5/vendor_perl/5.38.4/Devel/CheckLib.pm. Same idiom as # 36-libconfig-general-perl.test, the nearest pure-Perl sibling. # perl prints a four-line locale-warning preamble on EVERY invocation on this # image, on stderr. Section 5 captures 2>&1 (it must: a prove that died has said # so on stderr), so without this every prove-* red reports "Setting locale # failed" in the 100-char detail field -- the wrong diagnosis in the one place a # later reader attributes by, and measured: it made six reds unreadable on the # first verifying boot. Same strip section 3 applies to perl's own diagnostic. _dcl_clean() { printf '%s\n' "$1" | awk '/^perl: warning/ { skip=1; next } /^[ \t]/ && skip { next } { skip=0; print }' } V=$(ls -d /usr/lib/perl5/vendor_perl/* 2>/dev/null | head -1) CORE=$(ls -d /usr/lib/perl5/5.* 2>/dev/null | head -1) PT=/usr/lib/libdev-checklib-perl/ptest # ------------------------- 1. what the port SHIPS: filesystem facts ---------- check libdev-checklib-perl/vendor-dir test -n "$V" check libdev-checklib-perl/module-installed test -f "$V/Devel/CheckLib.pm" # The one script the package ships. It is DEPRECATED by its own banner and it # `use Devel::CheckLib`s, so it cannot run on this image -- that it is installed # and executable is still this port's claim, and is all that is asserted. check libdev-checklib-perl/bin-installed test -x /usr/bin/use-devel-checklib # The recipe's do_install:append rewrites that script's shebang to # `#!/usr/bin/env perl` and inserts `use warnings;`. Asserted because a recipe # that stopped doing it would ship a script with the build host's interpreter # path baked in, which nothing else here would notice. check_eq libdev-checklib-perl/bin-shebang "#!/usr/bin/env perl" \ "$(head -1 /usr/bin/use-devel-checklib 2>/dev/null)" # THE INSTALLED SUITE -- the deliverable the ticket's criterion runs on. check libdev-checklib-perl/ptest-dir test -d "$PT/t" check libdev-checklib-perl/ptest-helper test -f "$PT/t/lib/Helper.pm" check libdev-checklib-perl/ptest-header test -f "$PT/t/inc/headerfile.h" check libdev-checklib-perl/ptest-runptest test -f "$PT/run-ptest" # 12 -- the whole of the distribution's t/. This recipe removes none of them # (unlike meta-oe's libdbi-perl, which drops three), so the count is the # tarball's and a recipe that started pruning reds here. The x86-64 control ran # exactly these 12. check_eq libdev-checklib-perl/ptest-count 12 "$(ls "$PT"/t/*.t 2>/dev/null | wc -l | tr -d ' ')" # THE HARNESS THE CRITERION NAMES. `prove` reached this image under libdbi-perl # (perl-misc + perl-module-app-prove). Asserted as ordinary reds, not as a # `require`: their absence is a regression to report, not a reason to skip. check libdev-checklib-perl/prove-installed test -x /usr/bin/prove check libdev-checklib-perl/prove-lib test -f "$CORE/App/Prove.pm" # ------------------ 2. the test-time dependency this port brought ------------ # Seven of the twelve .t files `use Capture::Tiny` at COMPILE time, above the # guard that would otherwise skip them for want of a compiler. Without it those # seven abort with "Can't locate Capture/Tiny.pm" and prove counts seven FAILING # files, not seven skips -- measured on the control: absent -> Result: FAIL. # meta-perl is not one of this build's five layers, so the recipe is carried in # meta-brintos (recipes-devtools/perl/libcapture-tiny-perl_0.48.bb). check libdev-checklib-perl/capture-tiny-installed test -f "$V/Capture/Tiny.pm" # ------------------ 3. can perl LOAD this module on this boot? --------------- # Pure Perl, so BRINTOS-881 -- the -Uusedl -Uuseshrplib arm that stops every XS # module here -- does not apply and no .so is wanted. The EXPECTED outcome is # perl reaching _findcc() and dying on it: that die is the last statement the # file runs, so reaching it proves the module and its whole dependency closure # (Config, Text::ParseWords, File::Spec, File::Temp, Exporter) compiled. # # A clean import is ALSO a pass, and deliberately: it is what this case reports # the day a target toolchain lands, with nothing here to edit. pout=$(perl -e ' eval { require Devel::CheckLib }; my $e = $@; if (!$e) { print "DCL-LOADED-CLEAN\n" } elsif ($e =~ /Couldn.t find your C compiler/) { print "DCL-NO-COMPILER\n" } else { print "DCL-OTHER: $e\n" }' 2>&1); prc=$? # perl's locale warning is a four-line preamble on this image and is NOT the # failure: left in, it fills the 100-char detail field and the red reads # "Setting locale failed" -- the wrong diagnosis in the one field a later reader # attributes by. Same strip as 36-libdbi-perl.test. pdiag=$(printf '%s\n' "$pout" | awk '/^perl: warning/ { skip=1; next } /^[ \t]/ && skip { next } { skip=0; print }') [ -n "$pdiag" ] || pdiag=$pout case "$pdiag" in *DCL-NO-COMPILER*|*DCL-LOADED-CLEAN*) bst_ok libdev-checklib-perl/import-load ;; *) case "$pdiag" in *"Can't locate Devel/CheckLib.pm"*) why="OURS -- libdev-checklib-perl is not on this image, or did not install into \@INC" ;; *"Can't locate"*) why="OURS -- a module in Devel::CheckLib's closure is missing from this image; read the name in the diagnostic" ;; *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 under # BRINTOS-1011: glibc has a dozen spellings for one abort, and missing one # 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" ;; *) 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 the expected compiler die, a clean import, a missing-module diagnostic, nor #154's allocator abort" fi ;; esac bst_notok libdev-checklib-perl/import-load "rc=$prc $(_bst_1line "$pdiag") -- $why" ;; esac # The module compiled WHOLE, not just as far as the die. _findcc() is a run-time # statement near the top of the file, but perl compiles the entire file before # running any of it -- so if the exported subs are defined after the die, every # `sub` below _findcc() compiled too. Measured on the control: check_lib and # assert_lib both defined, $VERSION 1.16, with an empty PATH. api=$(perl -e ' eval { require Devel::CheckLib }; print "API ", (defined &Devel::CheckLib::check_lib ? "check_lib" : "-"), " ", (defined &Devel::CheckLib::assert_lib ? "assert_lib" : "-"), " ", (defined &Devel::CheckLib::check_lib_or_exit ? "check_lib_or_exit" : "-"), " ", ($Devel::CheckLib::VERSION // "undef"), "\n"' 2>&1 | tail -1) check_eq libdev-checklib-perl/api-compiled \ "API check_lib assert_lib check_lib_or_exit 1.16" "$api" # ------------------ 4. the probe's compiler discovery, BOTH directions ------- # This is what section 5 cannot reach on this image, and it is the nearest thing # here to the ticket's "both the success and the failure path of the probe". # _findcc() is the whole of Devel::CheckLib's decision on this arm, and it has # exactly two outcomes; nine skipped files exercise neither. # # FAILURE direction: the image's real $Config{cc} names a cross-compiler that is # not on the rootfs, so the probe must refuse -- and must say so in the wording # its own test suite matches on, which is what makes those nine skips skips # rather than errors. nocc=$(perl -e ' eval "use Devel::CheckLib"; print(($@ =~ /^Couldn.t find your C compiler/) ? "REFUSED-AS-EXPECTED\n" : "UNEXPECTED: $@\n")' 2>&1 | tail -1) check_eq libdev-checklib-perl/findcc-refuses "REFUSED-AS-EXPECTED" "$nocc" # ...and the cc it refused is the cross-compiler, not something else. Asserted # separately so that a perl whose Config lost its cc entirely -- which would ALSO # produce the refusal above -- cannot read as this port working. check_out libdev-checklib-perl/config-cc-is-cross "wasmcc" \ perl -MConfig -e 'print "$Config{cc}\n"' # SUCCESS direction: given a cc it CAN find, _findcc() must accept it and the # import must succeed. $^X stands in for the compiler exactly as the # distribution's own multi-word-compiler.t does it, including the %Config store # override that test installs -- so this asserts on target the one success path # the suite itself still runs here. yescc=$(perl -e ' use Config; BEGIN { *Config::STORE = sub { $_[0]->{$_[1]} = $_[2] } } $Config{cc} = qq{"$^X" -e0}; eval "use Devel::CheckLib"; print(($@) ? "IMPORT-FAILED: $@\n" : "IMPORT-OK\n")' 2>&1 | grep -v 'redefined' | tail -1) check_eq libdev-checklib-perl/findcc-accepts "IMPORT-OK" "$yescc" # ------------------ 4b. WHY THE CRITERION CANNOT PASS ON THIS ARM ------------ # The nine compiler-gated files do not skip here -- they PANIC while skipping, # and this pair is the isolation that says so rather than leaving it inferred # from nine names in a summary. Test::More's two plan forms are driven directly, # one statement apart, on the same interpreter: # # plan tests => N -- the form the three RUNNING files use. Works. # plan skip_all -- the form all nine gated files reach. Panics in # Test2::Hub's teardown: `panic: magic_killbackrefs` at # Test2/Hub.pm line 395. # # That is linux-in-the-browser#93, the magic_killbackrefs use-after-free this # arm's perl already carries a warnings.pm workaround for (see 35-perl.test). # It is a platform defect in perl-on-wasm32 and not a statement about # Devel::CheckLib: on the x86-64 control running the identical patched tree with # an empty PATH, those same nine files skip cleanly and the run is Result: PASS, # 12 files / 3 tests. # # Both cases are written in the direction that is TRUE ON A WORKING PERL, so # they go green by themselves the day #93 lands, with nothing here to edit. ab=$(perl -MTest::More -e 'plan tests => 1; ok(1, "ab");' 2>&1) case "$ab" in *"panic:"*) bst_notok libdev-checklib-perl/plan-tests-no-panic \ "plan tests => N panicked too -- $(_bst_1line "$(_dcl_clean "$ab")")" ;; *"ok 1"*) bst_ok libdev-checklib-perl/plan-tests-no-panic ;; *) bst_notok libdev-checklib-perl/plan-tests-no-panic "$(_bst_1line "$(_dcl_clean "$ab")")" ;; esac sk=$(perl -MTest::More -e 'plan skip_all => "probe";' 2>&1) case "$sk" in *"panic: magic_killbackrefs"*) bst_notok libdev-checklib-perl/skipall-no-panic \ "linux-in-the-browser#93 -- Test::More plan skip_all panics in Test2::Hub teardown: $(_bst_1line "$(_dcl_clean "$sk")")" ;; *"panic:"*) bst_notok libdev-checklib-perl/skipall-no-panic \ "perl panicked on plan skip_all, but NOT with #93's magic_killbackrefs -- $(_bst_1line "$(_dcl_clean "$sk")")" ;; *) bst_ok libdev-checklib-perl/skipall-no-panic ;; esac # ------------------------- 5. THE CRITERION: prove -r t/ --------------------- # Run from a WRITABLE COPY, not in place: the deployed rootfs is read-only at # browser runtime, Helper.pm chdir()s into a File::Temp directory and the suite # is copied for the same reason 36-libdbi-perl.test copies its own. Cheap here # (12 small files) and it removes a whole class of false red. WD=${TMPDIR:-/tmp}/bst-libdev-checklib-perl.$$ mkdir -p "$WD" && cp -r "$PT/t" "$WD/t" 2>/dev/null check libdev-checklib-perl/suite-staged test -f "$WD/t/lib/Helper.pm" # THE HARNESS ITSELF, BEFORE THE CRITERION THAT RIDES ON IT. `prove` resolving # and parsing is not the same as `prove` running: TAP::Harness names its five # collaborators as bare strings and loads them at run time, so poky's # use-scanning RDEPENDS generator never saw them and an image can carry a # /usr/bin/prove that dies on first invocation. That is exactly the state this # image was in until this port added the five roots to IMAGE_INSTALL. Asserted # on a trivial self-contained test so that a broken harness reds HERE, by name, # instead of turning the twelve cases below into an unreadable heap. # A .t file is a PROGRAM prove executes, not a TAP transcript it reads -- so the # smoke file has to be perl that PRINTS the plan, not the plan itself. Written # the wrong way round first, and the guest said so exactly: "Semicolon seems to # be missing at smoke.t line 1 ... syntax error near \"ok \"" -- perl parsing # "1..1" as code. Recorded because the red looked like a broken harness and was # a broken control. cat > "$WD/smoke.t" <<'SMOKE' print "1..1\nok 1 - smoke\n"; SMOKE smoke=$(cd "$WD" && prove smoke.t 2>&1); srn=$? case "$smoke" in *"Result: PASS"*) bst_ok libdev-checklib-perl/prove-smoke ;; *) bst_notok libdev-checklib-perl/prove-smoke \ "rc=$srn prove cannot run a 1-line passing test -- $(_bst_1line "$(_dcl_clean "$smoke")")" ;; esac rm -f "$WD/smoke.t" out=$(cd "$WD" && prove -r t/ 2>&1); rc=$? out=$(_dcl_clean "$out") # `Result: PASS` is prove's own verdict over the whole run and is the ticket's # "reporting zero failures" verbatim; the exit status is asserted beside it # because a harness that died before printing a verdict leaves neither. # The red carries its ATTRIBUTION, not just the transcript. Section 4b has # already established on this same boot which of the two plan forms panics, so # the classifier here keys on the panic being present rather than re-deriving it. case "$out" in *"Result: PASS"*) bst_ok libdev-checklib-perl/prove-result ;; *) if printf '%s\n' "$out" | grep -q '^panic: magic_killbackrefs'; then why="linux-in-the-browser#93 -- the nine compiler-gated files panic in Test2::Hub while calling plan skip_all; on x86-64 with no compiler they skip and the run is Result: PASS" elif printf '%s\n' "$out" | grep -q '^panic: '; then why="a perl interpreter panic that is NOT #93's magic_killbackrefs -- read perl-no-panic's detail" else why="no interpreter panic -- this is a genuine test failure and is THIS PORT'S to answer" fi bst_notok libdev-checklib-perl/prove-result "rc=$rc $(_bst_1line "$out") -- $why" ;; esac check_eq libdev-checklib-perl/prove-rc 0 "$rc" case "$out" in *"All tests successful."*) bst_ok libdev-checklib-perl/prove-all-successful ;; *) bst_notok libdev-checklib-perl/prove-all-successful "$(_bst_1line "$out")" ;; esac # The file count is pinned EXACTLY -- it is a property of the package, already # asserted on the rootfs in section 1, and a harness that silently ran fewer # files is the failure a green verdict cannot show. files=$(printf '%s\n' "$out" | sed -n 's/^Files=\([0-9]*\),.*/\1/p' | tail -1) check_eq libdev-checklib-perl/prove-files 12 "$files" # The assertion count is bounded from BELOW, not pinned. 3 is what the control # ran with no compiler; a target that grows a toolchain would run all 42 and MUST # NOT red for it. Pinning 3 would turn the good news into a failure, in the one # direction nobody re-checks. tests=$(printf '%s\n' "$out" | sed -n 's/^Files=[0-9]*, Tests=\([0-9]*\),.*/\1/p' | tail -1) case "$tests" in ''|*[!0-9]*) bst_notok libdev-checklib-perl/prove-tests "no Tests= total in prove's summary -- $(_bst_1line "$out")" ;; *) if [ "$tests" -ge 3 ]; then bst_ok libdev-checklib-perl/prove-tests else bst_notok libdev-checklib-perl/prove-tests "Tests=$tests, want >= 3 (no-compiler control ran 3)"; fi ;; esac # THE NINE SKIPS, COUNTED AND NAMED. This is the case that stops twelve green # files reading as twelve exercised ones. The count is bounded from ABOVE (<= 9): # nine is what a no-compiler image produces, and a target that grows a toolchain # runs them instead of skipping -- which must not red. More than nine means files # stopped running for some reason that is NOT the compiler, and that is a defect. skips=$(printf '%s\n' "$out" | grep -c "skipped: Couldn't find your C compiler") case "$skips" in ''|*[!0-9]*) bst_notok libdev-checklib-perl/compile-skips "could not count skips in prove's output" ;; *) if [ "$skips" -le 9 ]; then bst_ok libdev-checklib-perl/compile-skips else bst_notok libdev-checklib-perl/compile-skips "$skips files skipped for want of a compiler, want <= 9"; fi ;; esac # The three files that RUN, pinned by name. A skip and a pass are both green to # prove, so without this a regression that turned all twelve into skips would # still report Result: PASS -- the exact false green the criterion is meant to # exclude. These three need no compiler by construction: 00-load.t accepts the # refusal, and the two compiler-name tests substitute $^X. # Matched against THAT FILE'S OWN LINE, not the whole transcript. Written the # obvious way first -- `case "$out" in *"t/$t.t"*"skipped"*)` -- which is a # cross-line glob: nine other files legitimately report "skipped" further down, # so every one of these three reported its file as skipped while prove's # transcript plainly showed `t/00-load.t ... ok`. A control that was wrong in # the direction of a false RED, which is the direction that wastes a rebuild. for t in 00-load bad-single-word-compiler multi-word-compiler; do line=$(printf '%s\n' "$out" | grep -m1 "^t/$t\.t ") case "$line" in '') bst_notok "libdev-checklib-perl/ran-$t" "t/$t.t has no line in prove's transcript -- it did not run at all" ;; *skipped*) bst_notok "libdev-checklib-perl/ran-$t" "t/$t.t skipped; it needs no compiler and must run -- $(_bst_1line "$line")" ;; *ok*) bst_ok "libdev-checklib-perl/ran-$t" ;; *) bst_notok "libdev-checklib-perl/ran-$t" "$(_bst_1line "$line")" ;; esac done # WHICH FILES FAILED, BY NAME. Without this a non-zero prove is one red carrying # 100 characters of transcript, and the file that actually broke is whichever one # happened to print first. prove's Test Summary Report names each one on a line # of the form `t/.t (Wstat: ...)`, so the names are recoverable and belong # in the report rather than in a later reader's guess. bad=$(printf '%s\n' "$out" | sed -n 's/^\(t\/[A-Za-z0-9_.-]*\.t\) *(Wstat:.*/\1/p' | sort | tr '\n' ' ') # The nine compiler-gated files, in sort order. When the failing set is EXACTLY # this, the failure is #93's panic on their shared skip_all path and nothing to # do with the port; anything else is this port's to answer, and the two must not # read alike. GATED="t/analyze-binary.t t/cmdline-LIBS-INC.t t/custom-function.t t/dash-l-libs.t t/exit_with_message.t t/flags.t t/found.t t/headers.t t/not_found.t " if [ -z "$bad" ]; then bst_ok libdev-checklib-perl/prove-failing-files elif [ "$bad" = "$GATED" ]; then bst_notok libdev-checklib-perl/prove-failing-files \ "linux-in-the-browser#93 -- exactly the nine compiler-gated files, all failing on plan skip_all; they skip cleanly on the x86-64 control" else bst_notok libdev-checklib-perl/prove-failing-files \ "NOT the gated nine -- this set is this port's to answer: $bad" fi # A perl INTERPRETER PANIC is not a test failure and must not be reported as one. # perl prints `panic: ` from its own guts when an internal invariant breaks; # on this arm that is a platform fact about perl-on-wasm32, not a statement about # Devel::CheckLib. Named separately so it is attributable instead of being buried # in whichever red quoted the transcript first. pan=$(printf '%s\n' "$out" | grep -m1 '^panic: ') if [ -n "$pan" ]; then case "$pan" in *magic_killbackrefs*) pwhy="linux-in-the-browser#93 -- the magic_killbackrefs use-after-free this arm already carries a warnings.pm workaround for" ;; *) pwhy="an interpreter panic #93 does not cover -- unattributed, and worth a brintos.io issue" ;; esac bst_notok libdev-checklib-perl/perl-no-panic "$(_bst_1line "$pan") -- $pwhy" else bst_ok libdev-checklib-perl/perl-no-panic fi rm -rf "$WD"