. "$BST_LIB" # BST-TIMEOUT: 900 # # open62541 1.3.8 (meta-networking recipes-protocols/opcua) -- OPC UA # (IEC 62541) client and server in plain C. The image's FIRST OPC UA # implementation: the networking tier already had HTTP (lighttpd, civetweb), # MQTT (mosquitto), WebSocket (nopoll) and a messaging pair (nanomsg, nng), but # nothing that speaks the OPC UA binary protocol, its SecureChannel handshake, # or its address space. # # EVERY EXPECTED VALUE BELOW IS PINNED FROM AN x86-64 CONTROL, not guessed: the # same tarball, configured exactly as open62541_%.bbappend configures it, built # natively and driven through this same sequence on 2026-08-19. The control was # run in BOTH network shapes -- an ordinary host, and a network namespace # holding only `lo' with IPv6 administratively disabled, which is this guest's # shape -- and both arms agree, case for case. # # FOUR FACTS SHAPE THIS FILE. # # 1. NOTHING IS COPIED ANYWHERE. All four programs are exec'd where they lie # -- three under /usr/libexec/open62541-tests and /usr/bin/ua_client. # Copying one into /tmp first is what makes it unexecutable on this guest: # /tmp is a RAM-backed tmpfs and an exec image is staged out of that same # heap as a segment list capped at WASM32_IMG_MAX_SEGS=1024, so filling the # heap with a copy first lowers the ceiling to roughly 8 MB and the kernel # refuses -ENOMEM (that is brintos/linux#44, and 88-nopoll.test carries the # measured four-arm table for it). MEASURED sizes on this arm, all # comfortably under that ceiling and none of them near it: # # open62541-probe 3 316 073 # tutorial_server_variable 3 219 155 # ua_client 2 155 199 # tutorial_client_firststeps 2 113 044 # # do_install ships them 0755 in place, so nothing needs staging. # # 2. THE TUTORIAL CLIENT CANNOT REPORT A FAILED READ, WHICH IS WHY THERE IS A # PROBE. tutorial_client_firststeps prints its date line only when the read # came back GOOD, and returns EXIT_SUCCESS either way # (examples/tutorial_client_firststeps.c:35-46). Its silence is therefore # ambiguous and its exit status carries nothing. The ticket asks for the # status code explicitly, so open62541-probe asserts the status codes and # the declared variable's value, and the tutorial programs are driven for # what they are -- the deliverable the ticket names. # # 3. THE SERVER'S DISCOVERY URL CARRIES THE GUEST'S HOSTNAME, NOT `localhost'. # ServerNetworkLayerTCP_start builds it from UA_gethostname() # (arch/network_tcp.c:462-470), so it reads opc.tcp://brintos-wasm32:4840/, # and a client told `localhost' is redirected there by FindServers and # reconnects. That resolution has to work: it is /etc/hosts line # `127.0.1.1 brintos-wasm32' read through a STATICALLY linked glibc. It # does, because this arm's glibc is 2.43 and the `files' NSS module has # been built into libc since 2.34 -- but it is the reason # open62541/tutorial-client is asserted separately from the probe's own # cases, which connect through the same redirect. A red there and green # elsewhere would mean name resolution, not OPC UA. # # 4. THE STOP IS ASSERTED TWICE, BY TWO DIFFERENT ROUTES, because they fail # independently. open62541/server-stop sends SIGTERM to the backgrounded # tutorial server, whose handler clears `running' # (tutorial_server_variable.c:31-39, 138-139); open62541/server-shutdown # drives UA_Server_run_startup/_iterate/_shutdown in the probe, reaching # the same teardown by returning instead of by a delivery. Both pass here. # Keeping both is what tells a signal-delivery gap apart from an open62541 # regression -- and it is not hypothetical: 88-nopoll.test records that # this guest delivers no signal (brintos/linux#21), which is the reason the # first route was expected to fail and did not. require /usr/libexec/open62541-tests/open62541-probe open62541/present BIN=/usr/libexec/open62541-tests D=${TMPDIR:-/tmp}/bst-open62541.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok open62541/workdir "cannot create $D"; exit 0; } # --------------------------------------------------------------------------- # Drive one probe mode and turn its PROBE lines into BST result lines. # # A case the probe never printed is NOT silently dropped: either the exec # failed, in which case bst_unrun attributes it to the staging cap rather than # letting it read as an open62541 regression, or the probe died part way, in # which case it is a real red carrying whatever it managed to say. probe_cases() { _pc_out=$D/probe.$1.out _pc_err=$D/probe.$1.err _pc_mode=$1 _pc_arg=$2 _pc_names=$3 : > "$_pc_out"; : > "$_pc_err" if [ -n "$_pc_arg" ]; then "$BIN/open62541-probe" "$_pc_mode" "$_pc_arg" > "$_pc_out" 2> "$_pc_err" else "$BIN/open62541-probe" "$_pc_mode" > "$_pc_out" 2> "$_pc_err" fi _pc_errtxt=$(cat "$_pc_err" 2>/dev/null) for _pc_c in $_pc_names; do _pc_line=$(grep "^PROBE $_pc_c " "$_pc_out" 2>/dev/null | head -1) if [ -z "$_pc_line" ]; then if bst_execfail "$_pc_errtxt"; then bst_unrun "open62541/$_pc_c" "$_pc_errtxt" else bst_notok "open62541/$_pc_c" \ "probe printed no line for this case -- $(_bst_1line "$_pc_errtxt")" fi continue fi case "$_pc_line" in "PROBE $_pc_c PASS"*) bst_ok "open62541/$_pc_c" ;; *) bst_notok "open62541/$_pc_c" "$(_bst_1line "$_pc_line")" ;; esac done } # =========================================================================== # A. THE PACKAGE-FREE CONTROL, FIRST. # # A plain POSIX socket/bind/listen/connect/accept round trip on 127.0.0.1 using # nothing from open62541. If this is red, every case below is red for a reason # that has nothing to do with the port, and saying so here is what stops the # whole suite being read as an OPC UA regression. probe_cases control "" "loopback-control" # =========================================================================== # B. THE TICKET'S SERVER, EXEC'D IN PLACE AND LEFT RUNNING. # # tutorial_server_variable declares ns=1;s=the.answer as Int32 42, writes 43 # over it, then declares ns=1;s=double.matrix and enters UA_Server_run on 4840 # (examples/tutorial_server_variable.c:148-154). 43 is therefore what a reader # sees, and that is what the probe asserts. # # The readiness wait is on the server's OWN log line rather than a fixed sleep: # on this guest a wasm exec can take seconds to stage, and a sleep long enough # to be safe would be most of the suite's budget on a good day. SRVLOG=$D/server.log : > "$SRVLOG" "$BIN/tutorial_server_variable" > "$SRVLOG" 2>&1 & SRVPID=$! i=0 while [ $i -lt 120 ]; do grep -q "TCP network layer listening on" "$SRVLOG" 2>/dev/null && break sleep 1 i=$((i + 1)) done SRVERR=$(cat "$SRVLOG" 2>/dev/null) if grep -q "TCP network layer listening on" "$SRVLOG" 2>/dev/null; then bst_ok open62541/server-listen elif bst_execfail "$SRVERR"; then bst_unrun open62541/server-listen "$SRVERR" else bst_notok open62541/server-listen \ "tutorial_server_variable did not announce a listener in ${i}s -- $(_bst_1line "$SRVERR")" fi # =========================================================================== # C. THE TICKET'S CLIENT. # # tutorial_client_firststeps connects to opc.tcp://localhost:4840, is # redirected to the hostname URL by FindServers, opens a SecureChannel, creates # and activates a session, reads ServerStatus/CurrentTime and prints # "date is: D-M-YYYY H:M:S.mmm". The needle is the literal prefix; the value is # a clock reading and cannot be pinned, so the probe asserts the status code # and the type of the same read in section D. CLILOG=$D/client.log : > "$CLILOG" "$BIN/tutorial_client_firststeps" > "$CLILOG" 2>&1 CLIRC=$? CLITXT=$(cat "$CLILOG" 2>/dev/null) if grep -q "date is:" "$CLILOG" 2>/dev/null; then bst_ok open62541/tutorial-client elif bst_execfail "$CLITXT"; then bst_unrun open62541/tutorial-client "$CLITXT" else bst_notok open62541/tutorial-client \ "no date line (rc=$CLIRC) -- $(_bst_1line "$CLITXT")" fi # =========================================================================== # D. THE ASSERTIONS THE TUTORIALS CANNOT MAKE. # # endpoints GetEndpoints answers GOOD with >= 1 endpoint. Exactly # one here: encryption is not built, so the only # SecurityPolicy on offer is #None. # connect a SecureChannel and an activated session. # read-declared ns=1;s=the.answer reads GOOD, Int32, value 43 -- # status code AND value, which is the ticket's ask. # read-currenttime the same node the tutorial client reads, asserted by # status code, type and a non-zero DateTime. # read-missing-control the negative control: a node nothing declared must # come back BadNodeIdUnknown (0x80340000). Without it, # every GOOD above is equally consistent with a stack # that lost the status word and returns GOOD blindly. probe_cases client "opc.tcp://localhost:4840" \ "endpoints connect read-declared read-currenttime read-missing-control" # =========================================================================== # D2. THE PACKAGE'S OWN CLIENT PROGRAM, /usr/bin/ua_client. # # Upstream installs examples/client.c as ${bindir}/ua_client # (examples/CMakeLists.txt:131-133), so it is part of what the port ships # rather than something this suite invented. It is driven here for one reason: # open62541_%.bbappend deletes the other two things upstream put on the rootfs # (94 MB of nodeset tooling and ua_server_ctt.exe) on the grounds that nothing # exercises them, and keeping this one under that argument means covering it. # # It is a DEMO, not a test program: it browses, reads, WRITES, calls a method # that tutorial_server_variable does not register, adds nodes, and exits # EXIT_SUCCESS regardless of any of it. # # THE NEEDLE IS THE PREFIX, NOT THE VALUE, AND THAT IS DELIBERATE. The obvious # assertion is "the value is: 43", and it is wrong here for a reason measured # on target rather than reasoned about: ua_client WRITES the node it just read, # twice, so one run leaves ns=1;s=the.answer at 45. MEASURED against a freshly # started tutorial_server_variable: # # probe before ua_client value=43 # ua_client "the value is: 43" then "the new value is: 44" # probe after ua_client value=45 # # So an exact-value assertion here is a assertion about RUN ORDER, and it goes # red the day anyone adds a case above it or runs the suite twice against one # server. The exact value is asserted where it is actually safe -- section D's # probe, which only reads, and which runs before this against a pristine server. # # What is asserted here instead is the prefix, and it is not a weaker claim than # it looks: examples/client.c reaches that printf ONLY when the read returned # GOOD and the variant is a scalar Int32 (examples/client.c:131-135), so the # line's existence carries the status code and the type. The value it prints is # echoed into the detail either way, so a failure says what was seen. # # ANYTHING ADDED AFTER THIS POINT MUST NOT EXPECT 43. UACLOG=$D/ua_client.log : > "$UACLOG" ua_client > "$UACLOG" 2>&1 UACRC=$? UACTXT=$(cat "$UACLOG" 2>/dev/null) UACSAW=$(grep -m1 "the value is:" "$UACLOG" 2>/dev/null) if [ -n "$UACSAW" ]; then bst_ok open62541/ua-client-read elif bst_execfail "$UACTXT"; then bst_unrun open62541/ua-client-read "$UACTXT" else bst_notok open62541/ua-client-read \ "ua_client reached no GOOD Int32 read of ns=1;s=the.answer (rc=$UACRC) -- $(_bst_1line "$(tail -c 300 "$UACLOG" 2>/dev/null)")" fi # =========================================================================== # E. THE SERVER LIFECYCLE, DRIVEN WITHOUT A SIGNAL. # # The probe runs its own server on 4841 (not 4840 -- section B's is still # holding that) through UA_Server_run_startup, twenty UA_Server_run_iterate # turns and UA_Server_run_shutdown, asserting GOOD at each step. server-startup # is the case that would go red if this guest's loopback or getaddrinfo(3) # could not carry a listener at all; server-shutdown is the clean stop the # ticket asks for, reached by returning rather than by a delivery. probe_cases server "" \ "server-config server-startup server-iterate server-shutdown" # =========================================================================== # F. THE SIGNAL-DRIVEN STOP. IT WORKS, AND THAT WAS NOT THE EXPECTATION. # # This is the ticket's "stop the server and check it exits cleanly" taken # literally: SIGTERM to the backgrounded server, whose stopHandler clears # `running' so UA_Server_run returns and the network layer is torn down # (tutorial_server_variable.c:31-39). # # IT WAS WRITTEN EXPECTING A RED, ON THE STRENGTH OF 88-nopoll.test's note that # this guest delivers no signal (brintos/linux#21), AND THAT EXPECTATION WAS # WRONG. It passes here: the server leaves UA_Server_run and logs "Shutting # down the TCP network layer". The first target run did show it red, which # looked like confirmation -- but the needle is a UA_LOG_INFO line and that run # was built at UA_LOGLEVEL=600, where the line does not exist to be matched. So # the red was the log level, not the signal, and attributing it to #21 would # have recorded a platform gap this port had no evidence for. # # Whatever #21 covers, it does not stop a SIGTERM reaching this program on this # guest. Left as a real assertion rather than an attributed red. kill "$SRVPID" 2>/dev/null j=0 while [ $j -lt 15 ]; do grep -q "Shutting down the TCP network layer" "$SRVLOG" 2>/dev/null && break sleep 1 j=$((j + 1)) done if grep -q "Shutting down the TCP network layer" "$SRVLOG" 2>/dev/null; then bst_ok open62541/server-stop else bst_notok open62541/server-stop \ "the server did not leave UA_Server_run within ${j}s of SIGTERM; open62541/server-shutdown says whether the library's own clean-stop path still works, which separates a signal-delivery gap from an open62541 regression" fi rm -rf "$D"