. "$BST_LIB" # libpipeline builds and runs pipelines of subprocesses without handing a string # to a shell -- pipecmd_new()/pipecmd_arg() assemble each command as an argv, and # pipeline_command()/pipeline_start()/pipeline_wait() fork, wire the pipes and # reap. Avoiding the shell IS the point: no quoting, no word splitting, no # injection. It is here because man-db is built on it. # # WHAT THIS SUITE CAN AND CANNOT ASSERT # # libpipeline installs no program. Upstream's own test suite is a set of C # binaries gated on PKG_CHECK_MODULES(check), and `check` is not on this image, # so RUN_TESTS is off and nothing executable is built even upstream. With no CLI, # no consumer on the image yet, and no on-target C compiler, every case below # asserts the SHIPPED ARTIFACT -- the shape the header-and-archive libraries # before it (json-c, popt, libyaml) established. # # Being explicit about the gap rather than implying coverage: this suite does not # execute one line of libpipeline. The first thing that will is man-db, and the # platform reason that matters is that libpipeline's whole job is fork(2) + # exec(2) + pipe(2), which nothing on this image exercises through this library # today. # # What keeps this from being a file listing is the completeness check: it reads # every function the INSTALLED header declares and requires each one in the # archive, so a partial compile, a lost object or a header/library skew all fail # it. The assertion tracks the header rather than a list written here, which is # what makes it survive a version bump. # # --------------------------------------------------------------------------- # WHY THIS SUITE USES BUILTIN MATCHING INSTEAD OF check_out ... cat # # The three files are read ONCE into shell variables and every text assertion is # a `case` glob against them -- no fork per case -- and the archive symbol lookup # is a single batched extraction whose result is matched the same way. The cjson # suite was killed by the runner's 60s per-suite bound (BST_SUITE_TIMEOUT) at 156 # cases when written as check_out+cat: on the wasm32-hwjs runtime the forking IS # the cost, not the work. # --------------------------------------------------------------------------- inc=/usr/include/pipeline.h pc=/usr/lib/pkgconfig/libpipeline.pc lib=/usr/lib/libpipeline.a nl=' ' # hasfile -- ok iff is a non-empty regular file. # has -- ok iff contains the literal . # hasline -- ok iff carries as a WHOLE line. # Substring matching would let pipeline_readline # satisfy a lookup for pipeline_read, and # pipecmd_argstr satisfy pipecmd_arg -- this # library is full of such prefixes and a # substring test would pass vacuously on them. hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not in archive: $2" ;; esac; } # --- the header --------------------------------------------------------------- hasfile libpipeline/header-present "$inc" h=$(cat "$inc" 2>/dev/null) has libpipeline/header-guard '#ifndef PIPELINE_H' "$h" has libpipeline/header-cxx 'extern "C" {' "$h" has libpipeline/type-pipecmd 'typedef struct pipecmd pipecmd;' "$h" has libpipeline/type-pipeline 'typedef struct pipeline pipeline;' "$h" # The two callback typedefs are what pipecmd_new_function() and pipecmd_pre_exec() # take, and they are the reason the completeness check below skips typedef lines: # they are declared in the `name (args)` shape a function uses, but no such symbol # is ever emitted. has libpipeline/type-function 'typedef void pipecmd_function_type (void *);' "$h" has libpipeline/type-function-free 'typedef void pipecmd_function_free_type (void *);' "$h" has libpipeline/type-post-fork 'typedef void pipeline_post_fork_fn (void);' "$h" # The attribute macros are conditional on the compiler; a header that lost them # would still compile and would silently drop the sentinel/noreturn checking that # pipecmd_new_args() and pipecmd_exec() rely on. has libpipeline/attr-sentinel 'PIPELINE_ATTR_SENTINEL' "$h" has libpipeline/attr-noreturn 'PIPELINE_ATTR_NORETURN' "$h" has libpipeline/attr-printf 'PIPELINE_ATTR_FORMAT_PRINTF' "$h" # --- pkg-config ---------------------------------------------------------------- hasfile libpipeline/pc-present "$pc" p=$(cat "$pc" 2>/dev/null) has libpipeline/pc-name 'Name: libpipeline' "$p" has libpipeline/pc-version 'Version: 1.5.7' "$p" has libpipeline/pc-libdir 'libdir=/usr/lib' "$p" has libpipeline/pc-incdir 'includedir=/usr/include' "$p" has libpipeline/pc-cflags 'Cflags: -I${includedir}' "$p" # Libs must name libpipeline and NOTHING ELSE. libpipeline links gnulib as a # libtool convenience library, so those objects have to end up INSIDE # libpipeline.a; if they ever stopped doing so, the .pc would be the only place a # consumer could learn it needs a second archive -- and it does not say so. The # gnulib-* cases below assert the other half of the same invariant. has libpipeline/pc-libs 'Libs: -L${libdir} -lpipeline' "$p" case "$p" in *-lgnu*) bst_notok libpipeline/pc-libs-single "Libs names a separate gnulib archive" ;; *) bst_ok libpipeline/pc-libs-single ;; esac # --- the archive --------------------------------------------------------------- hasfile libpipeline/archive-present "$lib" check_eq libpipeline/archive-format '!' "$(head -c 7 "$lib" 2>/dev/null)" # --- completeness: every declared entry point is in the archive ----------------- # # Flatten the archive's printable runs to one token per line first: a raw # `grep -o` over binary data reports "Binary file matches" instead of the matches, # which would silently under-count and read as a partial compile. # # The extraction is `grep -oE` with a greedy trailing [a-z0-9_]+ so each match is # the LONGEST identifier at that position. A literal-list scan would stop at # pipeline_read inside pipeline_readline and never report the longer name. tmp=${TMPDIR:-/tmp} flat=$tmp/libpipeline-flat.$$ tr -c '[:print:]' '\n' < "$lib" > "$flat" 2>/dev/null found=$(grep -oE '(pipecmd|pipeline)_[a-z0-9_]+' "$flat" 2>/dev/null | sort -u) # Declared functions, read from the installed header. Typedef lines are dropped # (see type-function above); everything else that is ` (` is a prototype. decl=$(grep -v '^typedef' "$inc" 2>/dev/null \ | grep -oE '(pipecmd|pipeline)_[a-z0-9_]+[[:space:]]*\(' \ | tr -d ' (' | sort -u) ndecl=$(printf '%s\n' "$decl" | grep -c .) miss="" for s in $decl; do case "$nl$found$nl" in *"$nl$s$nl"*) ;; *) miss="$miss $s" ;; esac done # The floor guards the check itself: a header that lost its declarations, or an # extraction that silently matched nothing, would otherwise pass vacuously with # zero names to look up. 1.5.7 declares 67. if [ "$ndecl" -ge 60 ] && [ -z "$miss" ]; then bst_ok libpipeline/sym-complete else bst_notok libpipeline/sym-complete "declared=$ndecl missing:$miss"; fi # A named sample across the layers as well, so a failure names the layer that # broke rather than only reporting a count. for s in pipecmd_new pipecmd_new_argv pipecmd_new_args pipecmd_new_argstr \ pipecmd_new_function pipecmd_new_sequence pipecmd_new_passthrough \ pipecmd_arg pipecmd_argf pipecmd_args pipecmd_argstr pipecmd_argv \ pipecmd_get_nargs pipecmd_setenv pipecmd_unsetenv pipecmd_clearenv \ pipecmd_chdir pipecmd_fchdir pipecmd_nice pipecmd_discard_err \ pipecmd_pre_exec pipecmd_sequence_command pipecmd_dup pipecmd_dump \ pipecmd_tostring pipecmd_exec pipecmd_free \ pipeline_new pipeline_new_commands pipeline_new_command_args \ pipeline_connect pipeline_command pipeline_commands \ pipeline_get_ncommands pipeline_get_command pipeline_set_command \ pipeline_want_in pipeline_want_out pipeline_want_infile \ pipeline_want_outfile pipeline_get_infile pipeline_get_outfile \ pipeline_start pipeline_wait pipeline_wait_all pipeline_run \ pipeline_pump pipeline_read pipeline_readline pipeline_peek \ pipeline_peekline pipeline_peek_size pipeline_peek_skip \ pipeline_install_post_fork pipeline_dump pipeline_tostring \ pipeline_free; do hasline "libpipeline/sym-$s" "$s" "$found" done # --- gnulib is folded in, not a second archive --------------------------------- # # lib/Makefile.am does libpipeline_la_LIBADD = ../gl/lib/libgnu.la, a libtool # CONVENIENCE library: its objects are copied into libpipeline.a rather than # installed separately, which is why `-lpipeline` alone is a complete link line # and why nothing named libgnu.a ships. If that ever regressed, a consumer would # link cleanly against the .pc and fail at the final link with undefined gnulib # symbols -- so assert the objects are actually inside. gfound=$(grep -oE '^(asnprintf|base_name|dir_name|dup_cloexec|full_write|getprogname|gen_tempname|freea)$' "$flat" 2>/dev/null | sort -u) for g in asnprintf base_name dir_name dup_cloexec full_write getprogname \ gen_tempname freea; do hasline "libpipeline/gnulib-$g" "$g" "$gfound" done rm -f "$flat" # --- static-only guards --------------------------------------------------------- # # The distro is static-only (conf/distro/brintos.conf: wasmld's HWJS-3 coro arm # has no stable side-module ABI) and libpipeline's configure.ac asks for the # OPPOSITE default -- LT_INIT([disable-static]). The distro's --disable-shared is # what makes libtool turn static back on; a .so or a .la here would mean that # interaction had changed under us and the image was carrying something no loader # on this arm can use. noglob_ok() { # ok iff nothing matched the glob already expanded into "$@" _id=$1; shift if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi } set -- /usr/lib/libpipeline.so*; noglob_ok libpipeline/no-shared "$@" set -- /usr/lib/libpipeline.la; noglob_ok libpipeline/no-libtool "$@" set -- /usr/lib/libgnu.a; noglob_ok libpipeline/no-gnulib-archive "$@" # libpipeline installs no program under any name -- the package is headers and an # archive. A binary appearing here would mean upstream's check-gated test drivers # had been built and installed. set -- /usr/bin/pipeline*; noglob_ok libpipeline/no-binaries "$@"