. "$BST_LIB" # BST-TIMEOUT: 300 # # xmlrpc-c 1.59.01 (meta-oe recipes-devtools) -- the XML-RPC library for C: a # value model, an XML codec, a method registry, the Abyss HTTP server, and a # curl-backed client. The image's second RPC stack after libtirpc (79-libtirpc), # and the first whose transport is HTTP rather than ONC/TCP. # # EVERY EXPECTED VALUE BELOW IS PINNED FROM AN x86-64 CONTROL, not guessed: the # same SRCREV (08b05269) with the recipe's two patches applied, configured with # exactly this recipe's switches, built natively and driven through this same # sequence on 2026-08-18. Where a case says "control", that is what it means. # # THREE PLATFORM FACTS SHAPE THIS FILE. # # 1. ABYSS SERVES BY FORK HERE, NOT BY THREAD -- and that is why this file is # GREEN where 88-memcached and 88-civetweb are red. Both of those are # threaded by construction, and a thread-mode clone is refused outright on # this arm: # # wasm32: REFUSING thread-mode clone (CLONE_VM, flags=0x3d0f00) with # EAGAIN -- the coro substrate's runtime protocol state is single-lane # per process # # (brintos/linux-in-the-browser#85, with brintos/hardwarejs#12 behind it). # xmlrpc-c escapes that gap rather than working around it: upstream ships # lib/abyss/src/thread_fork.c as an equally supported concurrency model, # the recipe selects it with --disable-abyss-threads, and fork(2) DOES work # on this arm -- 41-luaposix pins a fork+wait round trip. So the server # below really serves, and a red here is a real finding rather than #85 # again. # # 2. `sleep N` DOES NOT SLEEP (brintos/linux#48, linux-in-the-browser#120) and # timeout(1) DOES NOT FIRE, alarm(2) being ENOSYS (brintos/linux#77). The # one wait in this file therefore spins against /proc/uptime and its bound # is enforced here, not by a timer. # # 3. EVERY EXEC COSTS A PARK SLOT. hwjs-cc#69 orphans one per exec and # brintos/hardwarejs#28 leaves the lane unreclaimed, so an exec-happy suite # can WEDGE THE GUEST and take every result with it (measured on # 88-civetweb: 62 command substitutions wedged 2 runs in 3). This file # spends 6 execs for ~60 cases: the probe runs ONCE into a file, and every # assertion off it is a shell function reading that file with `read`, which # forks nothing. Note pv() sets $PV rather than being called through $( ), # deliberately -- a command substitution is a fork, which is the whole cost # being avoided. require /usr/libexec/xmlrpc-c-tests/xmlrpc-c-probe xmlrpc-c/present T=/usr/libexec/xmlrpc-c-tests D=/tmp/.bst-xmlrpc-c rm -rf "$D"; mkdir -p "$D" || exit 0 PROBE_OUT=$D/probe.out # pv : set $PV to the probe's value for , from the ONE captured run. # A `while read` loop over a file -- no process, by design (fact 3 above). pv() { _k=$1; PV='' while IFS=' ' read -r _pk _pv; do if [ "$_pk" = "$_k" ]; then PV=$_pv; break; fi done < "$PROBE_OUT" } up() { read -r _u _rest < /proc/uptime; UPT=${_u%%.*}; } spin() { _i=0; while [ $_i -lt 2000 ]; do _i=$((_i + 1)); done; } # listening : true iff /proc/net/tcp carries a LISTEN (state 0A) row # for that local port. Read from /proc with shell builtins so the case asserts # the KERNEL's view and costs no process. listening() { _p=$1; _hit=1 while read -r _sl _la _ra _st _rest; do if [ "$_st" = "0A" ]; then case "$_la" in *:"$_p") _hit=0; break ;; esac fi done < /proc/net/tcp 2>/dev/null return $_hit } # ------------------------------------------------------------- artifacts ----- # The four programs the port ships. Presence is asserted with `test`, an ash # builtin, so this block spends nothing. for f in xmlrpc-c-probe xmlrpc_sample_add_server xmlrpc_sample_add_client \ gen_sample_add_xml; do if [ -x "$T/$f" ]; then bst_ok "xmlrpc-c/have-$f" else bst_notok "xmlrpc-c/have-$f" "$T/$f is missing or not executable"; fi done # The libraries and headers a consumer links against. This distro is # --disable-shared, so the archives are the deliverable and there is no .so to # look for. for l in libxmlrpc.a libxmlrpc_server.a libxmlrpc_server_abyss.a \ libxmlrpc_util.a; do if [ -f "/usr/lib/$l" ]; then bst_ok "xmlrpc-c/lib-$l" else bst_notok "xmlrpc-c/lib-$l" "/usr/lib/$l is missing"; fi done for h in base.h server.h server_abyss.h client.h; do if [ -f "/usr/include/xmlrpc-c/$h" ]; then bst_ok "xmlrpc-c/hdr-$h" else bst_notok "xmlrpc-c/hdr-$h" "/usr/include/xmlrpc-c/$h is missing"; fi done # The C++ wrapper must NOT be here: this port disables it deliberately because # libc++ is not on this arm yet (brintos/hwjs-cc#10). Asserting its ABSENCE is # what makes that a decision rather than an accident, and this case is what # goes red on the day libc++ lands and the recipe is not revisited. if [ -f /usr/include/xmlrpc-c/registry.hpp ]; then bst_notok "xmlrpc-c/no-cplusplus" "registry.hpp installed, but --disable-cplusplus was configured" else bst_ok "xmlrpc-c/no-cplusplus" fi # ----------------------------------------------------------------- probe ----- # ONE exec. Everything from here to the server section is read out of its # output, so the whole in-memory surface costs a single process. "$T/xmlrpc-c-probe" > "$PROBE_OUT" 2>&1 PROBE_RC=$? # The probe prints PROBE-END last. Its absence means the program died partway, # and every check_eq below would then compare against an empty string and go red # for a reason that has nothing to do with the value it names -- so say so once, # here, and let the rest report as themselves. pv probe/failures if [ -n "$PV" ]; then bst_ok "xmlrpc-c/probe-ran" else bst_notok "xmlrpc-c/probe-ran" "probe produced no probe/failures line (rc=$PROBE_RC)"; fi check_eq xmlrpc-c/probe-rc 0 "$PROBE_RC" pv probe/failures; check_eq xmlrpc-c/probe-failures 0 "$PV" # --- the value model --------------------------------------------------------- pv value/int; check_eq xmlrpc-c/value-int 1234 "$PV" pv value/int-type; check_eq xmlrpc-c/value-int-type INT "$PV" pv value/int32-max; check_eq xmlrpc-c/value-int32-max 2147483647 "$PV" pv value/int32-min; check_eq xmlrpc-c/value-int32-min -2147483648 "$PV" # The 64-bit boundaries are the surface most likely to differ on a 32-bit arm: # an i8 that lost its high word would still round-trip every other case here. pv value/i8-max; check_eq xmlrpc-c/value-i8-max 9223372036854775807 "$PV" pv value/i8-min; check_eq xmlrpc-c/value-i8-min -9223372036854775808 "$PV" pv value/bool; check_eq xmlrpc-c/value-bool 1 "$PV" pv value/double; check_eq xmlrpc-c/value-double 0.500000 "$PV" pv value/string; check_eq xmlrpc-c/value-string hello "$PV" # 5 bytes, not 4 characters: the string is stored as UTF-8, and a build that # re-encoded it would differ here while printing the same word. pv value/string-utf8-len; check_eq xmlrpc-c/value-utf8-len 5 "$PV" pv value/string-utf8; check_eq xmlrpc-c/value-utf8 "café" "$PV" # base64 is the one type whose wire form is a transformation rather than a # rendering, so a broken codec is invisible to every other case in this file. pv value/base64-len; check_eq xmlrpc-c/value-base64-len 4 "$PV" pv value/base64-hex; check_eq xmlrpc-c/value-base64-hex 0001feff "$PV" pv value/array-size; check_eq xmlrpc-c/value-array-size 3 "$PV" pv value/array; check_eq xmlrpc-c/value-array 1,2,3 "$PV" pv value/struct-size; check_eq xmlrpc-c/value-struct-size 2 "$PV" pv value/struct-has-n; check_eq xmlrpc-c/value-struct-has 1 "$PV" pv value/struct-has-zz; check_eq xmlrpc-c/value-struct-hasnt 0 "$PV" pv value/struct-n; check_eq xmlrpc-c/value-struct-n 7 "$PV" pv value/nil-type; check_eq xmlrpc-c/value-nil NIL "$PV" # --- serializing a call: the ticket's "expected XML", byte for byte ---------- # The flattened form (CR and LF removed) is compared whole, and the raw length # and CRLF count are compared separately -- so a build that changed the line # endings cannot hide inside the flattened string. pv call/xml-len; check_eq xmlrpc-c/call-xml-len 211 "$PV" pv call/xml-crlf; check_eq xmlrpc-c/call-xml-crlf 8 "$PV" pv call/xml check_eq xmlrpc-c/call-xml \ 'sample.add57' \ "$PV" # --- parsing that same call back --------------------------------------------- pv parse-call/method; check_eq xmlrpc-c/parse-call-method sample.add "$PV" pv parse-call/argc; check_eq xmlrpc-c/parse-call-argc 2 "$PV" pv parse-call/args; check_eq xmlrpc-c/parse-call-args 5,7 "$PV" # --- THE LOOPBACK OVER MEMORY BUFFERS ---------------------------------------- # The ticket's first pass criterion. The serialized call goes into the registry, # the registry dispatches sample.add, and the response XML comes back out -- the # whole RPC round trip with no socket anywhere in it. The 8080 server below is # the same round trip with one, and each is an independent witness for the # other: a codec bug reddens both, a socket bug only the second. pv loopback/response-len; check_eq xmlrpc-c/loopback-resp-len 141 "$PV" pv loopback/response-xml check_eq xmlrpc-c/loopback-resp-xml \ '12' \ "$PV" pv loopback/is-fault; check_eq xmlrpc-c/loopback-not-fault 0 "$PV" pv loopback/sum; check_eq xmlrpc-c/loopback-sum 12 "$PV" # A method that raises a fault, dispatched through that same registry -- so the # fault path is exercised as a dispatch outcome and not only as a parse error. pv loopback/fail-is-fault; check_eq xmlrpc-c/loopback-fail-is-fault 1 "$PV" pv loopback/fail-code; check_eq xmlrpc-c/loopback-fail-code 42 "$PV" pv loopback/fail-string; check_eq xmlrpc-c/loopback-fail-str deliberate "$PV" # An unregistered method: the registry's OWN fault, not a method's. # -506 is XMLRPC_NO_SUCH_METHOD_ERROR, pinned from the control and confirmed # independently by python3's xmlrpc.client against the native abyss server. pv loopback/miss-is-fault; check_eq xmlrpc-c/loopback-miss-is-fault 1 "$PV" pv loopback/miss-code; check_eq xmlrpc-c/loopback-miss-code -506 "$PV" # --- parsing responses the library did not write ----------------------------- # Canned XML, so a serializer and a parser that agree on the same mistake cannot # pass these. pv response/ok-is-fault; check_eq xmlrpc-c/resp-ok-not-fault 0 "$PV" pv response/ok-value; check_eq xmlrpc-c/resp-ok-value 12 "$PV" pv response/fault-is-fault; check_eq xmlrpc-c/resp-fault-is-fault 1 "$PV" pv response/fault-code; check_eq xmlrpc-c/resp-fault-code 7 "$PV" pv response/fault-string; check_eq xmlrpc-c/resp-fault-str boom "$PV" # The one case whose expected result is a REFUSAL. A parser that accepts # anything is caught here and nowhere else in this file. pv response/malformed-refused; check_eq xmlrpc-c/resp-malformed-refused 1 "$PV" pv response/serialize-len; check_eq xmlrpc-c/resp-serialize-len 141 "$PV" # ------------------------------------------------- the tree's own generator --- # gen_sample_add_xml is upstream's own program for exactly the ticket's # "serialize a request against the expected XML", so it witnesses the probe's # call/xml case from a second, independently-written source. Its output carries # CRLFs; command substitution strips only the trailing newline, so the embedded # CRs remain and the comparison is against the raw form with them named. GEN=$("$T/gen_sample_add_xml" 2>&1) case "$GEN" in *'sample.add'*) bst_ok "xmlrpc-c/gen-methodname" ;; *) bst_notok "xmlrpc-c/gen-methodname" "got:'$GEN'" ;; esac case "$GEN" in *'5'*'7'*) bst_ok "xmlrpc-c/gen-params" ;; *) bst_notok "xmlrpc-c/gen-params" "got:'$GEN'" ;; esac # ------------------------------------------ the abyss server on 127.0.0.1 ----- # The ticket's second pass criterion, and the half the in-memory loopback above # structurally cannot reach: a real listening socket, a real HTTP request, a # real response parsed by the shipped client. # # PORT 8080 IS NOT A CHOICE. xmlrpc_sample_add_client.c hardcodes # "http://localhost:8080/RPC2" and takes no argument, so the ticket's "call it # with the sample client" fixes both the port and the host name. 1F90 is 8080 in # the hex /proc/net/tcp reports. # # `localhost`, not 127.0.0.1, therefore also gets exercised -- and that is safe # for a reason worth recording rather than discovering: curl 8.7.1 short-circuits # both a numeric address (Curl_ip2addr) and the name `localhost` (get_localhost) # in lib/hostip.c BEFORE the resolver backend, so this call never reaches the # POSIX threaded resolver curl is configured with and never trips #85. A curl # built without that shortcut would red the client cases below while the server # cases stayed green, which is what tells the two apart. PORT=8080; HEX=1F90 "$T/xmlrpc_sample_add_server" $PORT > "$D/server.out" 2>&1 & SRV=$! # Spin for the listen, bounded HERE: sleep does not sleep and timeout does not # fire on this image (fact 2). up; DEADLINE=$((UPT + 20)); READY=0 while :; do if listening $HEX; then READY=1; break; fi if [ ! -d /proc/$SRV ]; then break; fi up; if [ "$UPT" -ge "$DEADLINE" ]; then break; fi spin done if [ "$READY" = 1 ]; then bst_ok "xmlrpc-c/server-listening" else bst_notok "xmlrpc-c/server-listening" "no LISTEN on :$PORT within 20s -- server said: $(_bst_1line "$(cat "$D/server.out" 2>/dev/null)")"; fi if [ -d /proc/$SRV ]; then bst_ok "xmlrpc-c/server-alive" else bst_notok "xmlrpc-c/server-alive" "server exited: $(_bst_1line "$(cat "$D/server.out" 2>/dev/null)")"; fi # The sample client against the sample server: the ticket's "comparing the # returned value". 5 + 7 = 12, computed on the far side of a socket. CLI=$("$T/xmlrpc_sample_add_client" 2>&1); CLI_RC=$? check_eq xmlrpc-c/client-rc 0 "$CLI_RC" case "$CLI" in *"The sum is 12"*) bst_ok "xmlrpc-c/client-sum" ;; *) bst_notok "xmlrpc-c/client-sum" "want:'The sum is 12' got:'$(_bst_1line "$CLI")'" ;; esac # The client announces the URL it used. Asserted so a client that silently fell # back to some other endpoint cannot pass the sum case above by accident. case "$CLI" in *"http://localhost:8080/RPC2"*) bst_ok "xmlrpc-c/client-url" ;; *) bst_notok "xmlrpc-c/client-url" "got:'$(_bst_1line "$CLI")'" ;; esac kill $SRV 2>/dev/null # The server forks a child per connection (thread_fork.c). Reap what we can; # a child that outlives us is the guest's to clean up, not this file's. wait $SRV 2>/dev/null # The socket must be GONE once the server is down -- otherwise the "listening" # case above could pass on a leftover listener from a previous run rather than # on this one's server, which would make every client case above ambiguous. if listening $HEX; then bst_notok "xmlrpc-c/server-stopped" "something is still listening on :$PORT after the server was killed" else bst_ok "xmlrpc-c/server-stopped" fi rm -rf "$D"