. "$BST_LIB" # BST-TIMEOUT: 300 # one probe run plus ~50 shell extractions; the 60s default # is for suites that fork far less than this one does. # xtrans 1.5.0 -- the X Transport Interface (poky recipes-graphics/xorg-lib). # See xtrans_%.bbappend for what the port adds and why. # # --------------------------------------------------------------------------- # THIS PACKAGE SHIPS NO CODE THAT EVER RUNS ON THIS IMAGE, AND THAT IS NORMAL # # xtrans is not a library. There is no .so and no .a: what ships is seven files # under /usr/include/X11/Xtrans -- four of them .c -- plus an autoconf macro and # a .pc. Consumers COMPILE IT IN. libX11, libICE and the X server each #include # transport.c into a translation unit of their own, under their own TRANS() # name prefix. So the deliverable is source, the consumers do not exist on this # image yet, and this image ships no C compiler: a shell suite on its own can # say only that seven files arrived. # # That is nowhere near enough for the first member of an X11 client stack. Every # other port in this epic reaches this code, and it can be complete, installed # and still parse a display address into the wrong host on this arm -- which # would surface as libX11 connecting somewhere unexpected, months later, with # nothing here having gone red. # # THE PORT THEREFORE BUILDS THE MISSING CONSUMER. The recipe compiles # xtrans_probe.c against the tree it is about to package, exactly as libX11 # compiles it (-DX11_t -DTRANS_CLIENT, this arm's connection families), and # ships it as xtrans-tests. This suite drives it. # # WHY THE EXPECTATIONS ARE WRITTEN OUT HERE RATHER THAN DIFFED AGAINST x86-64. # 57-xorgproto.test compares its probe against a build-host reference because # its question -- struct layout -- has no absolute answer and is genuinely a # comparison between two machines. This one is not like that. The parser is # string arithmetic over separators and the family mapping is a switch over # constants xtrans defines in its own bytes; both are right or wrong against the # DOCUMENTED ALGORITHM, on any arm, so each case states its answer outright. # That is the sharper assertion, and it does not rot when the build host moves. # # It would also be wrong for the one line that IS arch-dependent. On x86-64 # `long' is 8 bytes and here it is 4, so on XT-MODEL agreement with the build # host would be the defect rather than the pass -- see section 3. # # THE TWO MACHINE-DEPENDENT FACTS ARE NORMALISED BY THE PROBE, NOT TOLERATED # HERE. ParseAddress substitutes this machine's own hostname wherever an address # names no host, and ConvertAddress writes that same name out as the FamilyLocal # authorisation address with *addrlenp set to its LENGTH. The probe prints those # as and and reports the name on its own line, which section 3 # asserts. Without that, half the cases below would disagree between two # machines whose names merely differ in length. # # --------------------------------------------------------------------------- # READ A RED IN SECTION ORDER; THE FIRST SECTION TO GO RED LOCALISES THE DEFECT # # 1 the files arrived packaging only # 2 the .pc resolves consumers find the headers at all # 3 the probe runs to its end the compiled-in code executes here # 4 the address parser pure string arithmetic # 5 the transport table which families this build compiled # 6 the sockaddr -> X family mapping integers and memcmp # 7 an open that resolves to nothing the one path into TRANS(Open) # # A build where 3 passes and 4 does not has a string-handling fault; one where 4 # passes and 6 does not has a problem in the socket headers rather than in # xtrans. 5 failing on its own means a connection family was compiled out. # --------------------------------------------------------------------------- # A root prefix, empty on the target and set only when this suite is validated # NATIVELY under dash before it ships. The runner never sets it, so on the image # every path below is exactly the absolute one. R="${BST_XTRANS_ROOT:-}" INC=$R/usr/include/X11/Xtrans ACD=$R/usr/share/aclocal PCD=$R/usr/share/pkgconfig XT=$R/usr/libexec/xtrans-tests work=${TMPDIR:-/tmp}/bst-xtrans.$$ rm -rf "$work"; mkdir -p "$work" hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "missing or empty: $2"; fi } # xt_line # # The prefix is matched with grep -F rather than the whole line, so a red reports # WHAT THE PROBE ACTUALLY SAID for that case instead of merely "absent". Every # prefix is unique by construction: the parser fixtures are quoted in the probe's # output, so ':0' cannot match inside 'tcp/host:0', and the SELECT and FAMILY # labels are followed by a separator that ends them. xt_line() { _l=$(grep -F -- "$2" "$work/out" 2>/dev/null | head -1) check_eq "$1" "$2$3" "$_l" } # --------------------------------------------------------------------------- # 0. THE CANARY, which is not an assertion about xtrans at all. # # The guest console drops a suite's FIRST BST result line before the host parser # sees it -- brintos/linux#96. The harness computes its pass count as # total-minus-failures, so THE DROPPED LINE IS COUNTED AS A PASS, and a suite # whose first case is a genuine `not ok' loses the one line that mattered. So # the first line out of this file asserts something already true. # Remove this case when #96 is fixed. bst_ok xtrans/suite-canary # --------------------------------------------------------------------------- # 1. THE PAYLOAD -- all seven files, each by name. # # By name and not by count: /usr/include is shared, so a count taken here would # start moving the day libX11 lands. Each of these is #included by name from # some consumer's translation unit, so a missing one fails somebody else's # do_compile rather than anything on this image. check_rc xtrans/dir-Xtrans 0 test -d "$INC" hasfile xtrans/hdr-Xtrans-h "$INC/Xtrans.h" hasfile xtrans/hdr-Xtransint-h "$INC/Xtransint.h" hasfile xtrans/src-Xtrans-c "$INC/Xtrans.c" hasfile xtrans/src-Xtranslcl-c "$INC/Xtranslcl.c" hasfile xtrans/src-Xtranssock-c "$INC/Xtranssock.c" hasfile xtrans/src-Xtransutil-c "$INC/Xtransutil.c" hasfile xtrans/src-transport-c "$INC/transport.c" # The autoconf macro is the half of this package that never reaches a target at # all -- it is what a consumer's configure.ac calls to FIND the headers above -- # and it is the one file whose absence would be invisible on the image and fatal # in the next port's do_configure. hasfile xtrans/m4-macro "$ACD/xtrans.m4" hasfile xtrans/probe-shipped "$XT/xtrans-probe" # transport.c is the entry point every consumer names, and it is the one file # here that must pull the other four in. Asserted on its contents rather than # its size: a truncated install would still be non-empty. check xtrans/transport-includes-Xtrans grep -q 'include "Xtrans.c"' "$INC/transport.c" check xtrans/transport-includes-sock grep -q 'include "Xtranssock.c"' "$INC/transport.c" # --------------------------------------------------------------------------- # 2. THE .pc -- how a consumer finds any of the above. # # xtrans.pc lands in ${datadir}/pkgconfig, NOT ${libdir}/pkgconfig, and that is # deliberate upstream-plus-poky behaviour rather than a slip: poky's # multilibfix.patch removes the libdir line from xtrans.pc.in precisely so the # file can be architecture-independent, and an arch-independent .pc belongs in # share. A port that "corrected" this to /usr/lib/pkgconfig would break multilib # for everyone, so the path is pinned here. hasfile xtrans/pc-in-share "$PCD/xtrans.pc" check_rc xtrans/pc-not-in-libdir 1 test -e "$R/usr/lib/pkgconfig/xtrans.pc" # NOT `require pkg-config'. require ENDS THE FILE when the tool is absent, and # everything this suite exists for -- sections 3 to 7 -- comes after this one. # Gating the probe on a query tool would mean an image without pkg-config # reported a clean skip for the transport layer it never exercised. So the # pkg-config cases skip individually and the file runs on. if command -v pkg-config >/dev/null 2>&1; then bst_ok xtrans/pkg-config-present check xtrans/pc-exists pkg-config --exists xtrans check xtrans/pc-validate pkg-config --validate xtrans check_eq xtrans/pc-modversion 1.5.0 "$(pkg-config --modversion xtrans 2>/dev/null)" # xtrans.pc advertises the include directory through its own variable, because # the headers do not live at the top of ${includedir} and a consumer cannot # guess the X11/Xtrans tail. XORG_WITH_XTRANS in xtrans.m4 reads exactly this. check_eq xtrans/pc-var-includedir "$R/usr/include" \ "$(pkg-config --variable=includedir xtrans 2>/dev/null | sed 's/[[:space:]]*$//')" # And the directory it names has to be the one holding the files. A .pc whose # prefix arithmetic moved would answer with a path that is simply not there and # every case in section 1 would still pass. # # PKG_CONFIG_ALLOW_SYSTEM_CFLAGS is required, for the reason 57-xorgproto.test # records: pkg-config SUPPRESSES an -I naming a directory already on the # compiler's system include path, so the bare query answers with the empty # string here -- correctly, and uselessly. The variable asks for the flag # pkg-config COMPUTED rather than the one it decided to print. cf=$(PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --cflags-only-I xtrans 2>/dev/null | sed 's/[[:space:]]*$//') check_eq xtrans/pc-cflags "-I$R/usr/include" "$cf" check_rc xtrans/pc-cflags-dir-real 0 test -f "${cf#-I}/X11/Xtrans/transport.c" # xtrans links nothing, so a Libs line would be a defect rather than an omission: # a consumer that inherited one would try to link a library that does not exist. check_eq xtrans/pc-no-libs "" "$(pkg-config --libs xtrans 2>/dev/null | sed 's/[[:space:]]*$//')" else bst_skip xtrans/pkg-config-present "pkg-config not installed on this image" fi # --------------------------------------------------------------------------- # 3. THE PROBE RUNS, AND RUNS TO ITS END. # # do_install ships it 0755, but the rootfs is read-only at browser runtime and # the exec bit is not reliably readable there either, so it is copied to /tmp -- # a mode=1777 tmpfs with no noexec -- and given the bit where the copy lands. probe=$work/xtrans-probe if cp "$XT/xtrans-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then "$probe" >"$work/out" 2>"$work/err" rc=$? # The probe exits 0 whatever it measures -- every expectation lives in this # file, not in the binary -- so a non-zero status here means it did not RUN: a # trap, a missing import, an exec refusal. On this arm that is a real and # common failure and is what this case is for. if [ "$rc" -eq 0 ]; then bst_ok xtrans/probe-runs else bst_notok xtrans/probe-runs "rc=$rc $(_bst_1line "$(cat "$work/err")")"; fi total=$(sed -n 's/^XT-TOTAL //p' "$work/out") if [ -n "$total" ]; then bst_ok xtrans/probe-complete else bst_notok xtrans/probe-complete \ "no XT-TOTAL line: $(_bst_1line "$(cat "$work/out")")"; fi # The measurement count, bounded from below. Adding a fixture raises it; a # probe that got THINNER while still terminating cleanly is what this catches, # and nothing else here would. if [ -n "$total" ] && [ "$total" -ge 30 ]; then bst_ok xtrans/probe-coverage else bst_notok xtrans/probe-coverage \ "reported ${total:-0} measurements, expected not less than 30"; fi # gethostname(2) has to answer, because ParseAddress and ConvertAddress both # substitute its result and the normalisation below is built on it. A # blank here would make every case pass vacuously, so it is asserted # before any of them are read. hn=$(sed -n 's/^XT-HOSTNAME //p' "$work/out") if [ -n "$hn" ] && [ "$hn" != "(none)" ]; then bst_ok xtrans/hostname-resolves else bst_notok xtrans/hostname-resolves "gethostname gave '${hn:-}'"; fi # THE DATA MODEL, and this is the one line where agreeing with the build host # would be the defect. `long' and a pointer are 4 bytes on wasm32 and 8 on # x86-64. The three sockaddr sizes are glibc's own and are the same on both, # which is what makes the wire-address union usable across the boundary at all; # Xtransaddr is `struct sockaddr_storage' whenever IPv6 is compiled in, so 128 # here is also a statement that the IPv6 arm took. check_eq xtrans/data-model \ "long=4 ptr=4 xtransaddr=128 sockaddr_in=16 sockaddr_in6=28 sockaddr_un=110" \ "$(sed -n 's/^XT-MODEL //p' "$work/out")" # How many transports this build compiled in. It is a compile-time count over # #ifdef'd table entries, so it is the only way to ask which of TCPCONN, # UNIXCONN and IPv6 actually took effect: tcp, inet6, inet, local, unix. check_eq xtrans/transport-count 5 "$(sed -n 's/^XT-TRANSPORTS //p' "$work/out")" # ------------------------------------------------------------------------- # 4. THE ADDRESS PARSER. # # Each answer is the documented algorithm's, read off Xtrans.c's # TRANS(ParseAddress) rather than off a run of it. The order below follows the # order of the branches in that function, so a red says which branch moved. # An explicit protocol and host: taken verbatim, nothing inferred. xt_line xtrans/parse-tcp-host "XT PARSE 'tcp/host:0' " \ "rc=1 proto=tcp host=host port=0" xt_line xtrans/parse-inet-numeric "XT PARSE 'inet/1.2.3.4:6000' " \ "rc=1 proto=inet host=1.2.3.4 port=6000" # A BRACKETED IPv6 LITERAL IS RECLASSIFIED, not merely parsed: the function # calls inet_pton(AF_INET6) on the bracket contents and, when that succeeds, # REWRITES the protocol to inet6 and strips the brackets. So this case # measures glibc's inet_pton on this arm as much as it measures xtrans. xt_line xtrans/parse-ipv6-literal "XT PARSE 'tcp/[::1]:6000' " \ "rc=1 proto=inet6 host=::1 port=6000" # And the negative half of that branch, which is what makes the case above # mean something: brackets alone do not reclassify. inet_pton refuses this, # the protocol stays tcp and the brackets are restored -- a build where # inet_pton wrongly SUCCEEDED would pass the case above and fail this one. xt_line xtrans/parse-ipv6-not-an-address "XT PARSE 'tcp/[not:an:addr]:1' " \ "rc=1 proto=tcp host=[not:an:addr] port=1" # No host: the parser substitutes our own name. The protocol is kept as # written, which is why unix and local answer differently here despite # reaching the same transport. xt_line xtrans/parse-unix-nohost "XT PARSE 'unix/:0' " \ "rc=1 proto=unix host= port=0" xt_line xtrans/parse-local-nohost "XT PARSE 'local/:0' " \ "rc=1 proto=local host= port=0" # Neither protocol nor host -- the bare display name a client sees in $DISPLAY # most of the time. Documented to mean local. xt_line xtrans/parse-bare-display "XT PARSE ':0' " \ "rc=1 proto=local host= port=0" # A host but no protocol: documented to mean tcp. xt_line xtrans/parse-host-no-proto "XT PARSE 'host:0' " \ "rc=1 proto=tcp host=host port=0" # The two shortcuts taken BEFORE the string is copied at all: a leading '/' # and the literal `unix:' prefix. Both yield protocol local with an EMPTY host # -- not the substituted one -- and the whole remainder as the port, because # the path is the endpoint. An implementation that fell through to the generic # parser would answer here, which is why the empty host is asserted # rather than ignored. xt_line xtrans/parse-abs-path "XT PARSE '/tmp/.X11-unix/X0' " \ "rc=1 proto=local host= port=/tmp/.X11-unix/X0" xt_line xtrans/parse-unix-prefix "XT PARSE 'unix:/tmp/.X11-unix/X0' " \ "rc=1 proto=local host= port=/tmp/.X11-unix/X0" # The refusals. rc=0 is the failure return here -- ParseAddress returns 1 on # success -- and all three out-parameters are set to NULL, which the probe # prints as (null). A build that returned the fields on a refusal would leave # a consumer connecting to a half-parsed address. xt_line xtrans/parse-no-separator "XT PARSE 'garbage' " \ "rc=0 proto=(null) host=(null) port=(null)" xt_line xtrans/parse-no-port "XT PARSE 'foo/bar' " \ "rc=0 proto=(null) host=(null) port=(null)" xt_line xtrans/parse-empty "XT PARSE '' " \ "rc=0 proto=(null) host=(null) port=(null)" # ------------------------------------------------------------------------- # 5. THE TRANSPORT TABLE. # # The flags are upstream's own bits from Xtransint.h: TRANS_ALIAS is 1 and # TRANS_ABSTRACT is 32. tcp is an alias (it names the same socket transport # inet does, but must not create a server of its own), and local carries # TRANS_ABSTRACT because its endpoints live in the abstract namespace and are # therefore never unlinked. Both are pinned because they change BEHAVIOUR in # the consumer rather than merely labelling it. xt_line xtrans/select-tcp "XT SELECT tcp = " "tcp flags=1" xt_line xtrans/select-inet "XT SELECT inet = " "inet flags=0" xt_line xtrans/select-inet6 "XT SELECT inet6 = " "inet6 flags=0" xt_line xtrans/select-unix "XT SELECT unix = " "unix flags=0" xt_line xtrans/select-local "XT SELECT local = " "local flags=32" # The lookup is case-insensitive, through strcasecmp when the build has it and # a hand-rolled tolower loop when it does not. Which of the two this arm took # is invisible from outside; that they agree is what is asserted. xt_line xtrans/select-case-insensitive "XT SELECT TCP = " "tcp flags=1" # And a name no entry claims. Without this the census above would pass equally # well against a SelectTransport that returned its first entry for everything. xt_line xtrans/select-unknown "XT SELECT nosuchtransport = " "none" # ------------------------------------------------------------------------- # 6. THE sockaddr -> X AUTHORISATION FAMILY MAPPING. # # These numbers are the ChangeHost protocol's and are stated in Xtransutil.c # itself: FamilyInternet 0, FamilyInternet6 6, FamilyLocal 256. They are what # an X client puts on the wire when it presents its authorisation, so getting # them wrong is not a local matter. # THE BSD LOOPBACK HACK, which is the whole reason this function is not a # one-line cast: 127.0.0.1 is reported as LOCAL, not as Internet. Three # spellings of loopback reach it -- IPv4, IPv6, and IPv4-mapped-IPv6 -- and # each is a separate byte comparison in the source, so each is a separate case. xt_line xtrans/family-inet-loopback "XT FAMILY inet-loopback " \ "rc=0 family=256 len= addr=" xt_line xtrans/family-inet6-loopback "XT FAMILY inet6-loopback " \ "rc=0 family=256 len= addr=" xt_line xtrans/family-inet6-v4mapped-loopback "XT FAMILY inet6-v4mapped-loopback " \ "rc=0 family=256 len= addr=" # A routable address is Internet, and the address is rewritten in place as the # four raw bytes -- len=4 is sizeof(struct in_addr), not sizeof(sockaddr_in). # A build that left the whole sockaddr there would put 16 bytes of mostly # padding on the wire as the client's address. xt_line xtrans/family-inet-routable "XT FAMILY inet-routable " \ "rc=0 family=0 len=4 addr=-" # The v4-mapped routable case DOWNGRADES to FamilyInternet with four bytes: # the mapping is unwrapped rather than carried. This is the case that # distinguishes a correct implementation from one that simply passed AF_INET6 # through. xt_line xtrans/family-inet6-v4mapped-routable "XT FAMILY inet6-v4mapped-routable " \ "rc=0 family=0 len=4 addr=-" # A genuine IPv6 address stays IPv6, all sixteen bytes of it. xt_line xtrans/family-inet6-routable "XT FAMILY inet6-routable " \ "rc=0 family=6 len=16 addr=-" # AF_UNIX is local by definition and takes the same hostname payload the # loopback cases do. xt_line xtrans/family-unix "XT FAMILY unix " \ "rc=0 family=256 len= addr=" # The default arm -- the ONLY path that returns -1. Without this case, a build # that mapped every unknown family to FamilyLocal would look exactly as green # as a correct one. xt_line xtrans/family-unknown-refused "XT FAMILY unspec " "rc=-1" # ------------------------------------------------------------------------- # 7. AN OPEN THAT RESOLVES TO NOTHING. # # The only calls in this suite that enter TRANS(Open), and neither reaches a # socket(2) or a resolver: the first names a protocol the table does not # carry, so SelectTransport refuses it; the second is an address ParseAddress # itself refuses, one step earlier. Both are documented to return NULL, and a # build where only one of them did would have a real defect in the other. xt_line xtrans/open-unknown-transport "XT OPEN nosuchtransport = " "null" xt_line xtrans/open-unparseable "XT OPEN garbage = " "null" # The diagnostics upstream emits on those two paths, on stderr. They are # asserted because they are the only evidence that the refusal happened where # it is supposed to: identical NULLs would also be returned by a function that # gave up before parsing anything at all. if grep -q 'Unable to find transport for nosuchtransport' "$work/err" 2>/dev/null then bst_ok xtrans/open-diag-transport else bst_notok xtrans/open-diag-transport \ "no SelectTransport refusal on stderr: $(_bst_1line "$(cat "$work/err")")"; fi if grep -q 'Unable to Parse address garbage' "$work/err" 2>/dev/null then bst_ok xtrans/open-diag-parse else bst_notok xtrans/open-diag-parse \ "no ParseAddress refusal on stderr: $(_bst_1line "$(cat "$work/err")")"; fi else bst_notok xtrans/probe-stage "cannot stage $XT/xtrans-probe" fi rm -rf "$work"