# BST-TIMEOUT: 900 # 97 cases, 247s measured on a host that was building at the time; the 60s default is not close . "$BST_LIB" # libpcre (PCRE 8.45) -- the ORIGINAL Perl-compatible regular expression library, # closed on upstream's own test suite under ptest-runner, as cunit and cmocka are. # # THIS IS NOT libpcre2, WHICH THIS IMAGE ALSO CARRIES (77-libpcre2.test). PCRE2 is # a ground-up rewrite with a different API and its own recipe; 8.45 is the last of # the 8.x line. Both ship deliberately, and nothing below is shared with that file. # # WHAT UPSTREAM'S PTEST ACTUALLY IS. run-ptest is `make check-TESTS` over automake's # own driver, and it runs THREE tests: # RunTest 26 numbered test sets over the engine -- matching, # DFA matching, UTF-8, Unicode properties, internal # offsets and compiled code sizes -- each run twice, # once plain and once "with study". # RunGrepTest pcregrep's main, UTF-8 and newline-setting features. # pcre_stringpiece_unittest the C++ wrapper's own unit test. # # THE ORACLE IS A NATIVE x86_64 CONTROL BUILD of the identical 8.45 tarball (sha256 # 4dae6fdc...bffb8, the sum the recipe pins) configured with the recipe's own flags # --enable-utf --enable-pcre8 --enable-unicode-properties plus the distro-wide # --disable-shared, and with --disable-jit to match what this arm resolves to. Every # number below was read off that run, never off the source. It reported 3/3 PASS. # # AND THE EXPECTATIONS ARE A TRUE ORACLE, not a convenience. The testdata/testoutput* # files are checked into the tarball and are selected by LIBRARY BIT WIDTH alone -- # never by host pointer size. That includes test 11, whose "Memory allocation (code # space)" figures count compiled code UNITS at link size 2, a property of the # compiled pattern rather than of the machine. So nothing in these counts is allowed # to differ between x86_64 and wasm32, and a single disagreement is a defect on this # arm rather than a platform difference to reason about. # # TWO CONFIGURE OUTCOMES ARE THE ARM SPEAKING, and both are upstream defaults left # alone rather than overrides: # test 12 is SKIPPED --enable-jit=auto probes sljit, sljit carries no wasm # backend, so JIT switches itself off. Test 13, its "when JIT # is not available" counterpart, runs in its place. Asserting # the pair is how a JIT that silently came back would be seen. # tests 17-26 SKIPPED "Skipped when running 8-bit tests" -- the pcre8-only default. # # SPAWN BUDGET IS THE REAL CONSTRAINT, and this suite is the heaviest on the image # for it: exec here is a fresh wasm module instantiation (~2-3 s), and RunGrepTest # alone carries 128 pcregrep call sites while RunTest drives pcretest 37 times and a # `diff -u` after each. That is why the file runs ptest-runner ONCE, reads the logs # the run leaves behind, joins everything in ONE awk pass that emits a pre-computed # triple per case, and reports from a shell loop that spawns # nothing. Presence checks use check_rc with `test`, an ash builtin that forks no # subshell -- unlike check/check_out, whose command substitution does. # # AND THE HARNESS IS GIVEN -t DELIBERATELY. ptest-runner's own default timeout is # 300 s (DEFAULT_TIMEOUT, main.c). This whole file measured 247 s on a host that was # running a build at the same time, so the ptest alone sits close enough under 300 s # that the default would be decided by how loaded the machine is -- and the failure # mode is a run killed part-way and reported as a failing ptest, a red that says # nothing about pcre. -t removes that coin toss. It is set BELOW the suite's own # BST-TIMEOUT on purpose, so if the suite ever does overrun, ptest-runner reports the # timeout in its own voice instead of the whole .test file being killed around it. ptestdir=/usr/lib/libpcre/ptest runner=/usr/bin/ptest-runner hdr=/usr/include/pcre.h lib=/usr/lib/libpcre.a pc=/usr/lib/pkgconfig/libpcre.pc runner_t=780 tmp=${TMPDIR:-/tmp}/bst-libpcre.$$ mkdir -p "$tmp" 2>/dev/null || tmp=/tmp # ----------------------------------------------------------- what shipped ---- # The ptest payload, then the -dev/-staticdev half. All builtin `test`, so this # whole block costs no process. check_rc libpcre/ptest-dir 0 test -d "$ptestdir" check_rc libpcre/ptest-runptest 0 test -x "$ptestdir/run-ptest" check_rc libpcre/ptest-makefile 0 test -s "$ptestdir/Makefile" check_rc libpcre/ptest-testdata 0 test -d "$ptestdir/testdata" check_rc libpcre/ptest-driver 0 test -s "$ptestdir/test-driver" # The three binaries do_install_ptest copies. They are asserted individually # because oe-core copies them from ${B}/.libs/, which on a static-only distro holds # archives and NOT these -- the bbappend's whole subject. A regression there is a # build failure, but a partial copy would surface exactly here. for b in pcretest pcregrep pcre_stringpiece_unittest; do check_rc "libpcre/ptest-bin-$b" 0 test -x "$ptestdir/$b" done for s in RunTest RunGrepTest; do check_rc "libpcre/ptest-script-$s" 0 test -s "$ptestdir/$s" done # The three archives -- one per library the recipe builds. libpcrecpp.a is the # evidence that the C++ half built at all, which is what keeps # pcre_stringpiece_unittest in the suite. for a in libpcre libpcrecpp libpcreposix; do check_rc "libpcre/ar-$a" 0 test -s "/usr/lib/$a.a" done # ...and no shared object, which is distro policy rather than a pcre property. for a in libpcre libpcrecpp libpcreposix; do check_rc "libpcre/no-so-$a" 1 test -e "/usr/lib/$a.so" done # The six installed headers: the C API, the POSIX wrapper, and the four C++ ones. for h in pcre pcreposix pcrecpp pcrecpparg pcre_scanner pcre_stringpiece; do check_rc "libpcre/hdr-$h" 0 test -s "/usr/include/$h.h" done # The three pkg-config files, and the two shipped tools. for p in libpcre libpcrecpp libpcreposix; do check_rc "libpcre/pc-$p" 0 test -f "/usr/lib/pkgconfig/$p.pc" done check_rc libpcre/bin-pcregrep 0 test -x /usr/bin/pcregrep check_rc libpcre/bin-pcretest 0 test -x /usr/bin/pcretest # ------------------------------------------------------- run it, once --------- # TWO WAYS IN, AND THE SUITE PREFERS THE HARNESS WHENEVER IT IS THERE -- the same # arrangement 77-cunit.test uses, and for the same reason: this ticket closes on the # suite run UNDER ptest-runner, so the harness's discovery, its fork+execv+openpty # child plumbing and its tally are part of what is being asserted. # # The fallback runs run-ptest directly -- byte-for-byte the script the harness would # exec, so every assertion about PCRE is unaffected -- and SKIPS exactly the cases # that are about the HARNESS. A skip, not a red: they have no subject on an image # that ships no ptest-runner. out=$tmp/runner.out harness=0 if [ -x "$runner" ]; then harness=1 bst_ok libpcre/ptest-runner-present "$runner" -t "$runner_t" libpcre > "$out" 2>&1 rc=$? check_eq libpcre/ptest-runner-rc 0 "$rc" else bst_skip libpcre/ptest-runner-present "this image ships no ptest-runner" bst_skip libpcre/ptest-runner-rc "no ptest-runner to run" for c in runner-begin runner-end runner-tally runner-no-error; do bst_skip "libpcre/$c" "harness cases have no subject without ptest-runner" done ( cd "$ptestdir" && sh ./run-ptest ) > "$out" 2>&1 fi # The per-test detail is NOT on the runner's stdout: automake's check-TESTS prints # only the PASS:/FAIL: lines there and writes each test's own output to .log # beside the Makefile. RunTest.log is where the 26 test sets report, so it is the # file that carries the port's real evidence and it is read back here. rl=$ptestdir/RunTest.log check_rc libpcre/runtest-log-written 0 test -s "$rl" # --------------------------------------------------- the archive, flattened --- # The 77-cunit.test idiom. Member names here are libtool-prefixed # (libpcre_la-pcre_exec.o) and so exceed ar's 15-char inline limit, which puts them # in the archive's extended name table -- still literal text, still terminated by # '/', so the same whole-token lookup finds them. idx=$tmp/ar.idx tr -c '\40-\176\n' ' ' < "$lib" > "$idx" 2>/dev/null # ...and a SECOND flattening of the same archive, for symbols only. Members and # symbols want different alphabets and one pass cannot serve both: # # members keep every printable byte, because the stored name is # "libpcre_la-pcre_exec.o/" and the '-', '.' and '/' are part of what # makes it a whole token. # symbols map every NON-IDENTIFIER byte to a space, so each identifier in the # binary becomes a space-delimited token no matter what surrounds it. # # The second one is what makes a plain index() both correct and fast here. Doing # it with a regex boundary instead -- match(idx, "(^|[^A-Za-z0-9_])" tok "...") # -- is correct too and was tried: it costs a regex scan of the whole ~1 MB blob # per symbol, and on this arm that took the suite from 247 s to over 900 s and # tripped its own timeout. One extra tr is ~2 s and index() is a substring scan. sidx=$tmp/ar.sidx tr -c 'A-Za-z0-9_\n' ' ' < "$lib" > "$sidx" 2>/dev/null # ------------------------------------------------------- the single join ------ # One awk over five files, emitting "\t\t" per case. Everything # after this is pure shell. # # THE PROGRAM GOES IN A FILE, NOT ON THE COMMAND LINE, and that is a hard platform # constraint rather than a preference: this guest caps a process's whole argv block # at 8192 bytes (__wasm32_args_buf), and the program below is 9.3 KB on its own. # Passed inline it made a 10123 B argv, and the exec died with # [hardwarejs] user[347] error: argv block (10123B) exceeds __wasm32_args_buf (8192B) # -- the suite printing ==BST-BEGIN== and then nothing at all, since the join that # produces every verdict never ran. `awk -f` puts the program on stdin's side of the # boundary and leaves argv a few hundred bytes. Linux's own limit is MAX_ARG_STRLEN # (128 KB) per argument, so nothing about this program is unreasonable on a normal # arm; it is this guest that is short, and the limit is reported upstream. cat > "$tmp/join.awk" <<'JOINAWK' function emit(id, want, got) { print id, want, got } # The two archive lookups are set up here and answered by a SINGLE LINEAR SCAN of # each index, rather than by slurping the archive into an awk string and searching # it per token. That distinction is not style: `idx = idx " " $0` reallocates and # copies the whole accumulator on every line, so it is QUADRATIC in archive size. # 77-cunit.test gets away with it on a 285 KB / 1247-line archive; libpcre.a is # 911 KB over 5130 lines, which is ~10x the work for one index and ~20x once the # symbol index is added -- measured, not estimated: that version ran the suite past # its 900 s bound and tripped suite-timeout while the ptest underneath had finished # in about three minutes. Marking wanted tokens into a hash as each line is split # is linear in the archive and bounded in memory by the size of these two lists, # not by the size of the binary. BEGIN { MEMLIST = "pcre_compile pcre_exec pcre_dfa_exec pcre_study pcre_tables pcre_ucd " \ "pcre_valid_utf8 pcre_xclass pcre_newline pcre_ord2utf8 pcre_globals " \ "pcre_chartables pcre_version pcre_config pcre_fullinfo pcre_get " \ "pcre_maketables pcre_refcount pcre_byte_order pcre_string_utils " \ "pcre_jit_compile" SYMLIST = "pcre_compile pcre_compile2 pcre_exec pcre_dfa_exec pcre_study " \ "pcre_free_study pcre_fullinfo pcre_config pcre_version " \ "pcre_maketables pcre_refcount pcre_get_substring " \ "pcre_get_substring_list pcre_free_substring pcre_copy_substring " \ "pcre_get_named_substring pcre_get_stringnumber " \ "pcre_pattern_to_host_byte_order" nmem = split(MEMLIST, memv, " ") # ar stores a short member name slash-terminated; these are libtool-prefixed and # so live in the archive s extended name table, still as literal "/" text. for (i = 1; i <= nmem; i++) want_mem["libpcre_la-" memv[i] ".o/"] = 1 nsym = split(SYMLIST, symv, " ") for (i = 1; i <= nsym; i++) want_sym[symv[i]] = 1 } # ---- file 1: ptest-runner stdout ---- FILENAME == RUNOUT { if ($0 ~ /^PASS: pcre_stringpiece_unittest/) p_sp = "PASS" if ($0 ~ /^FAIL: pcre_stringpiece_unittest/) p_sp = "FAIL" if ($0 ~ /^PASS: RunTest/) p_rt = "PASS" if ($0 ~ /^FAIL: RunTest/) p_rt = "FAIL" if ($0 ~ /^PASS: RunGrepTest/) p_rg = "PASS" if ($0 ~ /^FAIL: RunGrepTest/) p_rg = "FAIL" # automake summary block: "# TOTAL: 3", "# PASS: 3", ... if ($0 ~ /^# TOTAL:/) s_total = $NF if ($0 ~ /^# PASS:/) s_pass = $NF if ($0 ~ /^# SKIP:/) s_skip = $NF if ($0 ~ /^# XFAIL:/) s_xfail = $NF if ($0 ~ /^# FAIL:/) s_fail = $NF if ($0 ~ /^# XPASS:/) s_xpass = $NF if ($0 ~ /^# ERROR:/) s_error = $NF if ($0 ~ /^BEGIN: /) begin_line = $2 if ($0 ~ /^END: /) end_line = $2 if ($0 ~ /^TOTAL: /) tally = $0 if ($0 ~ /^ERROR: /) runner_error = runner_error $0 " " next } # ---- file 2: RunTest.log, the per-set detail ---- FILENAME == RUNTESTLOG { if ($0 ~ /^PCRE version /) { ver = $0; sub(/^PCRE version /, "", ver) } if ($0 == " OK") ok_plain++ if ($0 == " OK with study") ok_study++ if ($0 ~ /Skipped/) skipped++ # the two skips that are this arm speaking, asserted by name rather than count if ($0 ~ /^Test 12:/) seen12 = 1 if (seen12 && $0 ~ /Skipped because JIT/) { jitskip = 1; seen12 = 0 } if ($0 ~ /^PASS RunTest/) rt_verdict = "PASS" if ($0 ~ /^FAIL RunTest/) rt_verdict = "FAIL" next } # ---- file 3: pcre.h ---- FILENAME == HDR { if ($0 ~ /^#define PCRE_MAJOR/) hmaj = $3 if ($0 ~ /^#define PCRE_MINOR/) hmin = $3 if ($0 ~ /^#define PCRE_DATE/) hdate = $3 next } # ---- file 4: libpcre.pc ---- FILENAME == PC { if ($0 ~ /^Name: /) { sub(/^Name: */, ""); pcname = $0 } if ($0 ~ /^Version: /) { sub(/^Version: */, ""); pcver = $0 } if ($0 ~ /^Libs: /) { sub(/^Libs: */, ""); pclibs = $0 } if ($0 ~ /^Cflags: /) { sub(/^Cflags: */, ""); pccf = $0 } next } # ---- file 5: the printable-flattened archive index (members) ---- FILENAME == IDX { n = split($0, w, " ") for (i = 1; i <= n; i++) if (w[i] in want_mem) mem_seen[w[i]] = 1 next } # ---- file 6: the identifier-normalised archive index (symbols) ---- FILENAME == SIDX { n = split($0, w, " ") for (i = 1; i <= n; i++) if (w[i] in want_sym) sym_seen[w[i]] = 1 next } # BOTH lookups above are whole-token by construction -- a token only lands in # mem_seen/sym_seen if split() produced it entire -- which is the anti-prefix # guarantee 77-cunit.test reaches for with `index(" " idx " ", " " tok " ")`: # pcre_compile must not match inside pcre_compile2, and pcre_get_substring must # not match inside pcre_get_substring_list. Both pairs are real and both are in # the lists above, and the rule was checked in the rejecting direction too -- # pcre_compil, pcre_get_substrin and pcre_pattern_to_host all correctly report # absent against this archive. # # WHY SYMBOLS NEED THEIR OWN INDEX, which cost a run to establish. Against the # PRINTABLE index the delimiter is whatever byte the object format happened to # place next to the name: pcre_pattern_to_host_byte_order occurs three times in # this archive and not one is space-delimited on both sides -- the first is # preceded by a printable "^", the third abuts the string "llvm" outright -- so it # read as MISSING for a symbol plainly present, while pcre_compile2 passed only # because its neighbours happened to be spaces. That is a coin toss, not a test. # Normalising every non-identifier byte to a space makes the delimiter a property # of the alphabet instead. END { # --- THE SUITE. These are the port evidence and everything else is payload. --- emit("libpcre/suite-stringpiece", "PASS", p_sp "") emit("libpcre/suite-runtest", "PASS", p_rt "") emit("libpcre/suite-rungreptest", "PASS", p_rg "") # automake s own tally, which is the sum the driver computes for itself rather # than one added up here -- so a disagreement between it and the three verdicts # above is caught too. emit("libpcre/suite-total", "3", s_total "") emit("libpcre/suite-pass", "3", s_pass "") emit("libpcre/suite-fail", "0", s_fail "") emit("libpcre/suite-skip", "0", s_skip "") emit("libpcre/suite-xfail", "0", s_xfail "") emit("libpcre/suite-xpass", "0", s_xpass "") emit("libpcre/suite-error", "0", s_error "") # --- RunTest, per set. The engine evidence proper. --- # 14 sets report a bare OK and 13 also report OK with study; test 13 is the one # that runs unstudied only, which is why the two counts differ by one. emit("libpcre/runtest-version", "8.45 2021-06-15", ver "") emit("libpcre/runtest-ok", "14", ok_plain "") emit("libpcre/runtest-ok-study", "13", ok_study "") emit("libpcre/runtest-skipped", "11", skipped "") emit("libpcre/runtest-verdict", "PASS", rt_verdict "") # JIT off is expected on this arm -- assert the REASON, so a build that lost # test 12 some other way does not read the same as sljit declining wasm32. emit("libpcre/runtest-jit-skipped", "1", jitskip "") # --- the harness: discovery, the child plumbing, and the tally. --- if (HARNESS == "1") { emit("libpcre/runner-begin", PTESTDIR, begin_line "") emit("libpcre/runner-end", PTESTDIR, end_line "") emit("libpcre/runner-tally", "TOTAL: 1 FAIL: 0", tally "") # ptest-runner reports its own fork/execv/openpty/pipe2 failures as ERROR # lines and still exits 0 for some of them, so absence is asserted directly. emit("libpcre/runner-no-error", "", runner_error "") } # --- the payload --- emit("libpcre/hdr-major", "8", hmaj "") emit("libpcre/hdr-minor", "45", hmin "") emit("libpcre/hdr-date", "2021-06-15", hdate "") emit("libpcre/pc-name", "libpcre", pcname "") emit("libpcre/pc-version", "8.45", pcver "") emit("libpcre/pc-libs", "-L${libdir} -lpcre", pclibs "") # PCRE_STATIC in Cflags is how a consumer gets the right linkage decls on a # static-only image; without it pcre.h declares the symbols dllimport-style on # some arms. It is the .pc half of this distro s --disable-shared. emit("libpcre/pc-cflags-static", "1", (index(pccf, "-DPCRE_STATIC") > 0 ? "1" : "0")) # Archive members, one per compilation unit that matters (MEMLIST, above). for (i = 1; i <= nmem; i++) emit("libpcre/ar-member-" memv[i], memv[i], (("libpcre_la-" memv[i] ".o/") in mem_seen ? memv[i] : "MISSING")) # One exported entry point per area a consumer actually calls (SYMLIST, above). # pcre_ucd.o and pcre_tables.o export data rather than functions and are covered # by the member check; everything else here pairs with a member. for (i = 1; i <= nsym; i++) emit("libpcre/ar-sym-" symv[i], symv[i], (symv[i] in sym_seen ? symv[i] : "MISSING")) } JOINAWK awk -v OFS='\t' -f "$tmp/join.awk" \ RUNOUT="$out" RUNTESTLOG="$rl" HDR="$hdr" PC="$pc" IDX="$idx" SIDX="$sidx" \ PTESTDIR="$ptestdir" HARNESS="$harness" \ "$out" "$rl" "$hdr" "$pc" "$idx" "$sidx" > "$tmp/verdicts" 2>/dev/null # ------------------------------------------------------ report, fork-free ----- # A join that produced nothing at all is itself a failure, and one that silently # emitted half its cases would otherwise read as a short but green suite. n=0 while IFS=' ' read -r id want got; do [ -n "$id" ] || continue check_eq "$id" "$want" "$got" n=$((n + 1)) done < "$tmp/verdicts" if [ "$harness" = 1 ]; then check_eq libpcre/join-complete 66 "$n" else check_eq libpcre/join-complete 62 "$n"; fi rm -rf "$tmp"