# BST-TIMEOUT: 300 # two short binaries; ~230 cases, all reported fork-free . "$BST_LIB" # libtraceevent 1.7.3 (poky meta/recipes-kernel/libtraceevent, SRCREV dd148189) # -- the library that reads a tracefs event `format` description and uses it to # decode the raw binary records the kernel's ring buffer produces. # # THE EVIDENCE IS UPSTREAM'S OWN TEST PROGRAMME, and that is the whole reason # this package was worth porting ahead of a library with none. utest/ builds # trace-utest, eleven CUnit cases over forty-one assertions, and every one of # them is a PARSER case rather than an API smoke test: it hands tep_parse_format # a real `format` file, hands tep_print_event a real binary record, and compares # the DECODED TEXT against a literal. That question -- "do the shipped tests # cover the parser or only the API?" -- is the one the ticket said to check # rather than assume, and the answer is the parser, so the fallback of writing # our own decode cases was not needed for event-parse.c. # # It WAS needed for kbuffer-parse.c. `grep -c kbuffer utest/*.c` is 0 in both # files: the 848 lines that walk a ring-buffer sub-buffer and hand out the # records event-parse then decodes have no upstream coverage at all. That is # also the file where a 32-bit port could actually go wrong -- it reads a # `commit` word whose width IS the traced kernel's long size (4 or 8, chosen by # the caller, NOT by this machine) and computes every record offset by casting # pointers to unsigned long. So the recipe builds a probe that lays out # sub-buffers by hand at both long sizes and both byte orders, walks them, and # prints one KEY=VALUE fact per line. # # THE ORACLE IS A NATIVE x86-64 BUILD OF THE IDENTICAL SOURCE at the same # SRCREV, run on the build host, captured verbatim into # data/libtraceevent-utest-native.txt and data/libtraceevent-kbuffer-native.txt. # Nothing below is a transcribed number -- the suite joins the target's output # against those two captures key by key, so a value can never drift out of step # with the run it came from. # # AND THE COMPARISON IS A TRUE ORACLE, not a convenience. Every value on both # sides is a property of the tracefs record format or the ring-buffer page # layout, which the kernel fixes in bytes: no pointer width, no sizeof, no # address and no timing appears in any of it. The utest's own sizeof cases make # that explicit -- test_parse_sizeof() calls tep_set_long_size(tep, 4) and then # (tep, 8) rather than reading this machine's long, so BOTH word sizes are # exercised on a 32-bit target. Nothing here is ALLOWED to differ between the # x86-64 control and wasm32, and a single disagreement is a defect on this arm # rather than a platform difference to reason about. # # THE ONE DELIBERATE EXCEPTION is SAMEHOST_*, reported as diagnostics and never # as a case. kbuffer_alloc(KBUFFER_LSIZE_SAME_AS_HOST) asks uname(2) whether the # machine name contains "64" and picks the traced long size from that, so it is # 8 on the control and whatever this arm's uname says here. It is a property of # the arm, not of the parser, and asserting it would red a correct port. # # TRACE-UTEST'S EXIT STATUS IS NOT ITS VERDICT, and this suite deliberately does # not use it. utest/trace-utest.c ends `CU_basic_run_tests(); CU_cleanup_registry(); # return 0;` -- there is no path on which it returns non-zero. Measured, not read # off the source: a native build with one expected string mutated # ("irq=0 handler=hello" -> "...XXXXX") reports two failed tests and STILL exits # 0. So the verdict is taken from the per-test lines and the Run Summary counts # below, which also catch the case an exit status could never see -- a run that # was truncated part way and whose remaining cases simply never appeared. # # SPAWN BUDGET. exec on this arm is a fresh wasm module instantiation, ~2-3 s # apiece, so the file runs each binary exactly once, joins all six inputs in ONE # awk pass that emits a pre-computed " " triple per case, and the # shell loop that reports them spawns nothing at all (check_eq forks only on a # failure). T=/usr/libexec/libtraceevent-tests utest=$T/trace-utest probe=$T/libtraceevent-kbuffer-probe lib=/usr/lib/libtraceevent.a pc=/usr/lib/pkgconfig/libtraceevent.pc inc=/usr/include/traceevent tmp=${TMPDIR:-/tmp}/bst-libtraceevent.$$ mkdir -p "$tmp" 2>/dev/null || tmp=/tmp # ----------------------------------------------------------- what shipped ---- # ${PN} is empty on this arm for the usual static-only reason -- no # libtraceevent.so is built, so FILES:${PN} matches nothing and the main package # is never emitted. The library reaches the image as -dev + -staticdev + -tests. check_rc libtraceevent/utest-present 0 test -x "$utest" check_rc libtraceevent/probe-present 0 test -x "$probe" check_rc libtraceevent/ar-present 0 test -s "$lib" check_rc libtraceevent/pc-present 0 test -f "$pc" for h in event-parse event-utils kbuffer trace-seq; do check_rc "libtraceevent/hdr-$h" 0 test -s "$inc/$h.h" done # ...and no plugin may ship: they are dlopen'd out of this directory and this arm # has neither dlopen nor a side-module ABI, so the recipe builds them static and # installs none. A .so appearing here would mean the patch stopped applying. check_rc libtraceevent/no-plugins-dir 1 test -d /usr/lib/traceevent/plugins # ------------------------------------------------------- run them, once ------ uout=$tmp/utest.out pout=$tmp/probe.out "$utest" > "$uout" 2>&1 urc=$? "$probe" > "$pout" 2>&1 prc=$? check_eq libtraceevent/probe-rc 0 "$prc" # --------------------------------------------------- the archive, flattened --- # The 76-gmp / 77-cunit idiom: one `tr` over the whole archive, then whole-token # lookups. Each member's name lives in that member's own header, so the head of # the file would not carry them all. idx=$tmp/ar.idx # Forked: a top-level redirect whose source is the rootfs ends the suite silently # under the guest kernel (brintos/linux#73), taking every case below with it. ( tr -c '\40-\176\n' ' ' < "$lib" > "$idx" 2>/dev/null ) # ------------------------------------------------------- the single join ------ # 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 7814 B on # its own. Passed inline it made an 8741 B argv and the exec died with # [hardwarejs] user[52] error: argv block (8741B) exceeds __wasm32_args_buf (8192B) # -- the suite printing ==BST-BEGIN== and then nothing at all, since the join that # produces every verdict never ran, which reads on the host as a wedged guest # rather than as a failure (brintos/hardwarejs#15). `awk -f` leaves argv a few # hundred bytes. 77-libpcre carries the same constraint for the same reason. cat > "$tmp/join.awk" <<'JOINAWK' function slug(s, t) { t = s; gsub(/[^A-Za-z0-9]+/, "-", t); sub(/-$/, "", t); return t } function emit(id, want, got) { print id, want, got } # A CUnit verbose line is " Test: ...passed" # with no separator before the verdict, so the decoded text is what sits # between " ..." and the trailing six-character verdict. function cunit(line, arr, p, name, rest, v) { sub(/^ *Test: /, "", line) p = index(line, " ...") if (p == 0) return 0 name = substr(line, 1, p - 1) rest = substr(line, p + 4) v = substr(rest, length(rest) - 5) if (v == "passed" || v == "FAILED") rest = substr(rest, 1, length(rest) - 6) else v = "?" arr["name"] = name; arr["out"] = rest; arr["verdict"] = v return 1 } function summary(line, tag, arr) { if (line !~ /^ *(suites|tests|asserts) /) return arr[$1 "-total"] = $2 arr[$1 "-ran"] = $3 arr[$1 "-passed"] = $4 arr[$1 "-failed"] = $5 } FILENAME == UORACLE { if ($0 ~ /^ *Test: /) { if (cunit($0, a)) { on[++no] = a["name"]; oout[a["name"]] = a["out"]; overd[a["name"]] = a["verdict"] } } else summary($0, "o", osum) next } FILENAME == UGOT { if ($0 ~ /^ *Test: /) { if (cunit($0, a)) { gout[a["name"]] = a["out"]; gverd[a["name"]] = a["verdict"]; ng++ } } else summary($0, "g", gsum) next } FILENAME == PORACLE { if ($0 ~ /^[A-Z][A-Z0-9_]*=/) { k = substr($0, 1, index($0, "=") - 1); pv[k] = substr($0, index($0, "=") + 1); if (k !~ /^SAMEHOST_/) pk[++npk] = k } next } FILENAME == PGOT { if ($0 ~ /^[A-Z][A-Z0-9_]*=/) { k = substr($0, 1, index($0, "=") - 1); gv[k] = substr($0, index($0, "=") + 1); ngv++ } next } FILENAME == IDX { idx = idx " " $0; next } 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: */, ""); pccfl = $0 } next } # Whole-token lookup for SYMBOLS: a bare index() would report tep_free present # on an archive carrying only tep_free_format, which is the difference between # "the module compiled" and "it did not". function has(tok) { return index(" " idx " ", " " tok " ") > 0 } # MEMBER names are not whole tokens. ar stores a short name in the 16-byte # header field as "/" space-padded -- and for a name that fills the field # exactly (event-parse.c.o/) the mtime digits follow with no separator at all, # while a name too long for it (event-parse-api.c.o/) lives in the "//" string # table instead. So members are matched as the substring "/", which is # unambiguous because every one of them ends ".c.o/" and none is a prefix of # another. function hasm(tok) { return index(idx, tok "/") > 0 } END { # ---- upstream unit tests: verdict AND decoded text, per case ---- # The decoded text is the assertion that matters. A verdict alone would # still pass if the library printed something else and the test had been # weakened; the text is what the x86-64 control actually produced. for (i = 1; i <= no; i++) { n = on[i]; s = slug(n) emit("libtraceevent/utest-" s, overd[n], (n in gverd) ? gverd[n] : "MISSING") emit("libtraceevent/utest-" s "-decode", oout[n], (n in gout) ? gout[n] : "MISSING") } # Every case the control ran must have run here: a short run whose cases all # pass is not a pass, and no exit status of trace-utest could ever say so. emit("libtraceevent/utest-cases-run", no "", ng "") # ...and the summary the programme computes for ITSELF, so a disagreement # between the per-case lines and the totals is caught too. split("suites-total suites-ran suites-failed tests-total tests-ran tests-passed tests-failed asserts-total asserts-ran asserts-passed asserts-failed", sk, " ") for (i in sk) emit("libtraceevent/utest-summary-" sk[i], osum[sk[i]] "", (sk[i] in gsum) ? gsum[sk[i]] "" : "MISSING") # ---- the kbuffer probe, key by key against the control ---- for (i = 1; i <= npk; i++) { k = pk[i] emit("libtraceevent/kbuffer-" tolower(k), pv[k], (k in gv) ? gv[k] : "MISSING") } emit("libtraceevent/kbuffer-facts-emitted", npk + 2 "", ngv "") # ---- the two arms that must agree with each other ---- # A page built big-endian and read big-endian has to decode identically to # the little-endian pair; this catches a byte-order bug that broke BOTH # halves the same way and so slipped past the per-key oracle above. be_ok = 1; l_ok = 1 for (k in gv) { if (k ~ /^L4LE_/) { k2 = k; sub(/^L4LE_/, "L4BE_", k2); if (gv[k] != gv[k2]) be_ok = 0 } if (k ~ /^L8LE_/) { k2 = k; sub(/^L8LE_/, "L8BE_", k2); if (gv[k] != gv[k2]) be_ok = 0 } } emit("libtraceevent/kbuffer-endian-symmetric", "1", be_ok "") # The 4- and 8-byte long arms must differ in EXACTLY the four places the # wider commit word moves, and nowhere else. nd = 0 for (k in gv) { if (k ~ /^L4LE_/) { k2 = k; sub(/^L4LE_/, "L8LE_", k2); if (gv[k] != gv[k2]) nd++ } } emit("libtraceevent/kbuffer-longsize-deltas", "4", nd "") # ---- the payload ---- emit("libtraceevent/pc-name", "libtraceevent", pcname "") emit("libtraceevent/pc-version", "1.7.3", pcver "") emit("libtraceevent/pc-libs", "-L${libdir} -ltraceevent", pclibs "") # One archive member per compilation unit src/meson.build names. A partial # compile that produced event-parse.o but not kbuffer-parse.o fails here and # not at the first consumer. no_m = split("event-parse-api.c.o event-parse.c.o event-plugin.c.o kbuffer-parse.c.o parse-filter.c.o parse-utils.c.o tep_strerror.c.o trace-seq.c.o", want_o, " ") for (i = 1; i <= no_m; i++) emit("libtraceevent/ar-member-" want_o[i], want_o[i], (hasm(want_o[i]) ? want_o[i] : "MISSING")) # One exported entry point per module a consumer actually calls, so every # member above is reached by at least one of these. ns = split("tep_alloc tep_free tep_parse_format tep_parse_event tep_print_event " \ "tep_find_any_field tep_print_field_content tep_set_long_size " \ "tep_get_long_size tep_strerror tep_load_plugins tep_register_event_handler " \ "tep_filter_alloc tep_filter_add_filter_str trace_seq_init trace_seq_reset " \ "trace_seq_printf trace_seq_do_printf trace_seq_destroy " \ "kbuffer_alloc kbuffer_free kbuffer_load_subbuffer kbuffer_read_event " \ "kbuffer_next_event kbuffer_timestamp kbuffer_curr_index kbuffer_curr_offset " \ "kbuffer_curr_size kbuffer_event_size kbuffer_missed_events " \ "kbuffer_subbuffer_size kbuffer_start_of_data kbuffer_read_at_offset " \ "kbuffer_ptr_delta kbuffer_raw_get kbuffer_translate_data", want_s, " ") for (i = 1; i <= ns; i++) emit("libtraceevent/ar-sym-" want_s[i], want_s[i], (has(want_s[i]) ? want_s[i] : "MISSING")) # ---- diagnostics: the values that are ALLOWED to differ ---- print "#diag: libtraceevent utest: control " no " cases, target " ng " cases; trace-utest exit status is 0 by construction and is not the verdict (see the header)" > "/dev/stderr" print "#diag: libtraceevent kbuffer SAME_AS_HOST inference (arm property, not asserted): uname.machine=" (("SAMEHOST_UNAME_MACHINE" in gv) ? gv["SAMEHOST_UNAME_MACHINE"] : "?") " -> start_of_data=" (("SAMEHOST_START_OF_DATA_ON_L4_PAGE" in gv) ? gv["SAMEHOST_START_OF_DATA_ON_L4_PAGE"] : "?") " (control: " pv["SAMEHOST_UNAME_MACHINE"] " -> " pv["SAMEHOST_START_OF_DATA_ON_L4_PAGE"] ")" > "/dev/stderr" } JOINAWK awk -v OFS='\t' -f "$tmp/join.awk" \ UORACLE="$BST_DATA/libtraceevent-utest-native.txt" UGOT="$uout" \ PORACLE="$BST_DATA/libtraceevent-kbuffer-native.txt" PGOT="$pout" \ IDX="$idx" PC="$pc" \ "$BST_DATA/libtraceevent-utest-native.txt" "$uout" \ "$BST_DATA/libtraceevent-kbuffer-native.txt" "$pout" \ "$idx" "$pc" > "$tmp/verdicts" 2>"$tmp/diag" # The diagnostics the join wrote, verbatim. while IFS= read -r d; do [ -n "$d" ] && echo "$d"; done < "$tmp/diag" # ------------------------------------------------------ report, fork-free ----- n=0 while IFS=' ' read -r id want got; do [ -n "$id" ] || continue check_eq "$id" "$want" "$got" n=$((n + 1)) done < "$tmp/verdicts" # 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. check_eq libtraceevent/join-complete 265 "$n" rm -rf "$tmp"