. "$BST_LIB" # BST-TIMEOUT: 1800 # three probes; the signal probe's 40,000 emissions are the # # long pole and this arm is far slower than native. # glib-2.0 2.78.6 -- the GLib/GObject/GIO stack as poky ships it from # meta/recipes-core/glib-2.0, ported to wasm32-hwjs under BRINTOS-992. # # --------------------------------------------------------------------------- # WHAT IS BEING MEASURED, AND WHY IT IS PURPOSE-WRITTEN PROBES # # glib installs eight programs of its own -- gio, gdbus, gsettings, gresource, # glib-compile-schemas, glib-compile-resources, gio-querymodules and # gobject-query -- and this suite asserts that each is present and answers a # trivial invocation, because a tool that cannot start says the link is broken. # But not one of them exercises the half of GLib the packages behind this port # use: BRINTOS-101's from-source port of mc established that mc reaches GList, # GHashTable, GString, GRegex and GKeyFile and NO GObject closure marshalling. # So the port ships three consumers of its own, built in-recipe against this # build's own archives, and this file drives those. # # --------------------------------------------------------------------------- # THE ONE THING TO UNDERSTAND BEFORE EDITING THIS FILE # # The port carries a source patch. signal_emit_valist_unlocked builds the # instance-and-parameters GValue array with a g_newa0 DYNAMIC ALLOCA and hands # its address to the closure vtable's indirect call; the coroutinize pass # refuses exactly that shape (brintos/hwjs-cc#15) and the refusal is fatal at # whole-program vantage, so before the patch NOTHING linking GObject could be # built -- the archives assemble and gobject-query is the first link to die. # The patch moves the array to the heap. # # That is a change to the memory management of EVERY signal this image emits, # and the linker cannot see whether it is correct. glib-signal-probe is the # oracle: it emits with 0, 4 and 8 parameters, checks each parameter value in # the handler, reads a return value back through an accumulator, drives both # detail paths, and repeats 40,000 times so that a leaked array is a growth in # RSS rather than nothing at all. IT ALSO DRIVES g_signal_emitv, WHICH DOES NOT # GO THROUGH THE PATCHED FUNCTION -- that is the control: emitv builds the array # in the caller, so agreement between the two says the array the patch now # heap-allocates is the array GObject expected. # # A red anywhere under signal/ is therefore a claim about the patch, not about # GLib, and should be read that way first. # # --------------------------------------------------------------------------- # THE ONE THING THAT DOES NOT WORK, PINNED RATHER THAN OMITTED # # g_signal_new() with a NULL marshaller MAY install g_cclosure_marshal_generic, # which marshals through libffi's ffi_call. This image's libffi is the # build-satisfying stub (BRINTOS-101, linux-in-the-browser#180) whose ffi_call # abort()s, because wasm32 has no runtime dynamic-call substrate. # # "MAY" is load-bearing and this comment used to say "does". gsignal.c # g_signal_new_valist() takes a BUILT-IN marshaller keyed by the signature when # one exists and the generic marshaller only when none does; the table is # no-parameters, and one parameter of seventeen scalar/pointer types, both # returning G_TYPE_NONE. So only a signature OUTSIDE that table reaches libffi. # The probe registers two signals to measure both halves separately. # # glib-generic-marshal-probe is that case, and `generic-marshal/aborts` EXPECTS # THE CRASH. An exit that is not a crash is the interesting result: it would # mean ffi_call has become real and this boundary has moved -- so the case goes # red, which is the only way a stale exclusion gets noticed. # # Signals WITH a marshaller are unaffected, which is every signal glib itself # registers and every signal a library shipping generated marshallers registers. TESTDIR= for d in /usr/libexec/glib-tests /usr/lib/glib-tests /usr/local/libexec/glib-tests; do [ -d "$d" ] && { TESTDIR=$d; break; } done # --- the port's own artifacts ------------------------------------------------- # Asserted first: if an archive or header is missing, every case below would go # red for a reason that has nothing to do with GLib, and the first red should # say so. check glib/lib-glib test -f /usr/lib/libglib-2.0.a check glib/lib-gobject test -f /usr/lib/libgobject-2.0.a check glib/lib-gio test -f /usr/lib/libgio-2.0.a check glib/lib-gmodule test -f /usr/lib/libgmodule-2.0.a check glib/lib-gthread test -f /usr/lib/libgthread-2.0.a check glib/header test -f /usr/include/glib-2.0/glib.h check glib/header-gobject test -f /usr/include/glib-2.0/glib-object.h check glib/header-gio test -f /usr/include/glib-2.0/gio/gio.h check glib/header-config test -f /usr/lib/glib-2.0/include/glibconfig.h check glib/pkgconfig test -f /usr/lib/pkgconfig/glib-2.0.pc check glib/pkgconfig-gobj test -f /usr/lib/pkgconfig/gobject-2.0.pc check glib/pkgconfig-gio test -f /usr/lib/pkgconfig/gio-2.0.pc # Unconverted, -Ddefault_library=static still applies and no .so is shipped -- # the lever that also keeps the thin-archive workaround honest. Converted, the # shipped libglib-2.0.so must be a real, non-empty file. bst_lib_shape glib/static-only "/usr/lib/libglib-2.0.so" "/usr/lib/libglib-2.0.so.0" # --- glib's own tools, split by whether they can be LINKED at all ----------- # Not a functional test of what they do -- there is no session bus here and no # schema to compile -- but a tool that cannot START is a broken link, and these # are the binaries the two gsignal/gobject cuts exist to make linkable. # # THE SPLIT IS THE POINT. Five of glib's programs reach GIO's socket layer, # whose g_socket_send_message_with_timeout sits on a cross-frame setjmp unwind # path the coroutinize pass refuses (brintos/hwjs-cc#15) -- and the refusal also # names glibc's own msort_r, which no recipe can patch. Those five are NOT # shipped, and the suite asserts their ABSENCE rather than skipping them, # because a skip would read the same on the day they start working. # # THE ORACLE FOR THE LINKED ONES IS "IT RAN ITS OWN CODE", NOT rc=0, and that is # deliberate rather than lax. These tools disagree about which smoke argument is # a success: measured on x86-64 glib, `gdbus --version` exits 1, `gsettings # --help` exits 1 and `gresource` exits 1 for BOTH -- each after printing its # own usage. A table of per-tool expected statuses would encode those accidents # and rot at the next release, while scoring rc=0 uniformly reddens working # binaries. What this port claims about them is that they start, that the static # link resolved and that control reached their own main -- and a tool that # printed its usage has shown all three. So the assertion is non-empty output # plus an exec that was staged, with the status in the detail line when it goes # red rather than being the judgement. for t in gobject-query gresource glib-compile-resources gio-querymodules \ glib-genmarshal glib-mkenums glib-gettextize gtester; do prog= for d in /usr/bin /usr/libexec; do [ -x "$d/$t" ] && { prog=$d/$t; break; } done if [ -z "$prog" ]; then bst_notok "glib/tool-$t" "$t is not installed, and it is one of the tools this port DOES link -- glib-2.0-utils or the main package is incomplete" continue fi out=$("$prog" --help 2>&1); rc=$? if bst_execfail "$out"; then bst_unrun "glib/tool-$t" "$out" elif [ -n "$out" ]; then bst_ok "glib/tool-$t" else bst_notok "glib/tool-$t" "rc=$rc and no output at all: the binary produced nothing, so it did not reach its own main" fi done # The five that cannot be linked. Their ABSENCE is the expected state and their # APPEARANCE is the interesting one -- it would mean hwjs-cc#15's setjmp arm has # been fixed, and that this port's exclusion, the recipe's BRINTOS-WORKAROUND # marker and 593.2 are all stale. Same reasoning as generic-marshal/aborts # below: a red is the only way a stale exclusion gets noticed. for t in gio gdbus gsettings gapplication glib-compile-schemas; do if [ -x "/usr/bin/$t" ] || [ -x "/usr/libexec/$t" ]; then bst_notok "glib/unlinkable-$t" "$t IS installed. It cannot be linked on the coro arm (brintos/hwjs-cc#15: g_socket_send_message_with_timeout and glibc's msort_r on a cross-frame setjmp unwind path), so its presence means the platform gap closed and this port's exclusion is now stale -- re-measure and restore it." else bst_ok "glib/unlinkable-$t" fi done # --- preflight for the probes ------------------------------------------------- if [ -z "$TESTDIR" ]; then bst_skip glib/tests-dir "glib-tests not installed on this image" exit 0 fi bst_ok glib/tests-dir # The recipe records which probes linked, beside the binaries, so a reader on # target can tell "not built" from "not installed". All three link today; one # that stopped is a regression in the two cuts, not a missing install. if [ ! -f "$TESTDIR/probe-link.txt" ]; then bst_notok probe-link/present "no probe-link.txt in $TESTDIR; the recipe writes one on every build" else bst_ok probe-link/present for p in glib_probe glib_signal_probe glib_generic_marshal_probe; do if grep -q "^LINKED $p\$" "$TESTDIR/probe-link.txt" 2>/dev/null; then bst_ok "probe-link/$p" else bst_notok "probe-link/$p" "the recipe recorded it as REFUSED rather than LINKED -- the coroutinize pass rejected a probe this port links today" fi done fi # --- the two self-checking probes --------------------------------------------- # Each prints one " ok" or " FAIL != " # line per assertion and exits with the failure count. Every line is scored as # its own case, so one wrong answer names itself instead of reddening the run, # and the trailing "done failures=N" line is checked too -- a probe killed # part-way prints no such line, which is how a truncated run is told from a # clean one with failures. # # The output is captured into a variable and iterated with a here-string-free # `while` over $( ), so no top-level stdin redirect from the rootfs is # performed (brintos/linux#73). run_probe() { _prog=$TESTDIR/$1 _pfx=$2 _want_cases=$3 if [ ! -x "$_prog" ]; then bst_notok "$_pfx/present" "not installed at $_prog (the bbappend installs it; check do_install)" return fi bst_ok "$_pfx/present" _out=$("$_prog" 2>&1); _rc=$? if bst_execfail "$_out"; then bst_unrun "$_pfx/run" "$_out" return fi _seen=0 _done=0 # set -f for the split: a FAIL detail can carry * or ? and `for` would glob it # against the cwd, turning one case's message into a directory listing. set -f for _line in $(printf '%s\n' "$_out" | sed -n "s/^$1 //p" | tr ' ' '\036'); do _l=$(printf '%s' "$_line" | tr '\036' ' ') case "$_l" in done\ failures=*) _done=1 _n=${_l#done failures=} check_int_case "$_pfx/failure-count" "$_n" 0 continue ;; esac _case=${_l%% *} _rest=${_l#* } _seen=$((_seen + 1)) case "$_rest" in ok) bst_ok "$_pfx/$_case" ;; FAIL\ *) bst_notok "$_pfx/$_case" "${_rest#FAIL }" ;; *) bst_notok "$_pfx/$_case" "unparsable probe line: $(_bst_1line "$_l")" ;; esac done set +f # A probe that stopped early emits fewer lines and no terminator. Both are # checked: the count alone would pass a run that printed the right number of # lines and then died, and the terminator alone would pass a run that emitted # half its cases and reached the end. if [ "$_done" -eq 1 ]; then bst_ok "$_pfx/terminated" else bst_notok "$_pfx/terminated" "no 'done failures=' line; the probe did not reach the end of main (rc=$_rc)" fi if [ "$_seen" -ge "$_want_cases" ]; then bst_ok "$_pfx/case-count" else bst_notok "$_pfx/case-count" "reported $_seen cases, expected at least $_want_cases -- coverage was lost, not merely failed" fi } # A tiny numeric comparison of its own, so the failure-count line is a case # rather than a silently-ignored trailer. check_int_case() { if [ "$2" -eq "$3" ] 2>/dev/null; then bst_ok "$1" else bst_notok "$1" "got=$2 want=$3" fi } run_probe glib-probe glib 95 run_probe glib-signal-probe signal 56 # --- the pinned abort --------------------------------------------------------- # glib-generic-marshal-probe emits TWO signals, both registered with NO # marshaller. The first takes one int, which HAS a built-in marshaller, so it # must run its handler and return -- that is the working half, and it is # emitted and flushed first because the second is expected to kill the process. # The second takes two ints, which has no built-in, so it reaches # g_cclosure_marshal_generic -> ffi_call -> abort. THE CRASH IS THE PASS. # # Four things are asserted rather than one, because "it exited non-zero" is too # weak an oracle for a deliberate crash: the built-in half must have RUN, the # probe must have STARTED the generic emission (it prints "emitting" first, so a # binary that could not be staged is told from one that aborted), it must NOT # have printed the survival line, and its status must be a signal death rather # than an ordinary non-zero return. if [ ! -x "$TESTDIR/glib-generic-marshal-probe" ]; then bst_skip generic-marshal/present "glib-generic-marshal-probe not installed on this image" else bst_ok generic-marshal/present gm_out=$("$TESTDIR/glib-generic-marshal-probe" 2>&1); gm_rc=$? case "$gm_out" in *"glib-generic-marshal-probe builtin-ran n=42"*) bst_ok generic-marshal/builtin-ran ;; *) if bst_execfail "$gm_out"; then bst_unrun generic-marshal/builtin-ran "$gm_out" else bst_notok generic-marshal/builtin-ran "the built-in marshaller half did not run its handler; a signature inside gsignal.c's built-in table takes no libffi path and must work: $(_bst_1line "$gm_out")" fi ;; esac case "$gm_out" in *"glib-generic-marshal-probe emitting"*) bst_ok generic-marshal/started ;; *) if bst_execfail "$gm_out"; then bst_unrun generic-marshal/started "$gm_out" else bst_notok generic-marshal/started "the probe printed no 'emitting' line, so it never reached the emission: $(_bst_1line "$gm_out")" fi ;; esac case "$gm_out" in *"survived -- ffi_call no longer aborts"*) bst_notok generic-marshal/aborts "the generic marshaller RETURNED. Before concluding ffi_call became real, check the SIGNATURE: this case is only meaningful while the probe's generic signal stays outside gsignal.c's built-in marshaller table (it takes two parameters for that reason). If the signature is still outside it, then ffi_call is real, and the libffi stub's BRINTOS-WORKAROUND marker (linux-in-the-browser#180) and this port's generic-marshalling exclusion are both stale and must be re-measured." ;; *) # 128+n is a signal death in the shell's encoding; abort() is SIGABRT (6). if [ "$gm_rc" -gt 128 ]; then bst_ok generic-marshal/aborts elif [ "$gm_rc" -ne 0 ]; then bst_notok generic-marshal/aborts "expected a signal death from ffi_call's abort, got an ordinary rc=$gm_rc: $(_bst_1line "$gm_out")" else bst_notok generic-marshal/aborts "the probe exited 0 without printing the survival line, so what happened is unknown: $(_bst_1line "$gm_out")" fi ;; esac fi