. "$BST_LIB" # BST-TIMEOUT: 300 # # civetweb 1.16 (meta-networking recipes-connectivity) -- an embeddable # HTTP/HTTPS server: one C file behind include/civetweb.h, plus a thin main.c # that turns the same library into a standalone server. This image's SECOND HTTP # server after lighttpd, and it is here for the half lighttpd cannot show: the # EMBEDDING API. lighttpd is a program you configure; civetweb is a library you # mg_start() inside your own process and hang C functions off with # mg_set_request_handler(). Both halves are covered below, and each is an # independent witness for the other. # # EVERY EXPECTED VALUE BELOW IS PINNED FROM AN x86-64 CONTROL, not guessed: the # same SRCREV (d7ba35bb) configured with exactly the recipe's cmake switches, # built natively, and driven through this same sequence on 2026-08-18. Where a # case says "control", that is what it means. # # FOUR PLATFORM FACTS SHAPE THIS FILE. # # 1. THREADS DO NOT WORK HERE, AND THAT IS WHY MOST OF THIS FILE IS RED. # civetweb is a threaded server -- mg_start() creates a master thread plus # num_threads workers, and mg_stop() joins them -- and this platform refuses # a thread-mode clone outright. MEASURED on target, 2026-08-18, the kernel # saying it in its own voice: # # [k] wasm32: REFUSING thread-mode clone (CLONE_VM, pid=70, flags=0x3d0f00) # with EAGAIN — the coro substrate's runtime protocol state is # single-lane per process; a second Worker lane over the same # user_memory is a structural protocol violation, not a schedulable # thread. pthread_create() gets EAGAIN (catchable std::system_error). # Opt-in: HWJS_EXPERIMENTAL_THREAD_CLONE=1. # # and civetweb, faithfully: # # Failed to start CivetWeb V1.16 with code 9: Cannot create first worker # thread: error 11 # # So `civetweb` cannot serve on this image, and neither can any embedder of # libcivetweb. That is a PLATFORM gap, not a defect in this port: it is # brintos/linux-in-the-browser#85 (open -- the single-lane coro park # protocol), with brintos/hardwarejs#12 behind it for the join side. # # THE OPT-IN IS NOT A WAY OUT, and it is named here so nobody spends an hour # discovering that. HWJS_EXPERIMENTAL_THREAD_CLONE=1 is read from the HOST # node process's environment (hardwarejs/src/worker.mjs), so nothing running # inside the guest -- this suite included -- can set it; and worker.mjs's own # comment says the path it re-opens ends in glibc's "futex facility returned # an unexpected error code" and exit 127. It is a bring-up hatch, not a # working mode. # # EVERY CASE BLOCKED BY THIS IS LEFT RED AND ATTRIBUTED, never skipped, so it # flips green by itself when #85 lands -- the same choice 79-libdaq makes for # daqtest-static. Each such red carries $THREADS in its detail, so a sweep can # separate "the platform has no threads" from "civetweb regressed" by grep, # and no red here is unattributed. # # WHAT IS NOT RED IS NOT NOTHING. The package, its artifacts, the whole # no-socket CLI surface and every compiled-in feature are asserted and pass, # and two cases below assert the thing that is genuinely GOOD about this # port's behaviour under the gap: civetweb DIAGNOSES the refusal and exits, # and libcivetweb returns mg_start()==NULL, rather than either of them # trapping and taking the guest with it. Those two are written to stay true # AFTER threads land, so they are not assertions that rot. # 2. `sleep N` DOES NOT SLEEP (brintos/linux#48, # linux-in-the-browser#120), and timeout(1) DID NOT FIRE before the # brintos/linux#77 fix, alarm(2) being ENOSYS. Every wait below therefore # spins against /proc/uptime and every bound is enforced by this file, not # by a timer. # 3. EVERY EXEC COSTS A PARK SLOT, so this file spends as few as it can -- # and that discipline is file-wide, not just inside loops. hwjs-cc#69 # orphans a park slot per exec and brintos/hardwarejs#28 leaves the lane # unreclaimed afterwards, so an exec-happy suite does not merely run slowly: # it can WEDGE THE GUEST and take every result with it. Measured on this # file, 2026-08-18: at 62 command substitutions it wedged 2 runs out of 3 # ("hwjs-rt VIOLATION: set-over-set: parked slot not empty at a new park" # inside wasm32_user_exec_load, cases=0), while 84-lmbench on the SAME image # and boot ran 27/27 -- so the image was healthy and this file was the # variable. It now spends 33. # # Concretely: `read < /proc/uptime` and `[ -d /proc/N ]` are builtins, so no # poll loop spends a process per iteration; files are slurped with `read` # and matched with `case` rather than through `$(cat f)`; /proc/net/tcp is # parsed with `read` rather than `awk`; and the probe's twenty-six # measurements are parsed in ONE builtin pass rather than one `awk` apiece. # `check_out` is avoided throughout because it forks per case (`check_eq` # does not, and is used freely). What remains is only what genuinely needs a # process: the subjects themselves, one `find`, one `cmp`, `nc` and `wget`. # 4. EVERYTHING IS NUMERIC AND ON LOOPBACK. 127.0.0.1 by address, so nothing # traverses getaddrinfo(3)'s dns leg -- independently broken on this image # (linux-in-the-browser#96) and it would otherwise turn a web-server result # into a resolver result. lo is the only interface this guest has. # # PORTS 8088/8089/8090, not 80 and not lighttpd's 8080: we are root so any port # would bind, and using neither the privileged default nor the neighbouring # suite's port means a server this file did not start cannot be mistaken for one # it did. require /usr/bin/civetweb civetweb/present CW=/usr/bin/civetweb PROBE=/usr/libexec/civetweb-tests/civetweb-probe PORT=8088 HEX=1F98 # printf '%04X' 8088 PORT1=8089 HEX1=1F99 # printf '%04X' 8089 PPORT=8090 # the probe's own listener, so it never waits on PORT # The attribution every thread-blocked red carries. One string, so a sweep can # grep the whole class and so no red here can go out unattributed. THREADS="threads refused (brintos/linux-in-the-browser#85, brintos/hardwarejs#12)" D=${TMPDIR:-/tmp}/bst-civetweb.$$ rm -rf "$D" mkdir -p "$D/www" || { bst_notok civetweb/workdir "cannot create $D/www"; exit 0; } http_proxy=; https_proxy=; no_proxy=127.0.0.1; export http_proxy https_proxy no_proxy # The fixture the document-root cases fetch. Written here rather than shipped in # ${BST_DATA} on purpose: the assertion is a byte-exact round trip, and a file # this suite just wrote is one whose content it knows without reading a second # copy that could itself be the thing that is wrong. printf 'civetweb fixture payload\nline two\nthird line with punctuation: /?&=%%\n' > "$D/www/fixture.txt" FIXLEN=$(wc -c < "$D/www/fixture.txt" 2>/dev/null | tr -d ' ') now() { read NOW _rest < /proc/uptime; NOW=${NOW%.*}; } spin() { _i=0; while [ $_i -lt 20000 ]; do _i=$((_i + 1)); done; } # listening -- true iff /proc/net/tcp carries a LISTEN (state 0A) row # for that local port. Read from /proc rather than through `ss`, so the case # asserts the KERNEL's view and cannot fail for iproute2's reasons. listening() { awk -v p=":$1" '$4 == "0A" && index($2, p) { hit = 1 } END { exit !hit }' \ /proc/net/tcp 2>/dev/null } wait_listen() { now; _s=$NOW while :; do listening "$1" && return 0 now; [ $((NOW - _s)) -ge "$2" ] && return 1 spin done } # wait_gone -- true iff /proc/ disappears within the bound. # `[ -d ... ]` is a builtin; `kill -0` would report a ZOMBIE as alive and every # stop case would then read as a timeout even when the server exited correctly. wait_gone() { now; _s=$NOW while [ -d "/proc/$1" ]; do now; [ $((NOW - _s)) -ge "$2" ] && return 1 spin done return 0 } # run_bounded -- run something that may never return, with # a wall-clock bound this file imposes (fact 2). Sets RB_RC to the command's # status, or 124 iff WE killed it; output lands in $D/.log. The done-file is # what is polled: a finished background child is a ZOMBIE until reaped and # `kill -0` SUCCEEDS on it, so a kill-0 poll would report every case as timed out # even when it completed correctly. run_bounded() { _lim=$1; _tag=$2; shift 2 rm -f "$D/$_tag.done" { "$@" > "$D/$_tag.log" 2>&1; echo $? > "$D/$_tag.done"; } & _p=$! now; _s=$NOW while [ ! -f "$D/$_tag.done" ]; do now if [ $((NOW - _s)) -ge "$_lim" ]; then kill -9 "$_p" 2>/dev/null RB_RC=124 return 0 fi spin done read RB_RC < "$D/$_tag.done" return 0 } # has ... : true iff EVERY needle is a substring. has() { _h=$1; shift for _n in "$@"; do case "$_h" in *"$_n"*) ;; *) return 1 ;; esac; done return 0 } # --- builtin file reads (fact 3) --------------------------------------------- # slurp -> SLURP, newlines folded to spaces. `read` is a builtin, so this # costs no process where `$(cat f)` costs one; the `|| [ -n "$_l" ]` tail is what # keeps a final line that carries no newline. slurp() { SLURP= # BST-REDIRECT-OK: its callers pass $D/*, under $TMPDIR. while IFS= read -r _l || [ -n "$_l" ]; do SLURP="$SLURP$_l "; done < "$1" 2>/dev/null } # want_in : ok iff contains . The builtin # stand-in for `check_out sh -c "cat "`, which forks twice. want_in() { slurp "$2" if has "$SLURP" "$3"; then bst_ok "$1" else bst_notok "$1" "want:'$3' got:'$(_bst_1line "$SLURP")'"; fi } # first_line -> LINE, trailing CR stripped. One builtin `read` where # `head -1` is a process. CR=$(printf '\r') # BST-REDIRECT-OK: its callers pass $D/*, under $TMPDIR. first_line() { LINE=; read -r LINE < "$1" 2>/dev/null; LINE=${LINE%$CR}; } ## ## 1. What the port installs, and the shape BUILD_SHARED_LIBS=OFF gave it ## check_eq civetweb/inventory-server "" "$([ -x /usr/bin/civetweb ] || echo /usr/bin/civetweb)" check_eq civetweb/inventory-archive "" "$([ -f /usr/lib/libcivetweb.a ] || echo /usr/lib/libcivetweb.a)" check_eq civetweb/inventory-header "" "$([ -f /usr/include/civetweb.h ] || echo /usr/include/civetweb.h)" check_eq civetweb/inventory-probe "" "$([ -f "$PROBE" ] || echo "$PROBE")" # 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 civetweb/no-shared-object "/usr/lib/libcivetweb*.so*" "/usr/lib/*/libcivetweb*.so*" \ "/lib/libcivetweb*.so*" "/lib/*/libcivetweb*.so*" # The -dev half a consumer actually needs: the pkg-config file (upstream installs # it under ${datadir}/pkgconfig, not ${libdir}/pkgconfig) and the cmake package. check civetweb/pkgconfig-file test -f /usr/share/pkgconfig/civetweb.pc check civetweb/cmake-config test -f /usr/lib/cmake/civetweb/civetweb-config.cmake check civetweb/cmake-targets test -f /usr/lib/cmake/civetweb/civetweb-targets.cmake # The stock recipe's do_install:append seds the build-host sysroot paths out of # civetweb-targets.cmake. A leaked one is not cosmetic -- it is an absolute path # into a build directory that does not exist on target, and find_package(civetweb) # would resolve against it. if [ -f /usr/lib/cmake/civetweb/civetweb-targets.cmake ]; then leak=$(grep -c 'recipe-sysroot' /usr/lib/cmake/civetweb/civetweb-targets.cmake 2>/dev/null) check_eq civetweb/cmake-targets-no-sysroot-leak 0 "$leak" else bst_notok civetweb/cmake-targets-no-sysroot-leak "civetweb-targets.cmake is absent" fi # The C++ wrapper is deliberately NOT built (PACKAGECONFIG has no `cpp`), so # CivetServer.h must not be here. Stated positively so the decision is visible on # target rather than only in the recipe. check_eq civetweb/no-cpp-header "" "$([ ! -f /usr/include/CivetServer.h ] || echo /usr/include/CivetServer.h)" # busybox has no civetweb applet, so a bare `civetweb` can only be this port. check_eq civetweb/resolves-to-port "/usr/bin/civetweb" "$(command -v civetweb)" ## ## 2. Identity and the argument surface -- no sockets involved ## # `civetweb -I` prints a JSON system-information block and exits. It is the only # CLI path that reports the compiled feature set, so it is where a PACKAGECONFIG # change becomes visible without starting a server. run_bounded 30 info "$CW" -I slurp "$D/info.log"; INFO=$SLURP if has "$INFO" "CivetWeb V1.16"; then bst_ok civetweb/version-banner else bst_notok civetweb/version-banner "$(_bst_1line "$INFO")"; fi if has "$INFO" '"version" : "1.16"'; then bst_ok civetweb/info-version else bst_notok civetweb/info-version "$(_bst_1line "$INFO")"; fi # Pinned from the control: `caching ipv6 server ssl websockets`, which is # upstream's default PACKAGECONFIG and what the bbappend deliberately keeps. if has "$INFO" "Server: Files HTTPS IPv6 WebSockets Cache"; then bst_ok civetweb/info-features else bst_notok civetweb/info-features "$(_bst_1line "$INFO")"; fi # Neither Lua nor server-side JS is built, and CGI is off. Their absence from the # feature list is the assertion; naming them individually is what makes a silent # PACKAGECONFIG change fail here rather than somewhere downstream. if has "$INFO" "Lua"; then bst_notok civetweb/info-no-lua "Lua appears in the feature list -- PACKAGECONFIG gained lua" else bst_ok civetweb/info-no-lua; fi if has "$INFO" "CGI"; then bst_notok civetweb/info-no-cgi "CGI appears in the feature list -- PACKAGECONFIG gained cgi" else bst_ok civetweb/info-no-cgi; fi # The usage text goes to stderr. `-Z` is not an option. run_bounded 30 usage "$CW" -Z want_in civetweb/usage-text "$D/usage.log" "Start server with a set of options" ## ## 3. The standalone server: the round trip the ticket names ## # Nothing may already be LISTENing, or every assertion below would be about a # server this suite did not start. if listening "$HEX"; then bst_notok civetweb/port-free-before-start \ "something is already LISTENing on $PORT -- the assertions below would not be about this suite's server" else bst_ok civetweb/port-free-before-start fi # STARTED IN THE FOREGROUND AND BACKGROUNDED BY THIS SHELL, never daemonized. # main.c's `daemonize` option defaults to "no", so this is civetweb's own default # and not a workaround -- but it matters here for a second reason: a forked child # is one this suite could not name and could not signal, and the stop case below # is an assertion about a pid we hold. # # NO -num_threads: this is the shipped default (50 workers), because "start the # civetweb executable" is what the ticket asks and a user who tunes nothing gets # exactly this. civetweb/one-thread-listens below is the discriminator that # separates "civetweb cannot serve here" from "fifty workers is too many here". "$CW" -document_root "$D/www" -listening_ports "127.0.0.1:$PORT" \ -enable_directory_listing no > "$D/srv.log" 2>&1 & SRV=$! if wait_listen "$HEX" 60; then bst_ok civetweb/server-listens SRV_UP=1 else bst_notok civetweb/server-listens \ "$THREADS -- no LISTEN on $PORT within 60 s -- $(_bst_1line "$SLURP")" SRV_UP=0 fi # THE ONE CASE THAT IS ABOUT HOW THIS PORT BEHAVES UNDER THE GAP, and it is # written to stay true on BOTH sides of it: if the server came up there is # nothing to diagnose and it passes; if it did not, it passes only when civetweb # said WHY in its own words and exited, rather than trapping and taking the guest # with it. So this does not become a wrong assertion the day #85 lands -- which # is the failure mode of the obvious alternative, a case asserting that starting # fails. if [ "$SRV_UP" -eq 1 ]; then bst_ok civetweb/start-failure-is-diagnosed else slurp "$D/srv.log" if has "$SLURP" "Failed to start CivetWeb" "worker thread"; then bst_ok civetweb/start-failure-is-diagnosed else bst_notok civetweb/start-failure-is-diagnosed \ "the server did not start and did not say why: $(_bst_1line "$SLURP")" fi fi # The startup banner is asserted DOWN IN THE SHUTDOWN BLOCK, not here, and that # is not an ordering preference. civetweb prints it to stdout, which is a file # here and therefore fully buffered, so while the server is still running the log # is empty -- this case read `got:''` against a banner that had been produced # correctly and simply had not been flushed. Waiting for exit is what makes the # assertion about civetweb rather than about stdio. # --- the fetch, byte for byte ----------------------------------------------- # wget, and then a raw socket, are two independent clients: a result both agree # on cannot be a quirk of either. wget goes first because the ticket's assertion # is about the BODY, and wget is the client that reconstructs it. if [ "$SRV_UP" -eq 1 ]; then run_bounded 60 wget wget -q -O "$D/got.txt" "http://127.0.0.1:$PORT/fixture.txt" if [ "$RB_RC" = "0" ]; then bst_ok civetweb/fetch-rc else slurp "$D/wget.log" bst_notok civetweb/fetch-rc "wget rc=$RB_RC $(_bst_1line "$SLURP")" fi if cmp -s "$D/got.txt" "$D/www/fixture.txt" 2>/dev/null; then bst_ok civetweb/fetch-byte-exact else bst_notok civetweb/fetch-byte-exact \ "document root has $FIXLEN bytes and the served copy differs" fi else bst_notok civetweb/fetch-rc "$THREADS -- server never listened" bst_notok civetweb/fetch-byte-exact "$THREADS -- server never listened" fi # --- the raw client: status line and 404 ------------------------------------ # HTTP/1.0 with no keep-alive, so the server closes and nc returns on its own. # The client WRITES before it reads, which is the direction brintos/linux#64 # leaves working. raw() { _tag=$1; _path=$2 : > "$D/$_tag.raw" [ "${SRV_UP:-0}" -eq 1 ] || return 1 run_bounded 40 "r_$_tag" sh -c \ "printf 'GET $_path HTTP/1.0\r\n\r\n' | nc 127.0.0.1 $PORT > '$D/$_tag.raw'" [ "$RB_RC" = "0" ] } if raw ok200 /fixture.txt; then first_line "$D/ok200.raw" case "$LINE" in "HTTP/1.0 200 OK") bst_ok civetweb/raw-200 ;; *) bst_notok civetweb/raw-200 "want:'HTTP/1.0 200 OK' got:'$(_bst_1line "$LINE")'" ;; esac else bst_notok civetweb/raw-200 "$THREADS -- raw GET /fixture.txt did not complete (rc=${RB_RC:-none})" fi if raw miss /definitely-absent; then first_line "$D/miss.raw" case "$LINE" in "HTTP/1.0 404 Not Found") bst_ok civetweb/raw-404 ;; *) bst_notok civetweb/raw-404 "want:'HTTP/1.0 404 Not Found' got:'$(_bst_1line "$LINE")'" ;; esac else bst_notok civetweb/raw-404 "$THREADS -- raw GET /definitely-absent did not complete (rc=${RB_RC:-none})" fi # Directory listing is off, so the document root itself is not browsable. This is # a configuration assertion, not a security claim: it establishes that # -enable_directory_listing no reached the running server. if raw dirlist /; then first_line "$D/dirlist.raw" case "$LINE" in *403*) bst_ok civetweb/dirlisting-off ;; *) bst_notok civetweb/dirlisting-off "want 403 for /, got '$(_bst_1line "$LINE")'" ;; esac else bst_notok civetweb/dirlisting-off "$THREADS -- raw GET / did not complete (rc=${RB_RC:-none})" fi # --- and it stops cleanly ---------------------------------------------------- # SIGTERM, which main.c's signal_handler turns into g_exit_flag; the process then # leaves its wait loop, calls mg_stop (which JOINS every worker thread) and # returns 0. Three assertions, and they are different: that the process went, # that it went through the shutdown path rather than dying, and that the join # completed. # # `kill` really does terminate a process on this image -- measured under # BRINTOS-934, which is evidence that brintos/linux#21's scope is narrower than # its title. If that regresses, this is where it shows. if [ "$SRV_UP" -eq 1 ]; then kill -TERM "$SRV" 2>/dev/null if wait_gone "$SRV" 60; then bst_ok civetweb/server-stops-on-sigterm else bst_notok civetweb/server-stops-on-sigterm "still alive 60 s after SIGTERM" kill -9 "$SRV" 2>/dev/null fi # Now that the process has exited its stdout is flushed, so the startup banner # is finally readable. It names the port and the web root civetweb RESOLVED, so # a server that came up against the wrong document root fails here rather than # passing the fetch against a file it found somewhere else. slurp "$D/srv.log"; SRVLOG=$SLURP if has "$SRVLOG" "started on port(s) 127.0.0.1:$PORT with web root [$D/www]"; then bst_ok civetweb/startup-banner else bst_notok civetweb/startup-banner "$(_bst_1line "$SRVLOG")"; fi if has "$SRVLOG" "Exiting on signal 15"; then bst_ok civetweb/clean-shutdown-log else bst_notok civetweb/clean-shutdown-log "$(_bst_1line "$SRVLOG")"; fi # mg_stop joins the workers, and upstream prints " done." only after it returns. # This is the case that notices a shutdown which started and never finished -- # exactly the shape a thread join that cannot complete would take here. if has "$SRVLOG" "done."; then bst_ok civetweb/shutdown-joined-threads else bst_notok civetweb/shutdown-joined-threads \ "mg_stop() did not report completing: $(_bst_1line "$SRVLOG")"; fi # The port must be gone with it: a listener surviving its process would mean the # socket outlived the shutdown. if listening "$HEX"; then bst_notok civetweb/port-free-after-stop "still LISTENing on $PORT after the server exited" else bst_ok civetweb/port-free-after-stop; fi else bst_notok civetweb/server-stops-on-sigterm "$THREADS -- server never listened" bst_notok civetweb/startup-banner "$THREADS -- server never listened" bst_notok civetweb/clean-shutdown-log "$THREADS -- server never listened" bst_notok civetweb/shutdown-joined-threads "$THREADS -- server never listened" bst_notok civetweb/port-free-after-stop "$THREADS -- server never listened" fi # --- the discriminator ------------------------------------------------------- # One worker instead of fifty. If the default-threaded server above failed and # this one listens, the limit is the NUMBER of threads (park slots, # hwjs-cc#69/linux-in-the-browser#85) rather than threading as such -- which is a # different finding, and the one a reader would otherwise have to rediscover. "$CW" -document_root "$D/www" -listening_ports "127.0.0.1:$PORT1" \ -num_threads 1 -enable_directory_listing no > "$D/srv1.log" 2>&1 & SRV1=$! if wait_listen "$HEX1" 60; then bst_ok civetweb/one-thread-listens else slurp "$D/srv1.log" bst_notok civetweb/one-thread-listens \ "$THREADS -- no LISTEN on $PORT1 within 60 s with -num_threads 1 -- $(_bst_1line "$SLURP")" fi kill -TERM "$SRV1" 2>/dev/null wait_gone "$SRV1" 30 || kill -9 "$SRV1" 2>/dev/null ## ## 4. The embedding API, in one process ## # civetweb-probe starts a server inside itself, registers a handler with # mg_set_request_handler, and drives the round trip against it with civetweb's # own client API. Its measurements are scored against the x86-64 control. if [ ! -f "$PROBE" ]; then bst_notok civetweb/probe-present "$PROBE is not installed -- civetweb-tests missing from the image" else run_bounded 120 probe "$PROBE" "$D/www" "$PPORT" P=$D/probe.log # ONE builtin pass over the probe's output (fact 3). This was an `awk` per # measurement -- twenty-six execs for a file of twenty-six lines, each one a # park slot on a platform that orphans one per exec (hwjs-cc#69). Unset keys # stay empty, which fails their case rather than passing it vacuously. P_VERSION=; P_INITLIB=; P_FILES=; P_TLS=; P_IPV6=; P_WS=; P_CACHE= P_CGI=; P_LUA=; P_SSJS=; P_START=; P_NPORTS=; P_PORT=; P_SSL= P_CBDATA=; P_HSTATUS=; P_HLEN=; P_HMATCH=; P_FIXLEN= P_FSTATUS=; P_FLEN=; P_FMATCH=; P_MISSING=; P_STOP=; P_EXITLIB=; P_DONE=; P_LAST= while read -r _k _v || [ -n "$_k" ]; do [ -n "$_k" ] && P_LAST="$_k $_v" case "$_k" in P00-VERSION) P_VERSION=$_v ;; P01-INITLIB) P_INITLIB=$_v ;; P02-FEAT-FILES) P_FILES=$_v ;; P02-FEAT-TLS) P_TLS=$_v ;; P02-FEAT-IPV6) P_IPV6=$_v ;; P02-FEAT-WEBSOCKET) P_WS=$_v ;; P02-FEAT-CACHE) P_CACHE=$_v ;; P02-FEAT-CGI) P_CGI=$_v ;; P02-FEAT-LUA) P_LUA=$_v ;; P02-FEAT-SSJS) P_SSJS=$_v ;; P03-START) P_START=$_v ;; P04-NPORTS) P_NPORTS=$_v ;; P04-PORT) P_PORT=$_v ;; P04-SSL) P_SSL=$_v ;; P06-CBDATA) P_CBDATA=$_v ;; P07-HANDLER-STATUS) P_HSTATUS=$_v ;; P07-HANDLER-LEN) P_HLEN=$_v ;; P07-HANDLER-MATCH) P_HMATCH=$_v ;; P08-FIXTURE-LEN) P_FIXLEN=$_v ;; P09-FILE-STATUS) P_FSTATUS=$_v ;; P09-FILE-LEN) P_FLEN=$_v ;; P09-FILE-MATCH) P_FMATCH=$_v ;; P10-MISSING-STATUS) P_MISSING=$_v ;; P11-STOP) P_STOP=$_v ;; P12-EXITLIB) P_EXITLIB=$_v ;; P99-DONE) P_DONE=$_v ;; esac done < "$P" 2>/dev/null # A hang is its own finding and is reported as one; a non-zero exit after # mg_start() returned NULL is the thread gap, not the probe misbehaving, so it # is attributed rather than reported as an unexplained rc. if [ "$RB_RC" = "124" ]; then bst_notok civetweb/probe-completes \ "probe did not finish in 120 s; last line: $(_bst_1line "$P_LAST")" elif [ "$RB_RC" = "0" ]; then bst_ok civetweb/probe-completes elif [ "$P_START" = "NULL" ]; then bst_notok civetweb/probe-completes \ "$THREADS -- mg_start() returned NULL so the probe stopped at P03 (rc=$RB_RC)" else bst_notok civetweb/probe-completes \ "rc=$RB_RC; last line: $(_bst_1line "$P_LAST")" fi check_eq civetweb/probe-version "1.16" "$P_VERSION" # 155 = FILES|TLS|IPV6|WEBSOCKET|CACHE -- the whole PACKAGECONFIG as one number. check_eq civetweb/probe-initlib "155" "$P_INITLIB" check_eq civetweb/probe-feat-files "1" "$P_FILES" check_eq civetweb/probe-feat-tls "2" "$P_TLS" check_eq civetweb/probe-feat-ipv6 "8" "$P_IPV6" check_eq civetweb/probe-feat-ws "16" "$P_WS" check_eq civetweb/probe-feat-cache "128" "$P_CACHE" check_eq civetweb/probe-feat-no-cgi "0" "$P_CGI" check_eq civetweb/probe-feat-no-lua "0" "$P_LUA" check_eq civetweb/probe-feat-no-ssjs "0" "$P_SSJS" # P08 is measured before the server is ever used -- the probe reads the fixture # off the filesystem -- so it is asserted OUTSIDE the mg_start() branch below. # Keeping it here is what makes "the probe ran and could read its inputs" # separable from "the server would not start". check_eq civetweb/probe-fixture-len "$FIXLEN" "$P_FIXLEN" # THE LIBRARY SIDE OF THE SAME TRANSITION-STABLE ASSERTION the executable gets # above, and the one that matters most for an EMBEDDING library: an embedder # that cannot start must be told so through the documented return, not by the # process dying under it. civetweb.h says mg_start() returns "web server # context, or NULL on error", and NULL is what libcivetweb returns here -- the # probe printed P03-START NULL and exited 1 under its own control. Passes when # the server starts too, so it does not rot when #85 lands. if [ "$P_START" = "ok" ] || [ "$P_START" = "NULL" ]; then bst_ok civetweb/probe-start-is-catchable else bst_notok civetweb/probe-start-is-catchable \ "mg_start() neither returned a context nor reported NULL -- P03-START='$started'; \ the probe died before it could say (last: $(_bst_1line "$P_LAST"))" fi if [ "$P_START" = "ok" ]; then bst_ok civetweb/probe-start check_eq civetweb/probe-nports "1" "$P_NPORTS" check_eq civetweb/probe-port "$PPORT" "$P_PORT" check_eq civetweb/probe-not-ssl "0" "$P_SSL" # The ticket's second case, in three parts: the handler ran, it received the # cbdata it was registered with, and the body arrived intact. check_eq civetweb/probe-handler-cbdata "probe-cbdata" "$P_CBDATA" check_eq civetweb/probe-handler-status "200" "$P_HSTATUS" check_eq civetweb/probe-handler-len "28" "$P_HLEN" check_eq civetweb/probe-handler-match "1" "$P_HMATCH" # The same document root the executable served, fetched in-process. check_eq civetweb/probe-file-status "200" "$P_FSTATUS" check_eq civetweb/probe-file-len "$FIXLEN" "$P_FLEN" check_eq civetweb/probe-file-match "1" "$P_FMATCH" check_eq civetweb/probe-missing-404 "404" "$P_MISSING" # mg_stop joins every civetweb thread and is the call most likely to hang # here. Bracketed in the probe, so a hang is attributable to exactly this # step rather than to "the probe stopped printing". check_eq civetweb/probe-stop "ok" "$P_STOP" check_eq civetweb/probe-exitlib "1" "$P_EXITLIB" check_eq civetweb/probe-done "1" "$P_DONE" else # mg_start() did not return a context, so none of the measurements below were # ever made. Report each as an ATTRIBUTED red rather than as a want/got # mismatch against an empty string: they are all missing for ONE known # reason, and sixteen bare `got:''` lines make a reader re-derive it sixteen # times. They stay RED, not skipped, so they flip green by themselves the day # #85 lands. for _c in start nports port not-ssl \ handler-cbdata handler-status handler-len handler-match \ file-status file-len file-match missing-404 \ stop exitlib done; do bst_notok "civetweb/probe-$_c" \ "$THREADS -- mg_start() returned NULL, so this measurement was never made" done fi fi rm -rf "$D"