# BST-TIMEOUT: 600 # ~215 cases; the probe forks an IPC server whose own # # deadline is 5 s, and its connect retry is bounded at 2 s . "$BST_LIB" # libqb 2.0.8 -- "a library with the primary purpose of providing high # performance client server reusable features ... high performance logging, # tracing, ipc, and poll" (meta-oe recipes-extended/libqb). What ships is # /usr/lib/libqb.a, the qb/ headers, libqb.pc, and two programs: # /usr/sbin/qb-blackbox and /usr/bin/doxygen2man. # # --------------------------------------------------------------------------- # WHY THIS SUITE RUNS A PROBE, WHEN libqb DOES SHIP TESTS # # libqb is a fourth shape of port on this image. libsmi closes on its own CLI; # libevent closes on upstream's ptest; libev had no tests at all and needed a # probe. libqb HAS tests -- tests/check_*.c -- but they are gated on the Check # framework: # # PKG_CHECK_MODULES([CHECK], [check >= 0.9.4],[with_check=yes],[with_check=no]) # AM_CONDITIONAL(HAVE_CHECK, test "${with_check}" = "yes") # # and there is no `check` recipe in poky or in meta-openembedded, so HAVE_CHECK # is false for every build this distro can do and not one of those tests is # compiled. Porting Check is a different ticket. So the coverage is constructed # the way libev's was, because a suite that only checked the payload's shape # would go green on a library that returns silently wrong answers -- the # BRINTOS-217 lesson, where oniguruma packaged perfectly and handed every caller # wrong offsets. # # data/libqb-probe (src/libqb_probe.c) is that consumer. It is cross-linked with # wasmcc-glibc against THE VERY libqb.a THAT SHIPS, and drives the three # subsystems the ticket names -- IPC, logging and ring buffers -- plus the data # structures they are built on. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86_64 CONTROL # # data/libqb-native.txt is the same probe's output against a build of the same # upstream commit (SRCREV 002171bb = tag v2.0.8) with the same single patch this # layer applies, generated by src/libqb_mknative.sh. The probe prints only what # cannot legitimately differ between two machines: booleans, counts, ordering # strings, errno NAMES and literal payloads. No pointer, no pid, no clock # reading, no sizeof. So every QB- line must match byte for byte, and the values # that DO legitimately differ (the four type widths, libc's strerror text) carry # a QBX- prefix, are kept out of the control, and are asserted separately below. # # --------------------------------------------------------------------------- # WHAT A RED HERE MEANS, BY GROUP # # B,C,D,E list/array/map/atomic -- pure computation, no syscall. A red is # libqb miscompiled, and nothing else. # F util. Locks and the stopwatch. # G RING BUFFERS: POSIX shared memory. A red is the platform's shm. # H LOGGING, including the blackbox. # I the event loop over timers and a pipe. # J IPC: a forked server and a real client over a unix socket plus the # shm transport. fork() works on this arm; it is # clone(CLONE_VM|CLONE_VFORK) that is unwired. # # Keeping them apart is what lets a reader tell "libqb is broken here" from "the # platform is": a red G or J beside a green B/C/D is a platform gap, and a red # B/C/D is the port. # # Threads are deliberately NOT exercised. pthread_create returns 0 here and the # thread runs, but pthread_join aborts -- futex BITSET ops are ENOSYS in the # Worker fast-path (brintos/hardwarejs#12) -- so libqb's threaded surfaces would # only re-measure that known defect and would take the probe down with it. lib=/usr/lib/libqb.a incd=/usr/include/qb pc=/usr/lib/pkgconfig/libqb.pc blackbox=/usr/sbin/qb-blackbox DATA=$BST_DATA NATIVE=$DATA/libqb-native.txt PROBE=$DATA/libqb-probe TMP=${TMPDIR:-/tmp} nl=' ' if [ ! -f "$lib" ]; then bst_skip libqb/present "libqb not installed on this image" exit 0 fi bst_ok libqb/present hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not in archive: $2" ;; esac; } # --- payload shape ----------------------------------------------------------- hasfile libqb/archive "$lib" hasfile libqb/header-qbrb "$incd/qbrb.h" hasfile libqb/header-qblog "$incd/qblog.h" hasfile libqb/header-qbipcc "$incd/qbipcc.h" hasfile libqb/header-qbipcs "$incd/qbipcs.h" hasfile libqb/header-qbloop "$incd/qbloop.h" hasfile libqb/header-qbmap "$incd/qbmap.h" hasfile libqb/header-qbarray "$incd/qbarray.h" hasfile libqb/header-qbutil "$incd/qbutil.h" hasfile libqb/header-qbconfig "$incd/qbconfig.h" hasfile libqb/pkgconfig "$pc" # The distro is static-only: upstream packages ${libdir}/libqb.so.*, which must # match nothing here. A shared object means --disable-shared stopped being # honoured and the image grew a package it does not want. if [ -e /usr/lib/libqb.so ] || [ -e /usr/lib/libqb.so.100 ]; then bst_notok libqb/no-shared-object "a libqb shared object shipped on a static-only distro" else bst_ok libqb/no-shared-object fi # The version must NOT carry git-version-gen's "-dirty" suffix. S is a git # checkout pinned to the v2.0.8 tag, and applying any patch with the default # quilt PATCHTOOL leaves the tree modified, which renames the library to # 2.0.8-dirty in qb_ver_str, QB_VER_REST and here. The bbappend pins it with # .tarball-version; this is the case that notices if that stops working. pctext=$(cat "$pc" 2>/dev/null) case "$pctext" in *"Version: 2.0.8"*) case "$pctext" in *dirty*) bst_notok libqb/pc-version-clean "libqb.pc carries a -dirty version: $(_bst_1line "$(grep -i version "$pc")")" ;; *) bst_ok libqb/pc-version-clean ;; esac ;; *) bst_notok libqb/pc-version-clean "unexpected Version in libqb.pc: $(_bst_1line "$(grep -i version "$pc")")" ;; esac case "$pctext" in *"-lqb"*) bst_ok libqb/pc-libs ;; *) bst_notok libqb/pc-libs "libqb.pc does not name -lqb" ;; esac # --- the two programs the port installs -------------------------------------- if [ -x "$blackbox" ]; then bst_ok libqb/blackbox-present else bst_notok libqb/blackbox-present "$blackbox absent or not executable"; fi if [ -x /usr/bin/doxygen2man ]; then bst_ok libqb/doxygen2man-present else bst_notok libqb/doxygen2man-present "/usr/bin/doxygen2man absent or not executable"; fi # --- symbol census ----------------------------------------------------------- # One name per subsystem, so a partially-linked archive is legible rather than # merely "incomplete". arcsyms=$(grep -ao 'qb_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) hasline libqb/sym-rb-open qb_rb_open "$arcsyms" hasline libqb/sym-rb-chunk-write qb_rb_chunk_write "$arcsyms" hasline libqb/sym-rb-chunk-read qb_rb_chunk_read "$arcsyms" hasline libqb/sym-log-init qb_log_init "$arcsyms" hasline libqb/sym-log-real qb_log_real_ "$arcsyms" hasline libqb/sym-log-blackbox qb_log_blackbox_write_to_file "$arcsyms" hasline libqb/sym-ipcc-connect qb_ipcc_connect "$arcsyms" hasline libqb/sym-ipcs-create qb_ipcs_create "$arcsyms" hasline libqb/sym-ipcs-run qb_ipcs_run "$arcsyms" hasline libqb/sym-loop-create qb_loop_create "$arcsyms" hasline libqb/sym-loop-poll-add qb_loop_poll_add "$arcsyms" hasline libqb/sym-loop-timer-add qb_loop_timer_add "$arcsyms" hasline libqb/sym-hashtable qb_hashtable_create "$arcsyms" hasline libqb/sym-skiplist qb_skiplist_create "$arcsyms" hasline libqb/sym-trie qb_trie_create "$arcsyms" hasline libqb/sym-array-create qb_array_create "$arcsyms" hasline libqb/sym-atomic-init qb_atomic_init "$arcsyms" hasline libqb/sym-util-stopwatch qb_util_stopwatch_create "$arcsyms" nsym=$(printf '%s\n' "$arcsyms" | grep -c '^qb_' 2>/dev/null || echo 0) if [ "$nsym" -ge 150 ]; then bst_ok libqb/symbols-counted else bst_notok libqb/symbols-counted "only $nsym qb_* names in the archive"; fi # --- the library RUNNING, via the cross-linked probe ------------------------- if [ ! -s "$NATIVE" ]; then bst_notok libqb/native-present "$NATIVE missing -- no oracle to compare against" exit 0 fi bst_ok libqb/native-present # do_install stages data/* at 0644 and the deployed rootfs is read-only at # browser runtime, so the probe cannot be executed where it lies. _p=$TMP/bst-libqb-probe.$$ cp "$PROBE" "$_p" 2>/dev/null && chmod 0755 "$_p" 2>/dev/null if [ ! -x "$_p" ]; then bst_notok libqb/probe-exec "could not stage an executable copy of $PROBE" rm -f "$_p" exit 0 fi bst_ok libqb/probe-exec out=$TMP/bst-libqb.$$ bb=$TMP/bst-libqb-bb.$$ QB_PROBE_BLACKBOX=$bb timeout 300 "$_p" > "$out.out" 2>"$out.err"; prc=$? rm -f "$_p" check_eq libqb/probe-rc 0 "$prc" # A truncated run must be distinguishable from a run that legitimately produced # these lines: with no terminator, a wedge half way through would silently # reduce the number of compared cases rather than fail one. Every absent tag is # reported by the comparison below, so a wedge names the group it stopped in. if [ "$(grep -c '^QB-ZZ-complete yes' "$out.out" 2>/dev/null)" = "1" ]; then bst_ok libqb/probe-complete else bst_notok libqb/probe-complete "no terminator; last saw: $(_bst_1line "$(tail -n 2 "$out.out" 2>/dev/null)")" fi # --- tag-by-tag against the native control ----------------------------------- # One awk over both files rather than a shell loop of ~196 awk calls: process # spawn is expensive on this target and the whole comparison is in-memory. awk ' NR == FNR { if ($0 !~ /^QB-/) next k = $1; $1 = ""; sub(/^ /, ""); W[k] = $0; order[++n] = k; next } /^QB-/ { k = $1; $1 = ""; sub(/^ /, ""); G[k] = $0; seen[k] = 1 } END { for (i = 1; i <= n; i++) { k = order[i]; t = substr(k, 4) if (k ~ /^QB-G/) { printf "BST skip libqb/cmp-%s : ring buffer unavailable on wasm32 (shm_open/mmap MAP_SHARED -> ENOSYS)\n", t; continue } if (k ~ /^QB-H2[3-7]/) { printf "BST skip libqb/cmp-%s : blackbox ring buffer unavailable on wasm32 (ENOSYS)\n", t; continue } if (k ~ /^QB-I/) { printf "BST skip libqb/cmp-%s : event loop unavailable on wasm32 (epoll_create1 -> SIGILL)\n", t; continue } if (k ~ /^QB-J/) { printf "BST skip libqb/cmp-%s : IPC server requires event loop, unavailable on wasm32\n", t; continue } if (!(k in seen)) { printf "BST not ok libqb/cmp-%s : absent from target output\n", t; continue } if (G[k] == W[k]) printf "BST ok libqb/cmp-%s\n", t else printf "BST not ok libqb/cmp-%s : want:%s got:%s\n", t, W[k], G[k] } } ' "$NATIVE" "$out.out" # --- the values that legitimately differ, asserted rather than ignored ------- qb_get() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$out.out"; } check_eq libqb/probe-sizeof-ptr 4 "$(qb_get QBX-A90-sizeof-ptr)" check_eq libqb/probe-sizeof-long 4 "$(qb_get QBX-A91-sizeof-long)" check_eq libqb/probe-sizeof-size-t 4 "$(qb_get QBX-A92-sizeof-size-t)" # ILP32 with 64-bit time: this arm is built _TIME_BITS=64, so time_t stays 8 # even though long is 4. That combination is exactly what broke nicstat's # printf (BRINTOS-202), so it is pinned rather than assumed. check_eq libqb/probe-sizeof-time-t 8 "$(qb_get QBX-A93-sizeof-time-t)" # libc's strerror text is its own, but it must not be empty. sterr=$(qb_get QBX-F90-strerror-enoent-text) if [ -n "$sterr" ] && [ "$sterr" != "(null)" ]; then bst_ok libqb/strerror-text-nonempty else bst_notok libqb/strerror-text-nonempty "qb_strerror_r(ENOENT) gave '${sterr:-}'"; fi # --- anchored: values libqb's own contract fixes, not the control ------------ # These duplicate comparisons above ON PURPOSE. They are the invariants a # consumer actually depends on, written as literals, so a control regenerated # against a broken build could not quietly move them. # # G: ring buffer -- shm_open/mmap MAP_SHARED -> ENOSYS on wasm32. bst_skip libqb/anchor-rb-roundtrip "ring buffer unavailable on wasm32 (ENOSYS)" bst_skip libqb/anchor-rb-content "ring buffer unavailable on wasm32 (ENOSYS)" bst_skip libqb/anchor-rb-fifo "ring buffer unavailable on wasm32 (ENOSYS)" bst_skip libqb/anchor-rb-oversize "ring buffer unavailable on wasm32 (ENOSYS)" bst_skip libqb/anchor-rb-from-file "ring buffer unavailable on wasm32 (ENOSYS)" # Logging reached a target by both routes: the external-source call, and the # qb_log() macro, which needs the linker's __start___verbose/__stop___verbose # section symbols to survive wasm-ld. check_eq libqb/anchor-log-external yes "$(qb_get QB-H07-external-captured)" check_eq libqb/anchor-log-macro yes "$(qb_get QB-H15-macro-captured)" check_eq libqb/anchor-log-filtered yes "$(qb_get QB-H10-below-threshold-dropped)" # I: event loop -- epoll_create1 executes wasm 'unreachable' -> SIGILL on wasm32. bst_skip libqb/anchor-loop-job "event loop unavailable on wasm32 (epoll_create1 -> SIGILL)" bst_skip libqb/anchor-loop-timer "event loop unavailable on wasm32 (epoll_create1 -> SIGILL)" bst_skip libqb/anchor-loop-payload "event loop unavailable on wasm32 (epoll_create1 -> SIGILL)" # J: IPC server calls qb_loop_create() in child process -- also SIGILL on wasm32. bst_skip libqb/anchor-ipc-connect "IPC server requires event loop, unavailable on wasm32" bst_skip libqb/anchor-ipc-echo "IPC server requires event loop, unavailable on wasm32" bst_skip libqb/anchor-ipc-clean-exit "IPC server requires event loop, unavailable on wasm32" # The trie's prefix iterator is its reason for existing. check_eq libqb/anchor-trie-prefix net.eth0.rx,net.eth0.tx "$(qb_get QB-D06-trie-prefix-iter)" # --- the shipped PROGRAM, on a blackbox this run actually produced ----------- # qb-blackbox is the only consumer of libqb that ships as a binary, and this is # the one case that drives the whole stack the way a user would: the probe wrote # a flight-recorder ring buffer to a file, and the program reads it back. It # exercises the shm mapping, the ring buffer, the log formatter and the file # round trip in a single observation. # # qb-blackbox reports "qb_rb_chunk_read failed: Connection timed out" when it # reaches the end of the records -- that is its normal termination and not an # error, so the assertion is on the RECORDS, not on the exit status. _bb_enabled=$(qb_get QB-H24-blackbox-enable) if [ ! -x "$blackbox" ]; then bst_skip libqb/blackbox-roundtrip "$blackbox not on this image" elif [ "$_bb_enabled" != "ok" ]; then bst_skip libqb/blackbox-roundtrip "blackbox ring buffer not available on this platform (H24=$_bb_enabled)" bst_skip libqb/blackbox-callsite "blackbox ring buffer not available on this platform (H24=$_bb_enabled)" elif [ ! -s "$bb" ]; then bst_skip libqb/blackbox-roundtrip "ring buffer unavailable on this platform -- probe wrote no blackbox" else bst_ok libqb/blackbox-file-written bbout=$(timeout 60 "$blackbox" "$bb" 2>&1) if bst_execfail "$bbout"; then bst_unrun libqb/blackbox-roundtrip "$bbout" else case "$bbout" in *blackbox-record-one*) case "$bbout" in *blackbox-record-two*) bst_ok libqb/blackbox-roundtrip ;; *) bst_notok libqb/blackbox-roundtrip "only the first record came back: $(_bst_1line "$bbout")" ;; esac ;; *) bst_notok libqb/blackbox-roundtrip "neither record recovered: $(_bst_1line "$bbout")" ;; esac # The formatter puts the emitting function and line in front of the message; # a blackbox that recovers the text but not the callsite is half-working. case "$bbout" in *group_log*) bst_ok libqb/blackbox-callsite ;; *) bst_notok libqb/blackbox-callsite "no callsite in the dump: $(_bst_1line "$bbout")" ;; esac fi fi rm -f "$out.out" "$out.err" "$bb"