# BST-TIMEOUT: 600 # ~150 cases; the probe's guarded groups can each cost 20 s # # of wall clock on a platform whose relative timers stall . "$BST_LIB" # libev 4.33 -- "a full-featured and high-performance event loop that is loosely # modelled after libevent" (meta-oe recipes-connectivity/libev). What ships is # /usr/lib/libev.a plus ev.h and ev++.h. There is no program, no pkg-config file # and no shared object. # # --------------------------------------------------------------------------- # WHY THIS SUITE RUNS A PROBE # # libev installs NO PROGRAM and ships NO TEST SUITE -- the 4.33 tarball has no # tests/ directory at all, so unlike libsmi (whose CLI is its own coverage) or # libevent (whose upstream ptest is), there is nothing here that could exercise # the library from the target's own command line. A suite that only checked the # payload's shape would go green on a library that returns silently wrong # answers, which is the BRINTOS-217 lesson: oniguruma compiled and packaged # perfectly and then handed every caller wrong offsets. # # data/libev-probe (src/libev_probe.c) is the consumer that closes that gap. It # is cross-linked with wasmcc-glibc against THE VERY libev.a THAT SHIPS -- the # suite re-checks that identity below rather than asserting it -- and it drives # the loop the way the ticket scopes it: over timers and a pipe, with no socket # peer and no device. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86_64 CONTROL # # data/libev-native.txt is the same probe's output against a build of the # IDENTICAL tarball (sha256 507eb7b8..., no patches -- meta-oe's recipe carries # none) on x86_64, generated by src/libev_mknative.sh. The probe prints only # what cannot legitimately differ between two machines: booleans, counts, # callback-order strings and literal payload text. No pointer, no pid, no clock # reading, no sizeof. So every EV- line must match byte for byte, and the three # values that DO legitimately differ -- the pointer width and the backend # bitmasks -- are printed with an EVX- prefix, kept out of the control, and # asserted separately below. # # --------------------------------------------------------------------------- # THE PROBE SPLITS PAST-DUE TIMERS FROM RELATIVE ONES, AND SO DOES THIS SUITE # # Group F arms timers with a NEGATIVE timeout, so `at` is already in the past and # the order they fire in is fixed by libev's timer heap -- a result no clock can # influence. Group G arms real relative timeouts, which only work if the # platform's clock advances and its poll timeout is honoured; this arm has an # open defect about exactly that (relative timer accuracy, # brintos/linux-in-the-browser#120). Keeping them apart is what lets a reader # tell "libev is broken here" from "the clock is": a red F is the library's, a # red G beside a green F is the platform's. # # G's loops each carry an always-ready guard fd, so libev polls with a zero # timeout and a platform whose relative timers never fire spins out to a bound # and says so, instead of blocking until the runner kills the suite. lib=/usr/lib/libev.a incd=/usr/include DATA=$BST_DATA NATIVE=$DATA/libev-native.txt PROBE=$DATA/libev-probe TMP=${TMPDIR:-/tmp} nl=' ' if [ ! -f "$lib" ]; then bst_skip libev/present "libev not installed on this image" exit 0 fi bst_ok libev/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 libev/archive "$lib" hasfile libev/header-ev "$incd/ev.h" hasfile libev/header-evpp "$incd/ev++.h" # The distro is static-only: upstream packages ${libdir}/libev.so.*, which must # match nothing. A shared object here means --disable-shared stopped being # honoured and the image grew a package it does not want. if [ -e /usr/lib/libev.so ] || [ -e /usr/lib/libev.so.4 ]; then bst_notok libev/no-shared-object "a libev shared object shipped on a static-only distro" else bst_ok libev/no-shared-object fi # libev 4.33 installs no pkg-config file -- its Makefile.am has no # pkgconfig_DATA at all. Pinned so a release that starts shipping one is noticed # and the image can then carry it, rather than the absence being forgiven for # ever by a case nobody wrote. if [ -f /usr/lib/pkgconfig/libev.pc ]; then bst_notok libev/upstream-no-pkgconfig \ "libev.pc is now installed -- upstream added pkgconfig_DATA; re-read this case and ship it" else bst_ok libev/upstream-no-pkgconfig fi # --- the libevent collision, which this image is the first to be able to hit -- # # libev ships TWO things in one archive: the library proper (ev.o) and a # libevent-1.x COMPATIBILITY LAYER (event.o) that defines event_init, event_add, # event_del, event_base_new, event_base_dispatch and ~25 more. The recipe's # do_install:append deletes libev's own event.h with the comment "avoid # conflicting with libevent" -- and that removes the HEADER conflict only. The # SYMBOLS stay in libev.a. # # Until BRINTOS-398 landed, that was theoretical on this image. libevent 2.1.12 # now ships here too, so a consumer that links -lev and -levent gets duplicate # definitions of the same names from two unrelated implementations. That is # upstream libev's shape, not a defect of this arm or this port, and it fails # LOUDLY at link time, which is the correct failure -- so it is pinned here # rather than worked around. arcsyms=$(grep -ao 'ev_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) evtsyms=$(grep -ao 'event_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) # libev's own event.h must NOT be installed. Another package's may be -- and # libevent's is -- so this is decided by CONTENT, not by the path existing. if [ -f "$incd/event.h" ] && grep -q 'libevent compatibility header' "$incd/event.h" 2>/dev/null; then bst_notok libev/event-h-removed \ "$incd/event.h is libev's own compatibility header -- do_install:append's rm did not take" else bst_ok libev/event-h-removed fi hasline libev/compat-event-init event_init "$evtsyms" hasline libev/compat-event-base-new event_base_new "$evtsyms" hasline libev/compat-event-dispatch event_dispatch "$evtsyms" # The hazard itself, stated as a measurement: if libevent's archive is on this # image too, the two carry the same names. A future libev that drops the compat # layer, or a future image that drops libevent, flips this case and the note # above should be re-read. if [ -f /usr/lib/libevent.a ]; then if grep -aq 'event_base_dispatch' /usr/lib/libevent.a 2>/dev/null; then bst_ok libev/libevent-symbol-overlap-known else bst_notok libev/libevent-symbol-overlap-known \ "libevent.a no longer carries event_base_dispatch -- re-read the collision note" fi else bst_skip libev/libevent-symbol-overlap-known "libevent.a not on this image" fi # --- every function the header declares is in the archive -------------------- # Read out of ev.h AS IT SHIPS rather than from a hardcoded list, so a release # that adds an entry point is checked rather than permanently forgiven. Only # EV_API_DECL lines are consulted: everything else ev.h offers a caller # (ev_io_init, ev_is_active, ev_priority, ...) is a MACRO, and a macro is not # expected in an archive. hdrsyms=$(grep -E '^EV_API_DECL' "$incd/ev.h" 2>/dev/null \ | grep -oE '\bev_[a-z0-9_]*[[:space:]]*\(' \ | sed -e 's/[[:space:]]*(//' | sort -u) # ev_walk is declared under `#if EV_WALK_ENABLE` and defined under the same # guard, and ev.h sets EV_WALK_ENABLE to 0 ("not yet"). grep cannot see a # preprocessor conditional, so the census above picks the declaration up; the # archive correctly does not carry it. This is upstream's own feature switch # working as intended -- NOT a missing symbol -- so it is excluded by name and # then pinned on its own below, where a release that enables it is noticed. GUARDED_OFF="ev_walk" nsym=0; nmiss=0; missing="" for f in $hdrsyms; do case "$nl$(printf '%s\n' $GUARDED_OFF)$nl" in *"$nl$f$nl"*) continue ;; esac nsym=$((nsym + 1)) case "$nl$arcsyms$nl" in *"$nl$f$nl"*) ;; *) nmiss=$((nmiss + 1)); missing="$missing $f" ;; esac done if [ "$nsym" -ge 60 ]; then bst_ok libev/symbols-counted else bst_notok libev/symbols-counted "only $nsym ev_* entry points found in ev.h"; fi if [ "$nmiss" -eq 0 ]; then bst_ok libev/symbols-complete else bst_notok libev/symbols-complete "$nmiss of $nsym missing:$(_bst_1line "$missing")"; fi case "$nl$arcsyms$nl" in *"${nl}ev_walk$nl"*) bst_notok libev/upstream-ev-walk-disabled \ "ev_walk is now DEFINED -- upstream enabled EV_WALK_ENABLE; drop it from GUARDED_OFF" ;; *) bst_ok libev/upstream-ev-walk-disabled ;; esac # One name per watcher family, so a partially-linked archive is legible rather # than merely "incomplete". hasline libev/sym-run ev_run "$arcsyms" hasline libev/sym-loop-new ev_loop_new "$arcsyms" hasline libev/sym-io-start ev_io_start "$arcsyms" hasline libev/sym-timer-start ev_timer_start "$arcsyms" hasline libev/sym-periodic ev_periodic_start "$arcsyms" hasline libev/sym-idle-start ev_idle_start "$arcsyms" hasline libev/sym-prepare ev_prepare_start "$arcsyms" hasline libev/sym-check-start ev_check_start "$arcsyms" hasline libev/sym-async-send ev_async_send "$arcsyms" hasline libev/sym-signal-start ev_signal_start "$arcsyms" hasline libev/sym-child-start ev_child_start "$arcsyms" hasline libev/sym-stat-start ev_stat_start "$arcsyms" hasline libev/sym-embed-start ev_embed_start "$arcsyms" hasline libev/sym-verify ev_verify "$arcsyms" # --- the library RUNNING, via the cross-linked probe ------------------------- if [ ! -s "$NATIVE" ]; then bst_notok libev/native-present "$NATIVE missing -- no oracle to compare against" exit 0 fi bst_ok libev/native-present # The probe must be the shipped archive's own consumer. do_install stages data/* # at 0644 and the deployed rootfs is read-only at browser runtime, so it cannot # be executed where it lies. _p=$TMP/bst-libev-probe.$$ cp "$PROBE" "$_p" 2>/dev/null && chmod 0755 "$_p" 2>/dev/null if [ ! -x "$_p" ]; then bst_notok libev/probe-exec "could not stage an executable copy of $PROBE" rm -f "$_p" exit 0 fi bst_ok libev/probe-exec out=$TMP/bst-libev.$$ timeout 240 "$_p" > "$out.out" 2>"$out.err"; prc=$? rm -f "$_p" check_eq libev/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 [ "$(tail -n 1 "$out.out" 2>/dev/null)" = "EV-Z01-end ALL-LIBEV-DONE" ]; then bst_ok libev/probe-complete else bst_notok libev/probe-complete "no terminator; last saw: $(_bst_1line "$(tail -n 2 "$out.out" 2>/dev/null)")" fi # libev's own syserr hook prints this and exits 9 rather than abort()ing, so a # failed syscall inside the library names itself instead of looking like a hang. if grep -q '^EVX-syserr ' "$out.out" 2>/dev/null; then bst_notok libev/no-syserr "$(_bst_1line "$(grep '^EVX-syserr ' "$out.out")")" else bst_ok libev/no-syserr fi # --- tag-by-tag against the native control ----------------------------------- # One awk over both files rather than a shell loop of ~116 awk calls: process # spawn is expensive on this target and the whole comparison is in-memory. awk ' NR == FNR { if ($0 !~ /^EV-/) next k = $1; $1 = ""; sub(/^ /, ""); W[k] = $0; order[++n] = k; next } /^EV-/ { 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 in seen)) { printf "BST not ok libev/cmp-%s : absent from target output\n", t; continue } if (G[k] == W[k]) printf "BST ok libev/cmp-%s\n", t else printf "BST not ok libev/cmp-%s : want:%s got:%s\n", t, W[k], G[k] } } ' "$NATIVE" "$out.out" # --- the values that legitimately differ, asserted rather than ignored ------- ev_get() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$out.out"; } check_eq libev/probe-pointer-width 32 "$(ev_get EVX-pointer-width)" # Which backends the kernel offers is this arm's answer to give, so it is not # compared against the control -- but it must be COHERENT, and libev must have # picked one of them (EV-B02/B03 assert the pick; these two assert the set is # sane). A supported set of zero would mean libev found no way to poll at all # and every loop above ran on nothing. sup=$(ev_get EVX-backends-supported) rec=$(ev_get EVX-backends-recommended) case "$sup" in 0x00000000|"") bst_notok libev/backends-nonempty "ev_supported_backends() = '${sup:-}'" ;; *) bst_ok libev/backends-nonempty ;; esac case "$rec" in 0x00000000|"") bst_notok libev/backends-recommended-nonempty "ev_recommended_backends() = '${rec:-}'" ;; *) bst_ok libev/backends-recommended-nonempty ;; esac # --- anchored: values libev's own contract fixes, not the control ------------ # These duplicate four of the comparisons above ON PURPOSE. They are the # invariants a consumer actually depends on, written as literals, so that a # control regenerated against a broken build could not quietly move them. # # The timer heap orders by expiry, NOT by registration: the probe registers # past-due timers least-past first (-1, -2, -3) and a correct queue fires them # most-past first. This is the single most load-bearing thing libev does. check_eq libev/anchor-past-timer-count 3 "$(ev_get EV-F01-past-timers-fired)" check_eq libev/anchor-past-timer-order abc "$(ev_get EV-F02-past-timer-order)" # Same expiry, different priority -> the pending queue orders by priority. check_eq libev/anchor-priority-order hl "$(ev_get EV-F09-priority-order)" # One loop iteration is prepare -> poll -> (queued events, drained from the END # of the per-priority pending array, so checks precede the io and idle events # queued before them). Two iterations of that is PCIPCD. check_eq libev/anchor-callback-order PCIPCD "$(ev_get EV-E01-callback-order)" # The pipe carried real bytes, not just a readiness bit. check_eq libev/anchor-pipe-payload hello "$(ev_get EV-D09-read-payload)" check_eq libev/anchor-pipe-eof 1 "$(ev_get EV-D18-eof-read-returned-zero)" # The compat layer reports libev's own version and names itself. check_eq libev/anchor-compat-version 4.33 "$(ev_get EV-J08-compat-event-get-version)" check_eq libev/anchor-compat-method libev "$(ev_get EV-J09-compat-event-get-method)" rm -f "$out.out" "$out.err"