. "$BST_LIB" require perl libxml-simple/present # BST-TIMEOUT: 1500 # # Set from a measurement rather than guessed, and it is the biggest number any # Perl suite here declares because this is the first one whose criterion RUNS. # `prove -r t/' starts fifteen perl children (fourteen .t files plus the harness) # and executes 487 assertions; the x86-64 control spends 1.15 s of child CPU and # 11 s of wall clock on it, ~9 s of which is the four `sleep 1' calls three of the # cache tests use to age a file. A perl process costs 46-68x native on this arm # (linux-in-the-browser#117), so the CPU half scales to ~60-80 s and the fifteen # interpreter starts dominate; the sleeps do not scale at all. The payload adds # one more perl. # # MEASURED ON TARGET 2026-08-23, unloaded host: `==BST-SUITE libxml-simple-perl # elapsed=204s bound=1500s cases=70==', of which prove itself was 141 wallclock # seconds. 1500 is ~7x that, which is headroom rather than slack -- the bound has # to hold on the build host's worst day, and this box routinely carries two Epic # runs at once. It stays under the runner's BST_SUITE_TIMEOUT_MAX of 2400. Tighten # it from the ==BST-SUITE line if it drifts, not from this comment. # # libxml-simple-perl 2.25 (POKY meta/recipes-devtools/perl/libxml-simple-perl_2.25.bb # -- NOT meta-oe, which the ticket names; the same correction libxml-perl, # libmodule-build-perl, libtest-fatal-perl, libtest-needs-perl and # libtest-warnings-perl each had to make). Twenty-first Perl module on this image # under BRINTOS-999. # # WHAT THE DISTRIBUTION IS. XML::Simple is the convenience API: XMLin() reads an # XML document into a Perl hash and XMLout() writes one back out. One .pm, pure # Perl, plus a FAQ pod. It does no parsing of its own -- it drives an underlying # parser and reshapes the result. # # THE ONE FACT THAT SHAPES THIS SUITE, AND IT IS THE OPPOSITE OF THE THREE PERL # PORTS BEFORE IT. build_tree() loads XML::SAX FIRST and falls back to XML::Parser # only when XML::SAX will not load. libxml-sax-perl is on this image and # recipes-extended/perl/libxml-sax-perl_%.bbappend registers XML::SAX::PurePerl in # ParserDetails.ini -- pure Perl, no XS, no loader. So the whole parse path CLEARS # the -Uusedl/-Uuseshrplib gap (BRINTOS-881) that leaves libxml-parser-perl, # libdbi-perl and libxml-perl with a blocked criterion, rather than meeting it. # The two .t files that do require XML::Parser open with # `eval { require XML::Parser }; plan skip_all => "no XML::Parser"', so on this # arm they SKIP -- they do not fail. # # SO THE CRITERION RUNS, AND EVERY ASSERTION IN IT PASSES. Measured on target # 2026-08-23: Files=14, Tests=487, and prove's own Test Summary Report carries # `Failed: 0' for every file. That is worth stating up front because the three # sibling suites are the other way round, and a reader arriving here from one of # them will otherwise read this file's reds as "the XS gap again". They are not: # nothing in this port's path is XS. # # WHAT prove STILL SAYS IS `Result: FAIL', AND THE REASON IS NOT THIS PORT'S. # The three files that `plan skip_all' exit non-zero -- 2, 2 and 255 -- because # perl PANICS in their END phase: # # panic: magic_killbackrefs (flags=...) at .../Test2/Hub.pm line 395. # Can't call method "_aborted" on unblessed reference at .../Instance.pm 487. # # Line 395 is `exit($code)' in Test2::Hub::terminate. That is # linux-in-the-browser#93, the backref use-after-free, reached from a second # trigger: #93 was filed against warnings.pm's stash `delete' and its 2026-07-20 # retest reported that trigger as no longer reproducing. This one reproduces # deterministically at this base, and the suite carries the two-line reproducer # rather than the argument -- see skipall-panics in section 4, with # test-more-ok as its negative control. prove-result, prove-rc and # prove-failing-files each say so by name and each flips green when #93 lands. # # THE CONTROL. The same 2.25 sources on x86-64 (perl 5.38.2, XML::SAX 1.02 with # XML::SAX::PurePerl the only registered parser, XML::NamespaceSupport 1.12, # Storable 3.32) run TWICE -- once with XML::Parser 2.47 available and once with # XML/Parser.pm shadowed by a stub that dies with this arm's own diagnostic: # # with XML::Parser Files=14, Tests=503, All tests successful, Result: PASS # without XML::Parser Files=14, Tests=487, All tests successful, Result: PASS # (t/A_XMLParser.t and t/C_External_Entities.t skip_all) # # and the payload's 27 tagged values are IDENTICAL between the two arms except # for the one line written to report it (R2-xmlparser). That is what makes 487 # the right lower bound for this arm and 503 the wrong one. # # THE PAYLOAD IS A GUARD, NOT A RUBBER STAMP, and that was demonstrated rather # than claimed. Six mutations of the shipped XML/Simple.pm and its environment # were run against the control -- unsorted XMLout keys, a ForceArray tag map # forced off, the nsexpand branch disabled, ParserDetails.ini deleted, XML::SAX # blocked, one .t file removed -- and each was caught by the case that should # catch it. The ParserDetails.ini arm is the sharp one: it reddens sax-registry # ALONE and leaves sax-factory green, because ParserFactory falls back to # PurePerl with an empty registry. The test-plan entry for this port carries # the table, under `The controls'. # # THIS SUITE IS MEMORY-HEAVY ON THE GUEST AND THE TICKET GATE HAS TO KNOW IT. # `prove -r t/' forks fifteen perl children in one boot, and guest memory is not # returned on process exit, so the host guest process grows ~2300 MiB/min and # peaks near 7 GiB. brintos_local_verify.py's default --rss-rate 2048 runaway # guard trips on that and quarantines the suite with ZERO cases -- which reads # exactly like a suite that hung at case 0. Run this one with `--rss-rate 0 # --rss-cap 20000 --runner-timeout 2400'. The growth itself is the platform's # own known shape, cited by the tool when it fires (brintos/linux#21, # brintos/hardwarejs#20); nothing here can avoid it short of not running the # criterion. # # Four layers, 70 cases: # 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 value battery -- 27 measurements through one payload process. # 3. the criterion itself: prove -r t/ over the installed suite. # 4. environment facts the criterion rides on, named so they are attributable # -- including the two-line on-boot reproducer for the one red it has. # perl prints a multi-line locale-warning preamble on EVERY invocation on this # image, on stderr (linux-in-the-browser#179: setlocale SUCCEEDS and perl reports # it as failed). Sections 2-4 capture 2>&1 -- they must, since a prove that died # has said so on stderr -- so without this strip every red would report "Setting # locale failed" in the 100-char detail field, which is the wrong diagnosis in # the one place a later reader attributes by. Same strip as 36-libxml-perl.test, # 36-libtest-warnings-perl.test, 36-libtest-fatal-perl.test and # 36-libtest-needs-perl.test. _lxs_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) PT=/usr/lib/libxml-simple-perl/ptest WD=${TMPDIR:-/tmp}/bst-libxml-simple-perl.$$ mkdir -p "$WD" # ------------------------- 1. what the port SHIPS: filesystem facts ---------- # # vendor-dir IS THE FIRST CASE THIS FILE EMITS, AND THAT IS DELIBERATE. The guest # loses a suite's first BST result line between its own printf and the host's read # -- linux-in-the-browser#177, reproduced and filed by BRINTOS-1149, with the # fleet-wide `brintos-selftest' filler-line mitigation held open as BRINTOS-1210 # because it re-signs every image. So one case per suite is going to be dropped # until #177 lands, and the only thing a suite author controls is WHICH one. This # is the right casualty: it is a precondition rather than a measurement, and every # case below would red on its own if it were false. Do not move a real assertion # above it. check libxml-simple/vendor-dir test -n "$V" # THE VERSION DIRECTORY, NOT THE ARCH DIRECTORY. XML::Simple is pure Perl in full, # so cpan installs it to vendor_perl// and nothing to the # vendor_perl//-linux/ tree an XS module lands in. That placement is # the packaging's own statement that this distribution has no compiled half -- # which is why BRINTOS-881 does not reach it. check libxml-simple/module-file test -f "$V/XML/Simple.pm" check libxml-simple/faq-pod test -f "$V/XML/Simple/FAQ.pod" VA=$(ls -d /usr/lib/perl5/vendor_perl/*/*-linux 2>/dev/null | head -1) if [ -z "$VA" ]; then bst_ok libxml-simple/no-arch-half elif [ ! -e "$VA/XML/Simple.pm" ] && [ ! -d "$VA/auto/XML/Simple" ]; then bst_ok libxml-simple/no-arch-half else bst_notok libxml-simple/no-arch-half \ "XML::Simple has a compiled half under $VA, which contradicts a pure-Perl distribution" fi # The -ptest package: the criterion cannot mean anything if the suite is not there. # Fourteen .t files and the five data files they open, by name -- a count alone # would not say which one went missing, and the names alone would not notice one # that was ADDED. check_eq libxml-simple/ptest-count 14 "$(ls "$PT"/t/*.t 2>/dev/null | wc -l | tr -d ' ')" for f in 0_Config 1_XMLin 2_XMLout 3_Storable 4_MemShare 5_MemCopy 6_ObjIntf \ 7_SaxStuff 8_Namespaces 9_Strict A_XMLParser B_Hooks C_External_Entities \ author-pod-syntax; do check "libxml-simple/ptest-$f" test -f "$PT/t/$f.t" done check libxml-simple/ptest-data-test1 test -f "$PT/t/test1.xml" check libxml-simple/ptest-data-test2 test -f "$PT/t/subdir/test2.xml" check libxml-simple/ptest-data-xmlin test -f "$PT/t/1_XMLin.xml" check libxml-simple/ptest-data-srt test -f "$PT/t/srt.xml" check libxml-simple/ptest-data-desertnet test -f "$PT/t/desertnet.src" check libxml-simple/ptest-data-taglib test -f "$PT/t/lib/TagsToUpper.pm" # ------------------------- 2. the value battery ------------------------------ # ONE payload process for all 27 values -- perl costs seconds on this arm, so # thirty `perl -e' children would cost more than the criterion itself. POUT=$WD/payload.out perl "$BST_DATA/libxml-simple-perl-selftest.pl" > "$POUT" 2>&1 prc=$? _lxs_clean "$(cat "$POUT")" > "$WD/payload.clean" # THE PAYLOAD'S OWN LIVENESS, BEFORE ANY VALUE READ OUT OF IT. A payload that # died at line 1 leaves an empty file, and every check_eq below would then compare # "" against its expected value and red 27 times with no statement of why. This # case is the why. R99-END is printed by the payload's last statement, so it is # absent exactly when the run did not finish. if grep -q '^R99-END$' "$WD/payload.clean"; then bst_ok libxml-simple/payload-complete else bst_notok libxml-simple/payload-complete \ "rc=$prc payload did not reach its end marker -- $(_bst_1line "$(cat "$WD/payload.clean")")"; fi _r() { sed -n "s/^$1 //p" "$WD/payload.clean" | head -1; } check_eq libxml-simple/load yes "$(_r R1-load)" check_eq libxml-simple/version 2.25 "$(_r R1-version)" # WHICH PARSER ANSWERED. This is the fact the whole port turns on, so it is # measured from three independent angles: XML::SAX loads, the registry lists # PurePerl, and the factory hands PurePerl back. A registry that had gone empty # would still leave the factory answering PurePerl -- ParserFactory ends # `return "XML::SAX::PurePerl"; # backup plan!' -- so the factory case alone # would not notice ParserDetails.ini going missing, which is the exact half- # working state libxml-sax-perl_%.bbappend exists to prevent. check_eq libxml-simple/xmlsax yes "$(_r R2-xmlsax)" check_eq libxml-simple/sax-registry "XML::SAX::PurePerl" "$(_r R2-parsers)" check_eq libxml-simple/sax-factory "XML::SAX::PurePerl" "$(_r R2-factory)" check_eq libxml-simple/nssupport yes "$(_r R2-nssupport)" check_eq libxml-simple/storable yes "$(_r R2-storable)" # XML::Parser is REPORTED, NOT REQUIRED, and the case is written to say which # state it found rather than to fail in either. It is XS against expat and this # arm's perl is -Uusedl -Uuseshrplib, so it cannot load (BRINTOS-881, measured by # 36-libxml-parser-perl.test) -- and NOTHING in this port needs it. A `no' here is # the expected state and costs the criterion 16 of its 503 assertions, which is # why prove-tests below is bounded at 487. xp=$(_r R2-xmlparser) case "$xp" in yes) bst_ok libxml-simple/xmlparser-state ;; no) bst_skip libxml-simple/xmlparser-state \ "XML::Parser does not load on this arm (BRINTOS-881: XS, and perl is -Uusedl -Uuseshrplib); XML::Simple does not need it -- it parses through XML::SAX::PurePerl, and t/A_XMLParser.t + t/C_External_Entities.t skip_all" ;; *) bst_notok libxml-simple/xmlparser-state "payload reported neither yes nor no: '$xp'" ;; esac # XMLin: the structure, and four separate readings of it. The whole dump is the # strongest single assertion; the three narrow ones are what make a red readable, # because a 100-character detail field cannot show which key of the dump moved. check_eq libxml-simple/xmlin-err none "$(_r R3-in-err)" check_eq libxml-simple/xmlin-struct \ "{'server' => {'gobi' => {'address' => ['10.0.0.102'],'osname' => 'irix','osversion' => '6.5'},'kalahari' => {'address' => ['10.0.0.103','10.0.1.103'],'osname' => 'linux','osversion' => '2.0.34'},'sahara' => {'address' => ['10.0.0.101','10.0.1.101'],'osname' => 'solaris','osversion' => '2.6'}}}" \ "$(_r R3-in)" check_eq libxml-simple/xmlin-keyattr "gobi,kalahari,sahara" "$(_r R3-servers)" check_eq libxml-simple/xmlin-forcearray "10.0.0.101|10.0.1.101" "$(_r R3-sahara-addr)" check_eq libxml-simple/xmlin-attr irix "$(_r R3-gobi-osname)" # The SAME document with no options at all -- a different shape, and the one a # consumer meets by default: without ForceArray the single-address server # collapses to a scalar while the two-address ones stay arrays. A port that got # the folding wrong in only that direction would leave the case above green. check_eq libxml-simple/xmlin-default \ "{'server' => {'gobi' => {'address' => '10.0.0.102','osname' => 'irix','osversion' => '6.5'},'kalahari' => {'address' => ['10.0.0.103','10.0.1.103'],'osname' => 'linux','osversion' => '2.0.34'},'sahara' => {'address' => ['10.0.0.101','10.0.1.101'],'osname' => 'solaris','osversion' => '2.6'}}}" \ "$(_r R3-plain)" # XMLout, and the round trip the ticket names. XMLout's element order is XML:: # Simple's own sort, so the string is deterministic and can be pinned exactly. check_eq libxml-simple/xmlout-err none "$(_r R4-out-err)" check_eq libxml-simple/xmlout \ "
10.0.0.102
10.0.0.103
10.0.1.103
10.0.0.101
10.0.1.101
" \ "$(_r R4-out)" check_eq libxml-simple/roundtrip same "$(_r R4-roundtrip)" check_eq libxml-simple/idempotent same "$(_r R4-idempotent)" # The options. Each is a documented behaviour a consumer relies on, and each is a # separate code path in Simple.pm rather than a restatement of the one above. check_eq libxml-simple/opt-forcearray "{'x' => ['1']}" "$(_r R5-forcearray)" check_eq libxml-simple/opt-noattr " 1 two " "$(_r R5-noattr)" check_eq libxml-simple/opt-attrs "" "$(_r R5-attrs)" check_eq libxml-simple/opt-contentkey "{'text' => 'text','x' => '1'}" "$(_r R5-contentkey)" check_eq libxml-simple/opt-xmldecl " " "$(_r R5-xmldecl)" check_eq libxml-simple/opt-keyattr "{'i' => {'a' => {'v' => '1'},'b' => {'v' => '2'}}}" "$(_r R5-keyattr)" check_eq libxml-simple/opt-nested "{'a' => {'b' => {'c' => 'deep'}}}" "$(_r R5-nested)" check_eq libxml-simple/opt-empty "{'e' => {}}" "$(_r R5-empty)" # An unknown option must DIE. A port that silently accepted one would be quietly # wrong rather than loudly so, and no round-trip case would notice. check_eq libxml-simple/opt-unknown-dies died "$(_r R5-badopt)" # The FILENAME arm of XMLin -- a different code path from the string arm above, # and it is driven against the documents the -ptest package installs. A red here # with the string cases green is a PACKAGING finding, not a parse finding. check_eq libxml-simple/xmlin-file "{'location' => 't/test1.xml'}" "$(_r R6-test1)" check_eq libxml-simple/xmlin-file-shipped \ "{'server' => {'gobi' => {'address' => '10.0.0.102','osname' => 'irix','osversion' => '6.5'},'kalahari' => {'address' => ['10.0.0.103','10.0.1.103'],'osname' => 'linux','osversion' => '2.0.34'},'sahara' => {'address' => ['10.0.0.101','10.0.1.101'],'osname' => 'solaris','osversion' => '2.6'}}}" \ "$(_r R6-desertnet)" check_eq libxml-simple/xmlin-file-complex "global,pubpath" "$(_r R6-srt-keys)" # nsexpand is the SAX-ONLY arm: with XML::Parser preferred XML::Simple warns # "'nsexpand' option requires XML::SAX" and does nothing, and through XML::SAX it # rewrites each name to {uri}local. It is therefore the sharpest single check # that the SAX path -- not a fallback -- is the live one on this arm. check_eq libxml-simple/nsexpand \ "{'{http://example.org/a}x' => '1','{http://www.w3.org/2000/xmlns/}a' => 'http://example.org/a'}" \ "$(_r R7-nsexpand)" # ------------------------- 3. THE CRITERION: prove -r t/ --------------------- # Run from a WRITABLE COPY, not in place. The deployed rootfs is read-only at # browser runtime and three of the .t files (3_Storable, 4_MemShare, 5_MemCopy) # CREATE a file in the directory holding t/ and then age it -- and each of them # degrades to `plan skip_all => "can't create test file"' when it cannot, which # would silently take 38 assertions out of the total this suite bounds. CWD is # the directory that HOLDS t/, which is how run-ptest invokes it too. PW=$WD/ptest mkdir -p "$PW" && cp -r "$PT/t" "$PW/t" 2>/dev/null check libxml-simple/suite-staged test -f "$PW/t/1_XMLin.t" # 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 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. Asserted on a trivial self-contained test so a broken harness # reds HERE, by name, instead of turning the criterion into an unreadable heap. A # .t file is a PROGRAM prove executes, not a TAP transcript it reads -- so the # smoke file is perl that PRINTS the plan, not the plan itself. cat > "$PW/smoke.t" <<'SMOKE' print "1..1\nok 1 - smoke\n"; SMOKE smoke=$(cd "$PW" && prove smoke.t 2>&1); srn=$? smoke=$(_lxs_clean "$smoke") case "$smoke" in *"Result: PASS"*) bst_ok libxml-simple/prove-smoke ;; *) bst_notok libxml-simple/prove-smoke \ "rc=$srn prove cannot run a 1-line passing test -- $(_bst_1line "$smoke")" ;; esac rm -f "$PW/smoke.t" cout=$(cd "$PW" && prove -r t/ 2>&1); crc=$? cout=$(_lxs_clean "$cout") # `Result: PASS' is prove's own verdict over the whole run and is the ticket's # "reporting zero failures" verbatim. Unlike the three Perl ports before it, this # one is EXPECTED GREEN -- so a red here is THIS PORT'S unless its detail says # otherwise, and the branches below exist to say which. case "$cout" in *"Result: PASS"*) bst_ok libxml-simple/prove-result ;; *) if printf '%s\n' "$cout" | grep -qE "locate loadable object|DynaLoader"; then why="an XS-load diagnostic, which this port's path does NOT contain -- XML::Simple parses through XML::SAX::PurePerl and needs no compiled half; read prove-failing-files, because something OTHER than XML::Parser reached for a loader" elif printf '%s\n' "$cout" | grep -q 'panic: magic_killbackrefs'; then why="linux-in-the-browser#93 -- the backref use-after-free, fired from Test2::Hub::terminate's exit(); it is NOT this port's and NOT a test failure. Read skipall-panics, which reproduces it on this boot in two lines with no XML::Simple involved. Every .t that RUNS is green: read prove-tests and prove-no-failed-subtests" elif printf '%s\n' "$cout" | grep -q 'panic: '; then why="a perl interpreter panic that is NOT the #93 backref one -- read perl-no-panic and file it" elif printf '%s\n' "$cout" | grep -qi "can't create test file\|Permission denied"; then why="the staged tree was not writable, so the cache tests could not run -- this is the HARNESS, not the port; read suite-staged" else why="no XS-load diagnostic, no panic and no write refusal -- this is a genuine test failure and is THIS PORT'S to answer" fi # The attribution leads and the transcript follows. prove's first line of # stdout is 0_Config.t's `# Package / Version' diag, so a detail built the # other way round spends its first 100 characters -- the part a truncating # reader sees -- saying nothing at all. bst_notok libxml-simple/prove-result "$why -- rc=$crc $(_bst_1line "$cout")" ;; esac # prove's EXIT STATUS, separately from its verdict, because they can disagree and # on this arm they do: a file that skip_alls exits non-zero here (see # skipall-panics), so prove ends 1 with every assertion it ran passing. if [ "$crc" = 0 ]; then bst_ok libxml-simple/prove-rc elif printf '%s\n' "$cout" | grep -q 'panic: magic_killbackrefs'; then bst_notok libxml-simple/prove-rc \ "rc=$crc with linux-in-the-browser#93 in the transcript -- the three skip_all files exit 2/255 from the panic in their END phase, after prove has already recorded them as skipped; no assertion failed" else bst_notok libxml-simple/prove-rc "rc=$crc, want 0, and no #93 panic in the transcript -- THIS PORT'S" fi # 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 verdict cannot show. files=$(printf '%s\n' "$cout" | sed -n 's/^Files=\([0-9]*\),.*/\1/p' | tail -1) check_eq libxml-simple/prove-files 14 "$files" # The assertion total, bounded from below at the x86-64 control's 487 -- the arm # WITHOUT XML::Parser, which is this arm. Bounded rather than pinned so a later # 2.26 with an extra case does not read as a regression; 503 would be the wrong # bound here and would red on a perfectly correct run. tests=$(printf '%s\n' "$cout" | sed -n 's/^Files=[0-9]*, Tests=\([0-9]*\),.*/\1/p' | tail -1) case "$tests" in ''|*[!0-9]*) bst_notok libxml-simple/prove-tests \ "no Tests= total in prove's summary -- $(_bst_1line "$cout")" ;; *) if [ "$tests" -ge 487 ]; then bst_ok libxml-simple/prove-tests else bst_notok libxml-simple/prove-tests \ "Tests=$tests, want >= 487 (the x86-64 control ran 487 with XML::Parser blocked and 503 with it available); a total between 449 and 486 means the three cache tests skipped, which is a writable-tree problem and not a port problem"; fi ;; esac # 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 # printed first. bad=$(printf '%s\n' "$cout" | sed -n 's/^\(t\/[A-Za-z0-9_.-]*\.t\) *(Wstat:.*/\1/p' | LC_ALL=C sort | tr '\n' ' ') # How many of them ran ZERO assertions and failed none -- i.e. exited non-zero # without any test having gone wrong. That is the whole difference between the # #93 exit-status artefact and a real failure, and it is read off prove's own # Test Summary Report rather than inferred from the count. nbad=$(printf '%s\n' "$cout" | sed -n 's/^t\/[A-Za-z0-9_.-]*\.t *(Wstat:.*/x/p' | wc -l | tr -d ' ') nzero=$(printf '%s\n' "$cout" | sed -n 's/^t\/[A-Za-z0-9_.-]*\.t *(Wstat:.*Tests: 0 Failed: 0).*/x/p' | wc -l | tr -d ' ') if [ -z "$bad" ]; then bst_ok libxml-simple/prove-failing-files elif [ "$nbad" = "$nzero" ] && printf '%s\n' "$cout" | grep -q 'panic: magic_killbackrefs'; then bst_notok libxml-simple/prove-failing-files \ "all $nbad are Tests: 0 Failed: 0 -- they are the skip_all files exiting non-zero on linux-in-the-browser#93, not files that failed a test: $bad" else bst_notok libxml-simple/prove-failing-files \ "$nbad file(s) failed and $nzero of them ran zero tests -- at least one ran assertions and is THIS PORT'S: $bad" fi # WHICH FILES SKIPPED, BY NAME, AND WHETHER THE SET IS THE EXPECTED ONE. This is # the case that keeps a green run honest: three files are expected to skip on this # arm -- A_XMLParser and C_External_Entities for the XS gap, author-pod-syntax # because it is an author test -- and a FOURTH skip would silently remove # assertions from a run still reporting `Result: PASS'. The prove-tests bound # would catch a large loss; this names a small one. # LC_ALL=C on the sort, and it is not a tidiness point. The expected set below is # a literal string, so the ORDER is part of the assertion -- and glibc's default # collation sorts case-insensitively (author-pod-syntax before A_XMLParser) while # busybox on the target sorts by byte. The two disagree on exactly these three # names, so without the pin this case reds on whichever host it was not written # on. It reddened on the x86-64 rehearsal first, which is how it was found: a new # guard's first red is more often the guard than the code. skipped=$(printf '%s\n' "$cout" | sed -n 's/^\(t\/[A-Za-z0-9_.-]*\.t\) *\.* *skipped:.*/\1/p' | LC_ALL=C sort | tr '\n' ' ') exp_skip="t/A_XMLParser.t t/C_External_Entities.t t/author-pod-syntax.t " if [ "$skipped" = "$exp_skip" ]; then bst_ok libxml-simple/prove-skipped elif [ "$skipped" = "t/author-pod-syntax.t " ]; then bst_notok libxml-simple/prove-skipped \ "only the author test skipped, so XML::Parser LOADED on this arm -- that is a change in the interpreter (BRINTOS-881 cleared?), not a defect here; the expected total then rises to 503 and this bound should be revisited" else bst_notok libxml-simple/prove-skipped "skipped set is '$skipped', expected '$exp_skip'" fi # HOW MANY ASSERTIONS FAILED, as distinct from how the run ENDED. Named separately # because prove reports a non-zero exit for a bad plan, a died file and a failed # assertion alike, and those are three different findings. fail=$(printf '%s\n' "$cout" | sed -n 's/.*Failed \([0-9]*\)\/[0-9]* subtests.*/\1/p' | tr '\n' ' ') if [ -z "$fail" ]; then bst_ok libxml-simple/prove-no-failed-subtests else bst_notok libxml-simple/prove-no-failed-subtests \ "prove reported failed subtests ($fail) -- these are genuine assertion failures and are THIS PORT'S"; fi # ------------------------- 4. environment facts ------------------------------ # THE CRITERION'S ONE RED, REPRODUCED ON THIS BOOT IN TWO LINES WITH NO # XML::Simple IN THEM. `prove -r t/' ends 1 because the three files that # `plan skip_all' exit non-zero, and they do that because perl PANICS in their # END phase -- linux-in-the-browser#93, the backref use-after-free, reached here # from Test2::Hub::terminate's exit() rather than from warnings.pm's stash # delete. Attribution by argument would be a guess; these two probes measure it, # on this boot, in fresh interpreters: # # - test-more-ok must be GREEN. Test::More itself is fine, and if it were not # the whole battery above would already have said so. This is the negative # control that makes the next case mean "skip_all", not "Test::More". # - skipall-panics is EXPECTED RED and is written to flip green the day #93 # lands -- which is the notification that this port's criterion has become # an unqualified PASS and that prove-result / prove-rc / prove-failing-files # should go green with it. # # Measured 2026-08-23 on this image: skip_all panics 5/5 runs, `use Test::More # tests => 1; ok(1)' is clean, a SKIP: block with skip() is clean, and a bare # `1..0 # SKIP' with no Test::More is clean. The same file on x86-64 perl 5.38.2 # exits 0. #93's own 2026-07-20 retest reported the warnings.pm trigger as no # longer reproducing; this trigger does, at this base. tmo=$(perl -e 'use Test::More tests => 1; ok(1, "control");' 2>&1); tmrc=$? tmo=$(_lxs_clean "$tmo") case "$tmo" in *"ok 1"*) if [ "$tmrc" = 0 ]; then bst_ok libxml-simple/test-more-ok else bst_notok libxml-simple/test-more-ok "printed ok 1 but exited $tmrc -- $(_bst_1line "$tmo")"; fi ;; *) bst_notok libxml-simple/test-more-ok \ "rc=$tmrc Test::More cannot run a single passing assertion, so nothing below attributes anything -- $(_bst_1line "$tmo")" ;; esac sao=$(perl -e 'use Test::More; plan skip_all => "control";' 2>&1); sarc=$? sao=$(_lxs_clean "$sao") if [ "$sarc" = 0 ] && ! printf '%s\n' "$sao" | grep -q 'panic:'; then bst_ok libxml-simple/skipall-panics elif printf '%s\n' "$sao" | grep -q 'panic: magic_killbackrefs'; then bst_notok libxml-simple/skipall-panics \ "linux-in-the-browser#93 -- \`use Test::More; plan skip_all' panics magic_killbackrefs and exits $sarc; this is what makes prove-result red and it involves no XML::Simple. Flips green when #93 lands" else bst_notok libxml-simple/skipall-panics \ "rc=$sarc skip_all failed WITHOUT the #93 backref panic -- a different defect: $(_bst_1line "$sao")" fi # A perl INTERPRETER PANIC is not a test failure and must not be reported as one. # Named separately so it is attributable instead of being buried in whichever red # quoted the transcript first. pan=$(printf '%s\n' "$cout" | grep -m1 'panic: ') if [ -z "$pan" ]; then bst_ok libxml-simple/perl-no-panic elif printf '%s\n' "$pan" | grep -q 'magic_killbackrefs'; then bst_notok libxml-simple/perl-no-panic \ "linux-in-the-browser#93 (already open) -- $(_bst_1line "$pan"); skipall-panics reproduces it in two lines on this boot, so it is attributed rather than merely observed" else bst_notok libxml-simple/perl-no-panic \ "an interpreter panic that is NOT the #93 backref one -- unattributed, and worth a brintos.io issue: $(_bst_1line "$pan")" fi # THE LOCALE PREAMBLE, REPORTED RATHER THAN ONLY STRIPPED. _lxs_clean removes it # from every detail field in this file, which is what every brintOS Perl selftest # does -- and that private, repeated workaround is precisely why nothing tracked # the defect until BRINTOS-1323 filed linux-in-the-browser#179. This case makes # the strip visible: it reports whether the preamble is still being emitted, so # the day #179 lands the suite says so instead of silently continuing to filter it. lp=$(perl -e 'print "X\n"' 2>&1) case "$lp" in *"Setting locale failed"*) bst_notok libxml-simple/no-locale-preamble \ "linux-in-the-browser#179 -- perl prints a locale-warning preamble on stderr on EVERY invocation although setlocale() succeeds; every Perl suite here strips it by hand" ;; *) bst_ok libxml-simple/no-locale-preamble ;; esac rm -rf "$WD"