. "$BST_LIB" require perl geoip-perl/present # BST-TIMEOUT: 1800 # 7 .t files, each compiling IP.pm's 6279 lines twice over # # (the file, then its eval'd pure-Perl half) plus the Test2 # # tree; the x86-64 control runs in 0.4 s CPU and this arm # # has been measured at 46-117x native elsewhere. # Geo::IP 1.51 (meta-networking recipes-support/geoip/geoip-perl_1.51.bb) -- the # Perl binding to MaxMind's legacy GeoIP C library, resolving an IP address to a # country, city, organisation, domain or netspeed against a local database file. # Lookups are offline reads of a bundled .dat, so nothing here needs a network. # # THIS SUITE'S PASS CRITERION IS THE DISTRIBUTION'S OWN t/, NOT A HAND-WRITTEN # BATTERY -- the ticket's wording. The port installs it (geoip-perl-ptest, from # the recipe's own do_install_ptest, enabled for this recipe alone) together with # the eleven small .dat fixtures the tests read, and section 4 runs `prove -r t/` # over it. # # WHY THE CRITERION RUNS AT ALL HERE, WHERE THE TWO XS PERL PORTS BEFORE IT COULD # NOT REACH THEIR FIRST ASSERTION. A third-party XS module cannot be loaded on # this arm -- perl is -Uusedl -Uuseshrplib, so the compile produces # auto/Geo/IP/IP.a and there is no DynaLoader to load an .so that was never built # (BRINTOS-881). libdbi-perl and libio-pty-perl stopped exactly there. Geo::IP # does not: its bootstrap sits inside an eval, and when that fails IP.pm # eval-compiles a COMPLETE PURE-PERL implementation of the same API. The module # loads, the lookups answer, and the distribution's own tests execute -- through # the pure-Perl path, which is a fact section 3 states by name rather than # letting a green run imply. # # AND WHY IT DOES NOT RUN CLEAN. Geo::IP's pure-Perl half defines its ENTIRE IPv6 # API inside a nested `eval <<'__IPV6__'` whose first statement is `use Socket` # (IP.pm:5768-5769), and `use Socket` panics this perl -- # linux-in-the-browser#93, the backref use-after-free that libconvert-asn1-perl # already meets on this image through Convert::ASN1::IO. So every *_v6 method is # undefined, t/country_v6.t dies outright and t/org.t loses its two _v6 subtests. # Two files of seven, and nothing else. # # THE TWO CONTROLS, BOTH ON x86-64 AND BOTH RUN BEFORE THIS EVER BOOTED. The same # 1.51 tree, installed to a prefix and resolved from @INC with no blib, driven # exactly as section 4 drives it: # # normal perl (XS loaded) 7 files, 24 tests, PASS # reduced to this arm: no IP.so, DynaLoader and Socket # both unloadable by a hook proved live first 7 files, 20 tests, FAIL # (country_v6.t, org.t) # # The second is what this image produces, file for file. Section 4's three # whole-run verdicts are therefore written in the FIRST control's direction and # are red today; they self-flip when #93 lands, and prove-failed-set is the case # that notices if anything else ever joins those two. # # Six 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 XS-load facts, RED today and written so they flip green by themselves # when BRINTOS-881 lands. # 3. can perl load Geo::IP on this boot, classified on the diagnostic, and # which of the two implementations answered. # 4. the criterion: prove -r t/ over the installed suite. # 4b. the two #93 facts named directly, so the criterion's reds have a stated # cause on the suite and not only in a design doc. # 5. direct lookups outside the harness, so a `prove` that somehow reported PASS # over zero real work still has to answer a question. # perl's locale warning is a four-line preamble on this image and is NOT a # finding. Left in, it fills the 100-char detail field of every red below and the # reader attributes the failure to "Setting locale failed" -- the wrong diagnosis # in the one field they have. Measured on the first boot of this suite, where all # five criterion reds carried it and none carried its own cause. _nowarn() { printf '%s\n' "$1" | awk '/^perl: warning/ { skip=1; next } /^[ \t]/ && skip { next } { skip=0; print }' } # The guest console drops a suite's FIRST BST result line before the host parser # sees it -- brintos/linux#96. The harness computes its pass count as # total-minus-failures, so THE DROPPED LINE IS COUNTED AS A PASS, and a suite # whose first case is a genuine `not ok` loses the one line that mattered. So the # first line out of this file asserts something already true. Measured on this # suite's first two verify runs, where geoip-perl/vendor-arch-dir was the line # lost each time. Remove this case when #96 is fixed. bst_ok geoip-perl/suite-canary V=$(ls -d /usr/lib/perl5/vendor_perl/*/*-linux 2>/dev/null | head -1) CORE=$(ls -d /usr/lib/perl5/5.* 2>/dev/null | head -1) PT=/usr/lib/geoip-perl/ptest # ------------------------- 1. what the port SHIPS: filesystem facts ---------- check geoip-perl/vendor-arch-dir test -n "$V" check geoip-perl/module-installed test -f "$V/Geo/IP.pm" check geoip-perl/record-installed test -f "$V/Geo/IP/Record.pm" check geoip-perl/record-pod test -f "$V/Geo/IP/Record.pod" # The XS artifact the compile produced, and the whole of the compiled half on a # -Uusedl perl: an archive nothing can load. Asserted so that a build which # silently stopped compiling the XS is a red rather than an invisible change -- # it is also the only on-target evidence that the geoip headers and libGeoIP.a # were visible in the wasm32 sysroot, which is what the ticket asked to check. check geoip-perl/xs-archive test -f "$V/auto/Geo/IP/IP.a" # extralibs.ld records the libraries the extension was linked against. Upstream's # EXTRA_CPANFLAGS puts -L${STAGING_LIBDIR} in it verbatim, which ships a build # path in the image (do_package_qa: `contains reference to TMPDIR [buildpaths]`); # the bbappend reduces it to the bare -lGeoIP. Both halves are pinned: the path # must be gone AND the link fact must survive, because a do_install:append that # emptied the file would satisfy the first alone. check geoip-perl/extralibs-present test -f "$V/auto/Geo/IP/extralibs.ld" if grep -q 'recipe-sysroot\|/tmp-glibc/\|/tmp-musl/' "$V/auto/Geo/IP/extralibs.ld" 2>/dev/null; then bst_notok geoip-perl/extralibs-no-buildpath "$(_bst_1line "$(cat "$V/auto/Geo/IP/extralibs.ld")")" else bst_ok geoip-perl/extralibs-no-buildpath fi check_out geoip-perl/extralibs-lib "-lGeoIP" cat "$V/auto/Geo/IP/extralibs.ld" # THE INSTALLED SUITE -- the deliverable the ticket's criterion runs on. Seven # .t files and eleven .dat fixtures; the tests open the fixtures by RELATIVE path # ('t/data/GeoIP.dat'), which is why the criterion is run from $PT and why the # fixtures are asserted here rather than assumed. check geoip-perl/ptest-dir test -d "$PT/t" check geoip-perl/ptest-data-dir test -d "$PT/t/data" check geoip-perl/ptest-runptest test -f "$PT/run-ptest" check_eq geoip-perl/ptest-count 7 "$(ls "$PT"/t/*.t 2>/dev/null | wc -l | tr -d ' ')" check_eq geoip-perl/ptest-data-count 11 "$(ls "$PT"/t/data/*.dat 2>/dev/null | wc -l | tr -d ' ')" # The four fixtures the direct probes in section 5 read, pinned individually so a # do_install_ptest that stopped copying t/data reds by name and not only by count. check geoip-perl/fixture-country test -f "$PT/t/data/GeoIP.dat" check geoip-perl/fixture-city test -f "$PT/t/data/GeoIPCity.dat" check geoip-perl/fixture-v6 test -f "$PT/t/data/GeoIPv6.dat" check geoip-perl/fixture-org test -f "$PT/t/data/GeoIPOrg.dat" # THE HARNESS THE CRITERION NAMES. `prove` reaches this image because # libdbi-perl (BRINTOS-1317) put perl-misc and perl-module-app-prove in # IMAGE_INSTALL. Asserted as ordinary reds rather than as a `require`, because # their absence is a regression to report, not a reason to skip the suite. check geoip-perl/prove-installed test -x /usr/bin/prove check geoip-perl/prove-lib test -f "$CORE/App/Prove.pm" # ------------------ 2. the XS-load facts: red today, self-flipping ----------- # 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 (--disable-shared is distro policy), so there is no forge issue # to chase. Same treatment as 36-libdbi-perl.test and 36-libxml-parser-perl.test. # # Both are written in the direction that is TRUE ON A NORMAL PERL, so they are red # here and go green by themselves the day 881 lands, with nothing to edit. check geoip-perl/xs-shared-object test -f "$V/auto/Geo/IP/IP.so" check geoip-perl/dynaloader-present test -f "$CORE/DynaLoader.pm" # ------------------ 3. can perl LOAD this module on this boot at all? -------- # Unlike every XS Perl port before it this is EXPECTED TO PASS, because the load # does not need the XS half. It is still classified on the diagnostic, because the # three ways it can fail here are not the same finding: linux-in-the-browser#154's # allocator abort while compiling any module, BRINTOS-881's missing loadable # object (which would mean the eval-guarded bootstrap stopped being guarded), and # a missing dependency split, which would be OURS. pout=$(perl -MGeo::IP -e 'print "GEOIP-OK ", Geo::IP->api, "\n"' 2>&1); prc=$? case "$pout" in *GEOIP-OK*) bst_ok geoip-perl/import-load ;; *) # 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. pdiag=$(printf '%s\n' "$pout" | awk '/^perl: warning/ { skip=1; next } /^[ \t]/ && skip { next } { skip=0; print }') [ -n "$pdiag" ] || pdiag=$pout case "$pdiag" in *"Can't locate loadable object for module Geo::IP"*|*"Can't locate DynaLoader"*) why="BRINTOS-881 reaching a path it should not -- IP.pm guards its bootstrap with eval, so a DynaLoader failure must NOT be fatal; if this fires the module changed" ;; *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, not by guesswork: 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" ;; *"Can't locate Geo/IP.pm"*) why="OURS -- geoip-perl is not on this image, or did not install into @INC" ;; *"Can't locate"*) why="OURS -- a module in Geo::IP's pure-Perl closure is missing from this image; read the name in the diagnostic" ;; *) 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 -- none of #154's allocator abort, a DynaLoader diagnostic, or a missing-module diagnostic" fi ;; esac bst_notok geoip-perl/import-load "rc=$prc $(_bst_1line "$pdiag") -- $why" # NAMED, not silent. The ticket's criterion IS section 4, and a suite that # merely stopped here would report twenty-odd greens and read as a pass. bst_notok geoip-perl/prove-unverified \ "not run: perl cannot load Geo::IP ($why) -- \`prove -r t/\` over the 7 installed test files (24 assertions on the x86-64 control) is unverified on wasm32-hwjs" exit 0 ;; esac # WHICH IMPLEMENTATION ANSWERED. `CAPI` is what a normal perl reports and is what # this arm will report once BRINTOS-881 lands; `PurePerl` is the fallback that # makes the rest of this suite possible today. Written in the normal-perl # direction like the two above, so it self-flips, and it is the case that stops # section 4's greens from being read as evidence that the XS half works. api=$(printf '%s\n' "$pout" | sed -n 's/^GEOIP-OK //p' | tail -1) check_eq geoip-perl/api-mode CAPI "$api" # ------------------------- 4. THE CRITERION: prove -r t/ --------------------- # Run IN PLACE from $PT, not from a copy. The tests only READ their fixtures -- # measured on the x86-64 control with the whole tree chmod'ed a-w: 7 files, 24 # tests, Result PASS, identical to the writable run -- so the read-only rootfs is # not an obstacle here and staging would only add a failure mode. The `cd` is # inside a command substitution rather than at the top level because a failed # top-level chdir is fatal to ash and would cost the rest of this file # (brintos/linux#73). out=$( cd "$PT" && prove -r t/ 2>&1 ); rc=$? # THE THREE WHOLE-RUN VERDICTS ARE WRITTEN IN THE NORMAL-PERL DIRECTION AND ARE # RED TODAY, held by linux-in-the-browser#93 and not by anything this port did. # Geo::IP's pure-Perl half defines its entire IPv6 API inside a nested # `eval <<'__IPV6__'` whose first statement is `use Socket` (IP.pm:5768-5769), # and `use Socket` PANICS this perl: # # perl -e 'use Socket' # panic: del_backref, svp=0 # # Socket.pm 2.036 deletes two CVs from its own stash once its XS boot has taken # (lines 890-892) and the backref walk that frees them reads a freed referrer -- # the use-after-free already tracked as linux-in-the-browser#93 and already # documented on this image by libconvert-asn1-perl, which meets it through # Convert::ASN1::IO. Here it costs the eval, so every *_v6 method is undefined # and two of the seven files cannot pass: t/country_v6.t dies outright and # t/org.t loses its two _v6 subtests. # # Measured on an x86-64 control reduced to exactly this arm's shape -- no IP.so, # DynaLoader unloadable, Socket unloadable, the hook proved live first -- driving # the SAME `prove -r t/`: Files=7, Tests=20, Result FAIL, those two files and no # others. Byte for byte what this image produces. So these three go green by # themselves when #93 lands, with nothing to edit. case "$out" in *"Result: PASS"*) bst_ok geoip-perl/prove-result ;; *) bst_notok geoip-perl/prove-result "rc=$rc $(_bst_1line "$(_nowarn "$out")")" ;; esac check_eq geoip-perl/prove-rc 0 "$rc" case "$out" in *"All tests successful."*) bst_ok geoip-perl/prove-all-successful ;; *) bst_notok geoip-perl/prove-all-successful "$(_bst_1line "$(_nowarn "$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. All seven still RUN here; # two of them fail. files=$(printf '%s\n' "$out" | sed -n 's/^Files=\([0-9]*\),.*/\1/p' | tail -1) check_eq geoip-perl/prove-files 7 "$files" # The assertion count is bounded from BELOW at what the reduced control ran, not # at the 24 a working perl gives. Bounding at 24 would put a fourth #93 red in # front of a reader for a fact three cases already state, and this figure has to # keep meaning something on the day #93 lands -- when it rises to 24 and stays # above the bound. What it catches is the failure a verdict cannot: files that # stopped running at all. 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 geoip-perl/prove-tests "no Tests= total in prove's summary -- $(_bst_1line "$(_nowarn "$out")")" ;; *) if [ "$tests" -ge 20 ]; then bst_ok geoip-perl/prove-tests else bst_notok geoip-perl/prove-tests "Tests=$tests, want >= 20 (reduced x86-64 control ran 20; a working perl runs 24)"; fi ;; esac # WHICH FILES FAIL IS ASSERTED AS A SUBSET, and that is the case doing the real # regression work here. It is green today (the two known files), green the day # #93 lands (the empty set is a subset), and red the moment ANY OTHER file starts # failing -- which is exactly the event three permanently-red verdict cases above # would otherwise hide. Never widen this set to make a run green: a third failing # file is a finding. # Read from prove's own "Test Summary Report", where each failing file is one # line `t/.t (Wstat: ...)`. The per-file progress lines above it carry the # leader dots and a trailing space and are not parseable the same way -- a regex # aimed at those matches nothing, and a failed-set that is always empty is a case # that always passes. failed=$(printf '%s\n' "$out" | sed -n 's|^\(t/[A-Za-z0-9_]*\.t\) *(Wstat:.*|\1|p' | sort -u) unexpected="" for f in $failed; do case "$f" in t/country_v6.t|t/org.t) ;; *) unexpected="$unexpected $f" ;; esac done if [ -z "$unexpected" ]; then bst_ok geoip-perl/prove-failed-set else bst_notok geoip-perl/prove-failed-set "files failing beyond the two held by linux-in-the-browser#93:$unexpected"; fi # NO FILE MAY REPORT AS SKIPPED. The control skips nothing, and the only two # conditional skips in the suite are guarded on `$] < 5.014`, which is false here. # A skip appearing on this image would be a silent narrowing of the criterion -- # and is a different event from the two #93 FAILURES above, which run and lose. case "$out" in *skipped*) bst_notok geoip-perl/no-skips "$(printf '%s\n' "$out" | grep -i skipped | head -1)" ;; *) bst_ok geoip-perl/no-skips ;; esac # ------------------ 4b. the two #93 facts, named directly -------------------- # Both written in the direction that is TRUE ON A NORMAL PERL, so they self-flip. # They exist so that the criterion's reds have a stated cause ON THE SUITE rather # than only in a design doc, and so that a reader can tell in one line whether # what changed was the platform or this port. check_rc geoip-perl/socket-loads 0 perl -MSocket -e 'exit 0' v6=$( cd "$PT" && perl -MGeo::IP -e 'print Geo::IP->can("country_code_by_addr_v6") ? "V6-YES" : "V6-NO", "\n"' 2>&1 ) check_eq geoip-perl/v6-methods-defined "V6-YES" "$(printf '%s\n' "$v6" | sed -n 's/.*\(V6-[A-Z]*\).*/\1/p' | tail -1)" # ------------------ 5. direct lookups, outside the harness ------------------- # Every one of these is a real answer read out of a bundled database, so a `prove` # that somehow reported PASS over zero work still has to produce them. The values # are the ones the distribution's own tests assert, re-driven here through the # public API in one perl rather than seven. # # THE v6 LOOKUP IS A SEPARATE PROCESS, and deliberately: calling an undefined # method is fatal, so a v6 probe sharing this one would take every line after it # down with it and report five empty values for one defect. That is precisely what # the first version of this suite did on its first boot. probe=$( cd "$PT" && perl -MGeo::IP -e ' my $c = Geo::IP->open("t/data/GeoIP.dat", GEOIP_STANDARD); printf "R-cc %s\n", $c->country_code_by_addr("64.17.254.216") // "undef"; printf "R-name %s\n", $c->country_name_by_addr("64.17.254.216") // "undef"; my $city = Geo::IP->open("t/data/GeoIPCity.dat", GEOIP_STANDARD); my $r = $city->record_by_addr("64.17.254.216"); printf "R-city %s\n", $r ? $r->city : "undef"; printf "R-region %s\n", $r ? $r->region_name : "undef"; printf "R-postal %s\n", $r ? $r->postal_code : "undef"; printf "R-tz %s\n", $r ? $r->time_zone : "undef"; my $org = Geo::IP->open("t/data/GeoIPOrg.dat", GEOIP_STANDARD); printf "R-org %s\n", $org->org_by_addr("12.87.118.0") // "undef"; printf "R-unknown %s\n", $c->country_code_by_addr("0.0.0.0") // "undef"; ' 2>&1 ) # The tag is matched ANYWHERE ON THE LINE, not anchored. The pure-Perl path # writes its diagnostics to stdout WITHOUT a trailing newline ("Error Traversing # Database for ipnum = ... - Perhaps database is corrupt?"), so a following # tagged line arrives glued to the end of one -- and an anchored match then finds # nothing and reports an empty value, which reads as a failed lookup rather than # as a parse that missed. Caught on the x86-64 fake rootfs, where exactly this # turned the non-database case red for the wrong reason. _p() { printf '%s\n' "$probe" | sed -n "s/.*$1 //p" | tail -1; } check_eq geoip-perl/lookup-country-code "US" "$(_p R-cc)" check_eq geoip-perl/lookup-country-name "United States" "$(_p R-name)" check_eq geoip-perl/lookup-city "El Segundo" "$(_p R-city)" check_eq geoip-perl/lookup-region-name "California" "$(_p R-region)" check_eq geoip-perl/lookup-postal-code "90245" "$(_p R-postal)" check_eq geoip-perl/lookup-time-zone "America/Los_Angeles" "$(_p R-tz)" check_eq geoip-perl/lookup-org "AT&T Worldnet Services" "$(_p R-org)" # A database that answers everything is answering nothing. 0.0.0.0 is not in the # fixture, and the API's contract for a miss is undef -- so this is the case that # says the lookups above are reads rather than a constant. check_eq geoip-perl/lookup-miss "undef" "$(_p R-unknown)" # The v6 lookup, in its own process. Red today for the same reason as # geoip-perl/v6-methods-defined -- linux-in-the-browser#93 -- and green by itself # the day that closes, because the fixture and the expected answer are real: the # CAPI control and the DynaLoader-only-reduced control both return JP. v6probe=$( cd "$PT" && perl -MGeo::IP -e ' my $g = Geo::IP->open("t/data/GeoIPv6.dat", GEOIP_STANDARD); my $x = eval { $g->country_code_by_addr_v6("2001:200::") }; printf "R-v6 %s\n", (defined $x ? $x : "undef"); ' 2>&1 ) check_eq geoip-perl/lookup-v6 "JP" "$(printf '%s\n' "$v6probe" | sed -n 's/.*R-v6 //p' | tail -1)" # Opening something that is not a GeoIP database must not produce a location. # ASSERTED ON THE LOOKUP, NOT ON THE OPEN, because the two implementations refuse # at different points and only the lookup is a contract both keep. Measured on the # x86-64 control: the C API's open() returns NULL (`none`), while the pure-Perl # open() hands back an object and the first query prints "Error Traversing # Database ... Perhaps database is corrupt?" and returns undef. An open()-shaped # case would therefore be red on this arm for an upstream difference between # Geo::IP's own two implementations -- not for a platform gap and not for anything # this port did. What matters, and what both keep, is that no garbage location # comes back. bad=$( cd "$PT" && perl -MGeo::IP -e ' my $g = eval { Geo::IP->open("run-ptest", GEOIP_STANDARD) }; my $v = $g ? eval { $g->country_code_by_addr("64.17.254.216") } : undef; print "R-bad ", (defined $v ? "[$v]" : "undef"), "\n";' 2>&1 ) check_eq geoip-perl/non-database-no-answer "undef" "$(printf '%s\n' "$bad" | sed -n 's/.*R-bad //p' | tail -1)"