. "$BST_LIB" # libubox 1.0.1+git -- "C utility functions for OpenWrt" (meta-oe # recipes-devtools/libubox, a git recipe pinned at SRCREV 07413cce). What ships # is /usr/bin/jshn and /usr/share/libubox/jshn.sh, the eighteen libubox/ # headers, and three archives: libubox.a (blob, blobmsg, uloop, list, avl, # vlist, kvlist, safe_list, runqueue, ustream, usock, md5, base64, ulog), # libblobmsg_json.a (the json-c bridge) and libjson_script.a. # # --------------------------------------------------------------------------- # WHY THIS SUITE RUNS A PROBE # # The ticket's requirements are in-process by construction -- "build a blobmsg # message, serialise it, parse it back and compare field by field; then convert # it to JSON through json-c and back", and "the uloop timer case runs to a fixed # deadline with no external event source". None of that is reachable from a # shell. jshn is genuinely exercisable and is exercised below, but it only # reaches the JSON bridge; nothing on the command line touches uloop, avl or # kvlist at all. # # libubox DOES ship tests/ (test-blobmsg.c, test-avl.c, test-list.c, ...) and # they are gated behind IF(UNIT_TESTING) in CMakeLists.txt, which nothing in the # meta-oe recipe sets; tests/CMakeLists.txt then drives them through a cram # harness wanting python and a fixture tree. So coverage is constructed the way # libev's and libqb's were: data/libubox-probe (src/libubox_probe.c), cross- # linked with wasmcc-glibc against THE VERY ARCHIVES THAT SHIP. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86_64 CONTROL # # data/libubox-native.txt is the same probe's output against a build of the same # upstream commit with the same four patches and the same cmake options, from # src/libubox_mknative.sh. Every LU- line is a boolean, a count, an ordering # string, a serialised length, a digest or a literal payload -- nothing that may # legitimately differ between two machines -- so each must match byte for byte. # The blob lengths and the blob digest can be in that set because blob's wire # format is defined big-endian with a 4-byte header and 4-byte padding, i.e. it # is architecture-independent by design; a difference there is a real finding, # not an ILP32-vs-LP64 artefact. The values that DO legitimately differ carry a # LUX- prefix, are kept out of the control, and are asserted separately below. # # --------------------------------------------------------------------------- # WHAT A RED MEANS, BY GROUP # # blob/parse/roundtrip pure computation over a byte buffer, no syscall. A red # is libubox miscompiled on this arm and nothing else. # json the same, plus json-c. A red here with blob green is # the bridge or json-c, not the serialiser. # list/avl/kv pure computation. Same reading as blob. # uloop THE ONLY GROUP THAT TOUCHES THE KERNEL: uloop_init() # calls epoll_create(). A red or an n/a here beside a # green everything-else is the platform, not the port. # jshn the shipped program, end to end. # # --------------------------------------------------------------------------- # THE EXPECTED SKIPS, AND THEY ARE THE PLATFORM'S # # Every libubox/*uloop* case is skipped on this image, held by # brintos/linux#53: epoll_create and epoll_create1 both return ENOSYS on the # wasm32 kernel, so uloop_init() cannot succeed. The same issue holds kmod's # ptest comparison and lighttpd's default event handler. The skip is conditional # on the probe reporting exactly that errno -- a signal, or any other errno, # stays red -- so the day #53 lands these become real assertions with no edit. # # --------------------------------------------------------------------------- # THE ONE EXPECTED NON-GREEN, AND IT IS UPSTREAM'S' # # libubox/json-roundtrip-exact is 0, HERE AND ON x86_64. blobmsg_json.c:70 # converts every JSON integer with blobmsg_add_u32(b, name, # json_object_get_int(obj)); json_object_get_int() returns int32_t and json-c # saturates, so a JSON integer above 2^31-1 comes back as 2147483647 narrowed # into a blobmsg INT32. The native control carries the identical clamped value, # which is what establishes it as an upstream libubox bug rather than this # arm's. It is asserted as the value it produces (libubox/json-int64-clamp) so # the cause is stated rather than inferred, and libubox/json-roundtrip-no64 # repeats the identical round trip with only that field removed -- that one is # the real assertion and it is exact. lib=/usr/lib/libubox.a libbj=/usr/lib/libblobmsg_json.a libjs=/usr/lib/libjson_script.a incd=/usr/include/libubox jshn=/usr/bin/jshn jshnsh=/usr/share/libubox/jshn.sh DATA=$BST_DATA NATIVE=$DATA/libubox-native.txt PROBE=$DATA/libubox-probe TMP=${TMPDIR:-/tmp} nl=' ' if [ ! -f "$lib" ]; then bst_skip libubox/present "libubox not installed on this image" exit 0 fi bst_ok libubox/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 libubox/archive "$lib" hasfile libubox/archive-blobmsg "$libbj" hasfile libubox/archive-jsonscript "$libjs" hasfile libubox/header-blob "$incd/blob.h" hasfile libubox/header-blobmsg "$incd/blobmsg.h" hasfile libubox/header-blobmsg-json "$incd/blobmsg_json.h" hasfile libubox/header-uloop "$incd/uloop.h" hasfile libubox/header-list "$incd/list.h" hasfile libubox/header-avl "$incd/avl.h" hasfile libubox/header-kvlist "$incd/kvlist.h" hasfile libubox/header-ustream "$incd/ustream.h" hasfile libubox/jshn-sh "$jshnsh" # The distro is static-only, and libubox is the reason that needed a patch: # upstream declares all three libraries ADD_LIBRARY( SHARED ...), which # BUILD_SHARED_LIBS cannot override. A shared object here means # 0001-cmake-follow-BUILD_SHARED_LIBS-instead-of-hardcoding-.patch stopped # applying and the image grew objects wasmld has no stable ABI for. if [ -e /usr/lib/libubox.so ] || [ -e /usr/lib/libubox.so.1 ] || \ [ -e /usr/lib/libblobmsg_json.so ] || [ -e /usr/lib/libjson_script.so ]; then bst_notok libubox/no-shared-object "a libubox shared object shipped on a static-only distro" else bst_ok libubox/no-shared-object fi # --- symbol census ----------------------------------------------------------- # One name per subsystem, so a partially-linked archive is legible rather than # merely "incomplete". arcsyms=$(grep -aoE 'blob[a-z0-9_]*|uloop_[a-z0-9_]*|avl_[a-z0-9_]*|kvlist_[a-z0-9_]*|ustream_[a-z0-9_]*|md5_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) bjsyms=$(grep -ao 'blobmsg_[a-z0-9_]*' "$libbj" 2>/dev/null | sort -u) hasline libubox/sym-blob-buf-init blob_buf_init "$arcsyms" hasline libubox/sym-blob-nest-end blob_nest_end "$arcsyms" hasline libubox/sym-blobmsg-parse blobmsg_parse "$arcsyms" hasline libubox/sym-blobmsg-field blobmsg_add_field "$arcsyms" hasline libubox/sym-uloop-init uloop_init "$arcsyms" hasline libubox/sym-uloop-timeout uloop_timeout_set "$arcsyms" hasline libubox/sym-uloop-run uloop_run_timeout "$arcsyms" hasline libubox/sym-avl-insert avl_insert "$arcsyms" hasline libubox/sym-avl-delete avl_delete "$arcsyms" hasline libubox/sym-kvlist-set kvlist_set "$arcsyms" hasline libubox/sym-kvlist-get kvlist_get "$arcsyms" hasline libubox/sym-ustream-init ustream_init_defaults "$arcsyms" hasline libubox/sym-md5-begin md5_begin "$arcsyms" hasline libubox/sym-json-format blobmsg_format_json_with_cb "$bjsyms" hasline libubox/sym-json-fromstr blobmsg_add_json_from_string "$bjsyms" # --- the shipped program ----------------------------------------------------- # jshn is the only binary libubox installs, and it exists at all only because # CMakeLists' find_library(json NAMES json-c) probe resolved -- an entire silent # block (jshn + both JSON archives) hangs off that IF(EXISTS). Its absence here # is therefore a specific, known failure and not a packaging oddity. if [ -x "$jshn" ]; then bst_ok libubox/jshn-present # jshn -r parses JSON and emits shell variable assignments; the round trip # through -w reads them back and re-emits JSON. This is the same bridge the # probe drives in-process, reached the way a user would. # jshn takes the document as an ARGUMENT (-r ), not on stdin; piping # it yields "option requires an argument -- 'r'", which is a usage error that # reads exactly like a parse failure and, worse, makes the malformed-input case # below pass for the wrong reason. jout=$(timeout 60 "$jshn" -r '{"alpha":"one","beta":42,"gamma":[7,8]}' 2>&1) if bst_execfail "$jout"; then bst_unrun libubox/jshn-read "$jout" else case "$jout" in *alpha*) bst_ok libubox/jshn-read ;; *) bst_notok libubox/jshn-read "no alpha in jshn -r output: $(_bst_1line "$jout")" ;; esac case "$jout" in *one*) bst_ok libubox/jshn-read-value ;; *) bst_notok libubox/jshn-read-value "no value 'one' in jshn -r output: $(_bst_1line "$jout")" ;; esac case "$jout" in *42*) bst_ok libubox/jshn-read-int ;; *) bst_notok libubox/jshn-read-int "no value 42 in jshn -r output: $(_bst_1line "$jout")" ;; esac fi # a malformed document must be refused rather than half-parsed jbad=$(timeout 60 "$jshn" -r '{"alpha":' 2>&1); jbrc=$? if bst_execfail "$jbad"; then bst_unrun libubox/jshn-bad-json "$jbad" elif [ "$jbrc" -ne 0 ]; then bst_ok libubox/jshn-bad-json else bst_notok libubox/jshn-bad-json "jshn -r accepted a truncated document (rc=$jbrc)" fi else bst_notok libubox/jshn-present "$jshn absent or not executable -- find_library(json NAMES json-c) did not resolve at configure, which drops jshn and both JSON archives silently" fi # --- the library RUNNING, via the cross-linked probe ------------------------- if [ ! -s "$NATIVE" ]; then bst_notok libubox/native-present "$NATIVE missing -- no oracle to compare against" exit 0 fi bst_ok libubox/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. if [ ! -f "$PROBE" ]; then bst_notok libubox/probe-present "$PROBE absent from the image" exit 0 fi _p=$TMP/bst-libubox-probe.$$ cp "$PROBE" "$_p" 2>/dev/null && chmod 0755 "$_p" 2>/dev/null if [ ! -x "$_p" ]; then bst_notok libubox/probe-exec "could not stage an executable copy of $PROBE" rm -f "$_p" exit 0 fi bst_ok libubox/probe-exec out=$TMP/bst-libubox.$$ timeout 300 "$_p" > "$out.out" 2>"$out.err"; prc=$? rm -f "$_p" check_eq libubox/probe-rc 0 "$prc" # Two terminators, because the probe runs uloop last and in a fork: everything # that does not touch the event loop is complete at LU-pre-uloop-done, so a run # carrying the first and not the second failed in uloop and in nothing else. if [ "$(grep -c '^LU-pre-uloop-done 1' "$out.out" 2>/dev/null)" = "1" ]; then bst_ok libubox/probe-complete-core else bst_notok libubox/probe-complete-core "no pre-uloop terminator; last saw: $(_bst_1line "$(tail -n 2 "$out.out" 2>/dev/null)")" fi if [ "$(grep -c '^LU-probe-done 1' "$out.out" 2>/dev/null)" = "1" ]; then bst_ok libubox/probe-complete else bst_notok libubox/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 ~50 awk calls: process # spawn is expensive on this target and the whole comparison is in-memory. lu_get() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$out.out"; } # --- is the event loop reachable on this platform at all? -------------------- # BRINTOS-WORKAROUND: brintos/linux#53 remove -- epoll_create/epoll_create1 are # ENOSYS on the wasm32 kernel, so uloop_init() cannot succeed and every timer # case below is unreachable through no fault of libubox. The branch reports them # as attributed skips instead of eleven reds; delete the ENOSYS arm once #53 # lands and the real assertions in the other arm take over on their own. # # THIS IS NOT A BROKEN THING DOWNGRADED TO A SKIP, and the distinction is the one # the packager guide insists on. libubox's uloop is present, linked and correct # -- the probe's own child exits 0 and reports errno by NAME. What is absent is # the syscall. The suite therefore skips only on the exact, expected errno; a # signal, or any other errno, stays red, because those would be new findings # rather than this known one. _st=$(lu_get LU-uloop-status) _init=$(lu_get LU-uloop-init) if [ "$_st" = "ok" ] && [ "$_init" = "0" ]; then uloop_state=up elif [ "$_st" = "ok" ] && [ "$_init" = "ENOSYS" ]; then uloop_state=enosys else uloop_state=broken fi ULOOP_WHY="epoll_create is ENOSYS on this platform (brintos/linux#53); libubox uloop is built and linked, the syscall is absent" # --- tag-by-tag against the native control ----------------------------------- # One awk over both files rather than a shell loop of ~50 awk calls: process # spawn is expensive on this target and the whole comparison is in-memory. awk -v ustate="$uloop_state" -v uwhy="$ULOOP_WHY" ' NR == FNR { if ($0 !~ /^LU-/) next k = $1; $1 = ""; sub(/^ /, ""); W[k] = $0; order[++n] = k; next } /^LU-/ { k = $1; $1 = ""; sub(/^ /, ""); G[k] = $0; seen[k] = 1 } END { for (i = 1; i <= n; i++) { k = order[i]; t = substr(k, 4) # LU-uloop-status is NOT held out: it is what establishes the probe child # ran to completion, and it must keep being compared in every state. if (ustate == "enosys" && k ~ /^LU-uloop-/ && k != "LU-uloop-status") { printf "BST skip libubox/cmp-%s : %s\n", t, uwhy; continue } if (!(k in seen)) { printf "BST not ok libubox/cmp-%s : absent from target output\n", t; continue } if (G[k] == W[k]) printf "BST ok libubox/cmp-%s\n", t else printf "BST not ok libubox/cmp-%s : want:%s got:%s\n", t, W[k], G[k] } } ' "$NATIVE" "$out.out" # --- the values that legitimately differ, asserted rather than ignored ------- check_eq libubox/probe-sizeof-ptr 4 "$(lu_get LUX-sizeof-ptr)" check_eq libubox/probe-sizeof-long 4 "$(lu_get LUX-sizeof-long)" check_eq libubox/probe-sizeof-size-t 4 "$(lu_get LUX-sizeof-size_t)" # The event loop's wall-clock readings are its own; they are bounded rather than # pinned. An elapsed of 0 would mean uloop_run_timeout returned without waiting # -- a broken loop that the ordering case alone would not catch, because three # timers that all fire instantly still fire in deadline order. _el=$(lu_get LUX-uloop-elapsed-ms) case "$_el" in n/a) bst_skip libubox/uloop-elapsed-bounded "$ULOOP_WHY" ;; ''|*[!0-9]*) bst_notok libubox/uloop-elapsed-bounded "unreadable elapsed: '${_el:-}'" ;; *) if [ "$_el" -ge 85 ] && [ "$_el" -lt 3000 ]; then bst_ok libubox/uloop-elapsed-bounded else bst_notok libubox/uloop-elapsed-bounded "elapsed ${_el}ms outside [85,3000) for a 90ms last deadline"; fi ;; esac # --- anchored: values libubox'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. # # The ticket's first requirement, stated as itself: every field survived the # serialise/parse round trip. check_eq libubox/anchor-roundtrip-fields 12/12 "$(lu_get LU-roundtrip-fields)" check_eq libubox/anchor-roundtrip-exact 1 "$(lu_get LU-roundtrip-exact)" # blob's wire format is architecture-independent; these are the bytes. check_eq libubox/anchor-blob-len 204 "$(lu_get LU-blob-pad-len)" check_eq libubox/anchor-blob-md5 6822d7d6bd41e88aaae6d70233707c6b "$(lu_get LU-blob-md5)" check_eq libubox/anchor-blob-attr-size 4 "$(lu_get LU-sizeof-blob_attr)" # md5 is checked against RFC 1321's own test vector, i.e. an oracle from outside # libubox: a wrong digest above would otherwise read as a broken serialiser. check_eq libubox/anchor-md5-known 900150983cd24fb0d6963f7d28e17f72 "$(lu_get LU-md5-known)" # a policy slot with no matching field must be NULL, not a neighbour check_eq libubox/anchor-parse-absent 0 "$(lu_get LU-parse-absent)" check_eq libubox/anchor-parse-i64 1234567890123 "$(lu_get LU-parse-i64)" # The ticket's second requirement. The full round trip is 0 for the upstream # reason in the header; the field-removed one is the assertion that the bridge # itself is exact, and the clamp is named rather than left as a bare zero. check_eq libubox/json-roundtrip-exact 0 "$(lu_get LU-json-roundtrip-exact)" check_eq libubox/json-int64-clamp 2147483647 "$(lu_get LU-json-int64-clamp)" check_eq libubox/json-roundtrip-no64 1 "$(lu_get LU-json-roundtrip-exact-no64)" check_eq libubox/anchor-json-back-s "libubox on wasm32-hwjs" "$(lu_get LU-json-back-s)" # containers check_eq libubox/anchor-list-order alpha,bravo,charlie,delta,echo "$(lu_get LU-list-order)" check_eq libubox/anchor-avl-order alfa,bravo,delta,kilo,mike,zulu "$(lu_get LU-avl-order)" check_eq libubox/anchor-avl-dup 1 "$(lu_get LU-avl-dup-refused)" check_eq libubox/anchor-kv-order golf,hotel,india "$(lu_get LU-kv-order)" check_eq libubox/anchor-kv-overwrite TWO "$(lu_get LU-kv-overwrite)" # The event loop, stated as the ticket states it: three timers registered out of # deadline order fire in deadline order, a cancelled one never fires, and the # loop ran to the fixed deadline with no external event source. case "$uloop_state" in up) check_eq libubox/anchor-uloop-init 0 "$(lu_get LU-uloop-init)" check_eq libubox/anchor-uloop-order b,a,c "$(lu_get LU-uloop-order)" check_eq libubox/anchor-uloop-fired 3 "$(lu_get LU-uloop-fired)" check_eq libubox/anchor-uloop-cancel 1 "$(lu_get LU-uloop-cancel-honoured)" check_eq libubox/anchor-uloop-deadline 1 "$(lu_get LU-uloop-deadline-met)" ;; enosys) # BRINTOS-WORKAROUND: brintos/linux#53 remove -- delete this arm when the # epoll family is wired and the `up` arm above will assert for real. bst_skip libubox/anchor-uloop-init "$ULOOP_WHY" bst_skip libubox/anchor-uloop-order "$ULOOP_WHY" bst_skip libubox/anchor-uloop-fired "$ULOOP_WHY" bst_skip libubox/anchor-uloop-cancel "$ULOOP_WHY" bst_skip libubox/anchor-uloop-deadline "$ULOOP_WHY" ;; *) # The probe forks uloop precisely so this is reportable: the child died or # uloop_init failed with something OTHER than the known ENOSYS. Either is a # new finding and stays red. bst_notok libubox/anchor-uloop-init "unexpected uloop state: status=$_st init=$_init (known gap is ENOSYS, brintos/linux#53)" bst_notok libubox/anchor-uloop-order "unexpected uloop state: status=$_st init=$_init" bst_notok libubox/anchor-uloop-fired "unexpected uloop state: status=$_st init=$_init" bst_notok libubox/anchor-uloop-cancel "unexpected uloop state: status=$_st init=$_init" bst_notok libubox/anchor-uloop-deadline "unexpected uloop state: status=$_st init=$_init" ;; esac rm -f "$out.out" "$out.err"