# BST-TIMEOUT: 300 # ~75 cases, but only ONE process spawn: the probe runs # # every assertion in a single 4 MB module and the shell # # scores its output. Provisional pending the first on-target # # measurement; the 60 s default is not close, because the # # probe's own socket and poll controls each carry a 3 s bound # # and its loop watchdog a 10 s one. . "$BST_LIB" # libuv 1.48.0 -- "a multi-platform support library with a focus on asynchronous # I/O", the event loop Node.js is built on (poky recipes-connectivity/libuv). # What ships is /usr/lib/libuv.a, /usr/lib/pkgconfig/libuv.pc, uv.h and # include/uv/*.h. There is no program and no shared object. # # --------------------------------------------------------------------------- # THE HEADLINE: THIS PORT'S EVENT LOOP DOES NOT START ON THIS KERNEL, AND THE # LIBRARY IS STILL WORTH SHIPPING AND STILL MEASURABLE. # # uv_loop_init opens its backend with epoll_create1(O_CLOEXEC) and returns the # errno if that fails (src/unix/linux.c:643). arch/wasm32/kernel/syscall_table.c # has no entry for any epoll number, so the answer is ENOSYS although # CONFIG_EPOLL=y and fs/eventpoll.o is in vmlinux -- brintos/linux#53. Behind it # sits a SECOND, INDEPENDENT wall: the same uv_loop_init calls uv_async_init for # its work queue and uv__async_start opens eventfd(0, EFD_CLOEXEC|EFD_NONBLOCK) # (src/unix/async.c:223), and eventfd is missing from that table too # (brintos/linux#76). Wiring epoll alone would move the failure one line down. # # libuv HAS NO SELECT OR POLL FALLBACK, which is why libev and libevent run here # and this does not: those carry several backends and choose at run time, while # libuv's unix backend IS epoll on Linux, fixed at compile time by platform. # # --------------------------------------------------------------------------- # WHAT MAKES THE TWO REDS A DIAGNOSIS RATHER THAN AN ASSERTION # # The probe asks epoll_create1 and eventfd DIRECTLY and prints their errnos (S01, # S02), and beside them drives poll(2) on a pipe (S03) and a complete POSIX TCP # echo on 127.0.0.1 in one process (S04). Those two are NEGATIVE CONTROLS in the # strict sense: they are the readiness primitive and the socket stack the loop # would have needed, exercised without libuv. Both pass. Without them, "libuv's # loop does not start" would be indistinguishable from "this guest has no I/O", # and the L/T reds below would be attributing a failure nobody had localised. # # --------------------------------------------------------------------------- # WHY THERE IS NO NATIVE x86_64 CONTROL FILE, unlike 79-libtirpc and 78-libev # # An oracle earns its keep where the correct answer is specified but not obvious # -- XDR's wire bytes, libev's callback ordering. Here it would be actively # misleading: the values this port's story turns on (S01, S02, L01, T01-T05) are # REQUIRED to differ from x86_64, where epoll and eventfd both work, so a diff # against a native run would paint the port's central finding as a wall of # mismatches and hide the cases that genuinely must agree. What must agree is # specified elsewhere and pinned directly below: the round-tripped payload is # libuv's own, the address text is RFC 4291/1123 notation, the errno names are # libuv's public API, and the version is what the recipe pins. Each is asserted # against that source, not against another machine. # # --------------------------------------------------------------------------- # THE REDS ARE LEFT FAILING, NOT SKIPPED, and that is deliberate: an attributed # red flips green by itself the day the syscalls are wired, which is the # regression signal this suite is bought for. The probe still CONTAINS the timer, # the TCP echo and the version check inside one uv_run, guarded by the loop-init # result, so that day needs no source change here or in the probe. PROBE=${BST_LIBUV_PROBE:-/usr/libexec/libuv-tests/libuv-probe} ARCHIVE=/usr/lib/libuv.a PCFILE=/usr/lib/pkgconfig/libuv.pc INC=/usr/include # The version the recipe pins. Asserted rather than read back from the library, # so a recipe bumped without updating this suite is a red here instead of a # silent change of subject. WANT_VERSION=1.48.0 if [ ! -f "$ARCHIVE" ]; then bst_skip libuv/present "libuv not installed on this image" exit 0 fi bst_ok libuv/present # --- the package is whole ----------------------------------------------------- check libuv/staticlib test -s "$ARCHIVE" check libuv/pkgconfig test -s "$PCFILE" check libuv/hdr-uv test -s "$INC/uv.h" # The five upstream installs on THIS platform, read off Makefile.am rather than # off the include/uv/ directory listing: uvinclude_HEADERS starts as errno.h + # threadpool.h + version.h, and the per-platform branches add unix.h (non-Windows) # and linux.h (LINUX). uv/tree.h is deliberately NOT here -- Makefile.am adds it # only in the WINNT branch, beside uv/win.h, so its absence is upstream's # packaging and not a truncated install. Asserting it cost this suite a red that # said "the port is incomplete" about a correct port. for h in errno.h version.h unix.h linux.h threadpool.h; do check "libuv/hdr-uv-$(echo "$h" | tr '.' '-')" test -s "$INC/uv/$h" done # The same fact from the other side, so a future image that DOES ship it -- a # recipe change, or a bbappend installing the tree -- is reported rather than # silently accepted by a list that no longer mentions the file. check libuv/hdr-uv-tree-h-absent test ! -e "$INC/uv/tree.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libuv/no-shared "/usr/lib/libuv.so" "/usr/lib/libuv.so.1" # The .pc must name the version the recipe pinned, or a consumer resolving libuv # through pkg-config gets a different answer from one reading uv/version.h. pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -n 1) check_eq libuv/pkgconfig-version "$WANT_VERSION" "$pcver" # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes. -a where the grep supports it -- # on this arm the archive members are LLVM IR bitcode (ThinLTO), so a grep that # suppresses matches in a file containing NULs reports nothing; GNU grep needs # -a, busybox grep as shipped does not. Probing keeps this measuring libuv rather # than measuring the image's grep. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in uv_loop_init uv_run uv_timer_start uv_tcp_bind uv_listen uv_accept \ uv_read_start uv_write uv_fs_open uv_fs_read uv_fs_write uv_fs_scandir \ uv_async_init uv_version_string uv_ip4_addr uv_inet_ntop uv_strerror \ uv_err_name uv_random uv_hrtime; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libuv/sym-$sym" else bst_notok "libuv/sym-$sym" "$sym absent from $ARCHIVE" fi done # --- the probe ---------------------------------------------------------------- if [ ! -x "$PROBE" ]; then bst_skip libuv/probe-runs "$PROBE absent from the image" exit 0 fi pout=${TMPDIR:-/tmp}/bst-libuv-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libuv/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libuv/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libuv/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libuv/probe-complete else bst_notok libuv/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -n 1 "$pout" 2>/dev/null))" fi # val -- the probe's value for one case, empty when the probe stopped first. val() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$pout"; } # want -- score one probe value against a pinned answer. # A missing value is reported as the probe having stopped early rather than as a # mismatch against '', which is a different fault and localises to a different # line. want() { _got=$(val "$1") if [ -z "$_got" ]; then bst_notok "libuv/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libuv/$2" else bst_notok "libuv/$2" "want:'$3' got:'$_got'" fi } # --- version ------------------------------------------------------------------ want V01 version-string "$WANT_VERSION" want V02 version-decoded "$WANT_VERSION" want V03 version-hdr-lib "header-matches-archive" # --- error mapping ------------------------------------------------------------ # Every uv_fs_* result below is reported THROUGH this mapping, so a broken # translate would make the whole F group lie in one direction with nothing to # notice it. That is why it is scored first and separately. want E01 err-name-enosys "ENOSYS" want E02 err-name-enoent "ENOENT" want E03 err-name-einval "EINVAL" want E04 strerror-enoent "no such file or directory" want E05 strerror-enosys "function not implemented" want E06 translate-enosys "yes" want E07 translate-enoent "yes" # --- buffers and addresses ---------------------------------------------------- want B01 buf-len "8" want B02 buf-base "base-kept" want A01 ip4-addr "OK" want A02 ip4-name "OK 127.0.0.1" want A03 ip4-port "7999" want A04 ip6-addr "OK" want A05 ip6-name "OK ::1" # libuv ships its own inet_pton/ntop rather than using the libc ones, and the # wire order is fixed by definition -- printed as hex so a wrong-endian port is # visible instead of round-tripping through its own mistake. want A06 inet-pton "OK 0a010203" want A07 inet-ntop4 "OK 10.1.2.3" want A08 inet-ntop6 "OK ::1" # A refusal is a case: a parser that accepted this would accept anything. want A09 ip4-addr-refuses "EINVAL" # --- the synchronous filesystem API (loop = NULL) ----------------------------- # uv_fs_* with a NULL callback never touches the loop: src/unix/fs.c's INIT macro # only stores the pointer and POST calls uv__fs_work inline. This group is # therefore REAL coverage of the port on a platform with no event loop, not a # stand-in for it. want F01 fs-open-write "OK" want F02 fs-write-bytes "55" want F03 fs-fsync "OK" want F04 fs-close "OK" want F05 fs-stat-size "OK 55" want F06 fs-open-read "OK" want F07 fs-read-bytes "55" # The whole point of the round trip: the bytes that came back are the bytes that # went in. A byte count alone would pass a build that wrote the right LENGTH of # the wrong content. want F08 fs-roundtrip "match" want F09 fs-rename "OK" want F10 fs-stat-renamed "OK" want F11 fs-unlink "OK" # A stat that now FAILS is the assertion; one that still succeeded would mean # unlink reported success and removed nothing. want F12 fs-gone-after-unlink "ENOENT" want F20 fs-mkdir "OK" want F21 fs-create-in-dir "OK" want F22 fs-scandir "OK 1" want F23 fs-scandir-entry "found-one" want F24 fs-unlink-inner "OK" want F25 fs-rmdir "OK" # --- loop-free platform surface ---------------------------------------------- want M01 hrtime-monotonic "monotonic" want M02 hrtime-nonzero "nonzero" want M03 os-getpid "same-as-getpid" want M04 cwd "OK absolute" want M05 gethostname "OK" # BRINTOS-WORKAROUND: brintos/linux#69 remove -- uv_exepath() is readlink() on # /proc/self/exe, and on this kernel that answers ENOENT for every process but # pid 1. libuv computes nothing wrong here; it is handed the errno. Scored as a # red rather than a skip, on the same reasoning as S01/S02: it flips green by # itself the day the link resolves, with no change to this file. # # The exact pair is matched, not just the errno, so a DIFFERENT wrong answer -- # a success naming some other binary, or a second errno -- stays an untracked # red instead of being absorbed by this attribution. m06=$(val M06) case "$m06" in "OK names-self") bst_ok libuv/exepath ;; "ENOENT OTHER") bst_notok libuv/exepath "brintos/linux#69: uv_exepath -> ENOENT (readlink(/proc/self/exe) is ENOENT for every process but pid 1); M04 cwd is OK on the same probe, so this is that one link and not a broken procfs" ;; "") bst_notok libuv/exepath "no value on target (probe stopped before M06)" ;; *) bst_notok libuv/exepath "uv_exepath -> $m06, which is neither the working answer nor the tracked brintos/linux#69 shape" ;; esac want M07 total-memory "nonzero" want M08 free-memory "nonzero" want M09 uptime "OK nonneg" want M10 rss "OK" want M11 random-sync "OK" # 16 bytes back all-zero would be a getrandom that reported success and wrote # nothing, which is a failure this platform can actually produce. want M12 random-nonzero "nonzero" # --- the platform controls ---------------------------------------------------- # S03 and S04 are the negative controls for the two reds below: they establish # that this guest HAS a readiness primitive and a working loopback socket stack, # so a red L01 cannot be read as "no I/O here". want S03 control-poll "OK" want S04 control-tcp-echo "OK" # --- the two syscalls libuv's loop is built on -------------------------------- # BRINTOS-WORKAROUND: brintos/linux#53 remove -- epoll_create1 answers ENOSYS # because arch/wasm32/kernel/syscall_table.c wires no epoll number, although # CONFIG_EPOLL=y and fs/eventpoll.o is in vmlinux. Scored as a red rather than a # skip so it flips green by itself when the entry lands. s01=$(val S01) case "$s01" in OK) bst_ok libuv/epoll-create1 ;; ENOSYS) bst_notok libuv/epoll-create1 "brintos/linux#53: epoll_create1 -> ENOSYS (no epoll entry in the wasm32 syscall table); libuv's unix backend IS epoll and has no select/poll fallback, so this alone blocks every loop case below" ;; "") bst_notok libuv/epoll-create1 "no value on target (probe stopped before S01)" ;; *) bst_notok libuv/epoll-create1 "epoll_create1 -> $s01, which is neither OK nor the tracked ENOSYS" ;; esac # BRINTOS-WORKAROUND: brintos/linux#76 remove -- eventfd is likewise absent from # the wasm32 syscall table, so uv_loop_init would still fail at uv_async_init # even with epoll wired. Filed from the libtevent port, which reaches the same # missing entries from its signal half; same red-not-skip reasoning. s02=$(val S02) case "$s02" in OK) bst_ok libuv/eventfd ;; ENOSYS) bst_notok libuv/eventfd "brintos/linux#76: eventfd(2) -> ENOSYS (no eventfd entry in the wasm32 syscall table); uv_loop_init calls uv_async_init for its own work queue, so this blocks the loop independently of #53" ;; "") bst_notok libuv/eventfd "no value on target (probe stopped before S02)" ;; *) bst_notok libuv/eventfd "eventfd -> $s02, which is neither OK nor the tracked ENOSYS" ;; esac # --- the loop, and the four subsystems the ticket names ----------------------- # These are the ticket's own criterion. They are scored against the WORKING # answer, so each is a red today and each turns green on its own the day the two # syscalls above are wired -- no edit here, and none in the probe, which already # contains the timer, the file round trip and the TCP echo behind its loop-init # guard. loop_blocked=0 l01=$(val L01) case "$l01" in OK) bst_ok libuv/loop-init ;; ENOSYS) loop_blocked=1 bst_notok libuv/loop-init "brintos/linux#53 + brintos/linux#76: uv_loop_init -> ENOSYS at epoll_create1 (src/unix/linux.c:643); poll(2) and loopback TCP both work here (S03/S04), so this is the missing syscall and not a broken socket layer" ;; "") bst_notok libuv/loop-init "no value on target (probe stopped before L01)" ;; *) bst_notok libuv/loop-init "uv_loop_init -> $l01, which is neither OK nor the tracked ENOSYS" ;; esac # blocked -- one of the ticket's four # subsystems. A `blocked-no-loop` value is the attributed red; anything else is # scored against the working answer, so the day the loop runs these become # ordinary assertions with no edit. blocked() { _got=$(val "$2") if [ "$_got" = "blocked-no-loop" ]; then bst_notok "libuv/$1" "blocked by brintos/linux#53 + brintos/linux#76: uv_loop_init returned ENOSYS, so this subsystem never ran" elif [ -z "$_got" ]; then bst_notok "libuv/$1" "no value on target (probe stopped before $2)" elif [ "$_got" = "$3" ]; then bst_ok "libuv/$1" else bst_notok "libuv/$1" "want:'$3' got:'$_got'" fi } blocked timer-ticks T01 "3" blocked timer-self-stop T02 "stopped-at-bound" blocked tcp-server-recv T03 "server-got-ping" blocked tcp-client-echo T04 "client-got-pong" blocked loop-version T05 "$WANT_VERSION" blocked loop-run L02 "ran rc=0" # The two walls are independent, and a reader has to be able to see that from the # report alone: this case says the loop is blocked by BOTH, so a future run in # which epoll is wired and eventfd is not stays red here for a reason that is # already written down rather than needing rediagnosis. if [ "$loop_blocked" = "1" ] && [ "$s02" = "ENOSYS" ]; then bst_notok libuv/loop-two-walls "brintos/linux#53 (epoll) and brintos/linux#76 (eventfd) each block uv_loop_init on their own; wiring one leaves the other" else bst_ok libuv/loop-two-walls fi rm -f "$pout"