. "$BST_LIB" # BST-TIMEOUT: 420 # ~110 cases, each one exec of a multi-MB wasm image; that # exec is the whole cost -- every case is bounded and none # of them waits on anything. Replace with a measured # elapsed once this suite has run on target a few times. # 57-xstdcmap.test -- xstdcmap 1.0.5 (meta-oe recipes-graphics/xorg-app): the # standard-colormap utility. It defines RGB_DEFAULT_MAP, RGB_BEST_MAP, # RGB_GRAY_MAP and the three single-channel properties on the root window, so # that clients sharing a visual agree on how to allocate colour. See # recipes-graphics/xorg-app/xstdcmap_%.bbappend for what the port changes -- a # static-link closure, and nothing else. # # THERE IS NO ROOT WINDOW HERE, so every property this program exists to define # is out of reach: XmuLookupStandardColormap, XmuDeleteStandardColormap and # XmuAllStandardColormaps all take a Display *. What is reachable is its OPTION # PARSER and its REFUSAL, and the boundary between them -- which on this program # sits in an unusually good place. # # THE BOUNDARY. main() (xstdcmap.c:334-377) calls parse() FIRST, answers -help # at :346 and -version at :350, and only then reaches XOpenDisplay at :355. So # the whole option parser, the usage text and every argument error answer as # themselves with no server, as shipped and with no patch. That is xsetroot's # shape (BRINTOS-2467), not xprop's (BRINTOS-2461), where the connection is # opened before the option loop and a patch was needed to get anything at all # out of the binary. # # THREE OF THIS TICKET'S ACCEPTANCE CRITERIA ARE REFUTED BY THE PROGRAM, and # the suite asserts what it measured rather than what was asked for. Each is # marked `refutes-' below and each is a statement a later reader can check: # # 1. `-help asserted to print the documented usage'. It does -- on STDERR, # and it exits 0. usage() is xstdcmap.c:198-204: both streams and both # statuses are upstream's choice, and `-help' takes usage(0) at :347. # # 2. `-delete with a map name that is not one of the standard ones asserted # to produce the documented diagnostic and a non-zero exit rather than # ... a silent acceptance'. It IS a silent acceptance. parse() walks # propertyTable at :151-158 and simply sets no delete flag when nothing # matches; there is no else and no diagnostic. The non-zero exit does # arrive, but it arrives from the display refusal at :358, which is the # same answer `-delete default' gets. So a wrong map name is NOT # reportable on this image. # # 3. `-display with a malformed display specification ... produce the # documented diagnostic'. The diagnostic it produces is the ordinary # `cannot open display' one with the malformed string interpolated -- # XDisplayName(display_name) returns the argument verbatim (:357). A # malformed spec is therefore INDISTINGUISHABLE from an address nothing # is listening on, both by text shape and by status. # # WHAT DISTINGUISHES THE TWO CLASSES IS THE TEXT, NOT THE STATUS, and that is # worth saying because it is the opposite of xsetroot's arrangement. There the # refusal exits 2 and an argument error exits 1. Here BOTH exit 1 -- usage(1) # at :203 for an argument error, exit(1) at :358 for the refusal -- so a suite # that asserted only statuses would pass against a binary that had confused the # two. Every case below therefore pins the stderr text as well. # # THE REFUSAL ITSELF IS THE PORT'S OWN CASE. A wasm32 socket-address or # struct-packing mistake lands in connection setup, and it lands there as a # crash, a hang, or a diagnostic that is not this one. Each refusal case is the # same four-part shape: the documented text on STDERR, nothing on STDOUT, # status 1, and inside a bound. A red names which of the four the target got # wrong. # # EVERY EXPECTATION BELOW IS AN x86-64 MEASUREMENT, NOT A READING OF THE SOURCE. # The host used to author this port ships xstdcmap 1.0.5 -- the SAME version the # recipe pins -- so each case was run against that binary with DISPLAY removed # before it was written here: the refusal text and its one space, the two spaces # in `usage: xstdcmap', the stream each answer uses, the statuses, the eleven # usage lines, and the precedence orderings in section 10. That is what a phase-A # suite can honestly assert without a target: it removes typo-shaped reds from # the first on-target run, so a red that DOES arrive is about wasm32 rather than # about the expectation. # # WHAT THE CONTROL CANNOT SAY, and the target run is what settles it: whether # wasm32's connect path reaches this refusal at all rather than crashing or # hanging (sections 6 and 7 bound it for exactly that), and the packaging cases # in section 1, which are about this image's .deb and have no x86-64 analogue. S=xstdcmap require xstdcmap $S/present W=${TMPDIR:-/tmp}/bst-xstdcmap.$$ mkdir -p "$W" 2>/dev/null check $S/tmp-writable sh -c "printf x > $W/.w && rm -f $W/.w" nl=' ' # The exact refusal, built from the display name the run should report. # # ONE space after the colon and a trailing FULL STOP: xstdcmap.c:356 is # fprintf(stderr, "%s: cannot open display \"%s\".\n", ...) # Neither is guessed -- both were read off the host binary's bytes. xsetroot's # neighbouring suite pins TWO spaces and no full stop, which is why writing # either from memory would make every case here red for a typo. # # The `%s' is program_name, and :339-342 DOES basename it (strrchr '/'), unlike # xsetroot -- so a run by absolute path still reports `xstdcmap', and the # by-path case in section 1 asserts that. refusal() { printf 'xstdcmap: cannot open display "%s".' "$1"; } # The first line of the usage block. Two spaces after `usage:' (xstdcmap.c:201). USAGE1='usage: xstdcmap [-options]' # first : its first line, with shell builtins only (no fork). first() { case "$1" in *"$nl"*) printf '%s' "${1%%$nl*}" ;; *) printf '%s' "$1" ;; esac; } # split : run xstdcmap with DISPLAY REMOVED, bounded, keeping # the two streams APART; sets $sout, $serr and $rc. # # `env -u DISPLAY' rather than `DISPLAY= ': XDisplayName() treats an EMPTY # DISPLAY and an ABSENT one alike, so both reach the same diagnostic -- but only # the absent one is the state this image actually boots in, and asserting the # state we ship is the point. # # Keeping the streams apart is half of what this suite asserts: -version is the # ONLY thing that answers on stdout and everything else is stderr, so a folded # capture could not tell a caller that the version had started arriving on # stderr or the usage text on stdout. Both files are under $TMPDIR, never the # rootfs. split() { _t=$1; shift bst_bound "$_t" env -u DISPLAY xstdcmap "$@" >"$W/o" 2>"$W/e" rc=$? sout=$(cat "$W/o" 2>/dev/null); serr=$(cat "$W/e" 2>/dev/null) } # withdisp : as split, but with DISPLAY SET. withdisp() { _t=$1; _dpy=$2; shift 2 bst_bound "$_t" env DISPLAY="$_dpy" xstdcmap "$@" >"$W/o" 2>"$W/e" rc=$? sout=$(cat "$W/o" 2>/dev/null); serr=$(cat "$W/e" 2>/dev/null) } # refuses : the whole four-part refusal shape, as three # cases. Used by the option sweep, where the assertion is identical every time # and only the argv changes. refuses() { _rid=$1; _rt=$2; shift 2 split "$_rt" "$@" check_eq "$S/$_rid-rc" "1" "$rc" check_eq "$S/$_rid-stderr" "$(refusal '')" "$serr" check_eq "$S/$_rid-stdout" "" "$sout" } # rejects : the pre-connect argument-error shape -- # status 1, the usage block on stderr, nothing on stdout, and specifically NOT # the refusal. That last part is the whole point: both paths exit 1, so only the # text tells them apart. rejects() { _jid=$1; _jt=$2; shift 2 split "$_jt" "$@" check_eq "$S/$_jid-rc" "1" "$rc" check_eq "$S/$_jid-stderr" "$USAGE1" "$(first "$serr")" check_eq "$S/$_jid-stdout" "" "$sout" case "$serr" in *"cannot open display"*) bst_notok "$S/$_jid-not-a-refusal" \ "the argument error reached XOpenDisplay instead of usage(1): $(_bst_1line "$serr")" ;; *) bst_ok "$S/$_jid-not-a-refusal" ;; esac } # --- 1. the packaging -------------------------------------------------------- # Asserted against dpkg's own file list as well as by stat: a binary that # reached the image by some other route satisfies the stat and does not satisfy # the claim. check $S/install-path test -x /usr/bin/xstdcmap check_eq $S/which "/usr/bin/xstdcmap" "$(command -v xstdcmap)" check $S/man-installed test -s /usr/share/man/man1/xstdcmap.1 pkglist=$(cat /var/lib/dpkg/info/xstdcmap.list 2>/dev/null) if [ -n "$pkglist" ]; then bst_ok $S/pkg-list hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not listed: $2" ;; esac; } hasline $S/pkg-binary /usr/bin/xstdcmap "$pkglist" # xorg-app-common.inc's FILES:${PN} would also take ${libdir}/X11/xstdcmap # and the app-defaults directory if the package had grown either; xstdcmap # ships neither, so the one binary IS the package's runtime content. check_eq $S/pkg-one-binary 1 \ "$(printf '%s\n' "$pkglist" | LC_ALL=C grep -c '^/usr/bin/')" else bst_notok $S/pkg-list "no /var/lib/dpkg/info/xstdcmap.list -- the packaging cases cannot run" fi # The man page is a SEPARATE package, and naming it in the roster is a decision # a split could silently undo. doclist=$(cat /var/lib/dpkg/info/xstdcmap-doc.list 2>/dev/null) if [ -n "$doclist" ]; then bst_ok $S/pkg-doc-list hasline $S/pkg-doc-man /usr/share/man/man1/xstdcmap.1 "$doclist" else bst_notok $S/pkg-doc-list "no /var/lib/dpkg/info/xstdcmap-doc.list" fi # dpkg's own view of the install, which is what "the .deb installs on target" # means as an assertion rather than as a claim. sed -n '/^Package: xstdcmap$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $S/pkg-known test -s "$W/st" check_out $S/pkg-installed "Status: install ok installed" cat "$W/st" check_out $S/pkg-arch "Architecture: wasm32" cat "$W/st" # --- 2. the binary ran at all ------------------------------------------------ # THE FIRST THING TO ESTABLISH, and it is the static-link closure's case. # # A main whose recorded needed entries cannot be bound is refused by the loader # before its first instruction, and that refusal arrives as exit 127 (or 126 on # the needed-entry leg) with no output of the program's own -- see # lib/bst-census.sh for why those two statuses and not others. xstdcmap failing # to open a display is a status the PROGRAM chose, so a status of 1 carrying # xstdcmap's own diagnostic is proof that the image bound libXmu, libXt, libSM, # libICE, libXext, libX11, libxcb, libXau and libXdmcp and entered main(). If # the .bbappend's XSTDCMAP_LIBS closure were missing, wasmld would have refused # the image long before this -- but if such a binary ever did ship, this is the # case that would name it. split 30 -default check_eq $S/linked-and-ran "1" "$rc" # THE Xmu LEG SPECIFICALLY, which is what makes this port's closure different # from every earlier member of this round. -version answering at all means the # image loaded a binary whose link line resolved libXmu's Xt/SM/ICE closure -- # the archives xhost, xrdb, xset, xsetroot and xauth never had to name, because # they ask pkg-config for `xmuu' and this one asks for `xmu'. split 30 -version check_eq $S/xmu-leg-bound "0" "$rc" # --- 3. -version: the one thing that answers on stdout ----------------------- # xstdcmap.c:350-353 is AFTER parse() and after the -help check, and before # XOpenDisplay. PACKAGE_STRING is autoconf's ` ' from AC_INIT, # which is what pins the shipped version to the recipe's PV. split 30 -version check_eq $S/version-rc "0" "$rc" check_eq $S/version-stdout "xstdcmap 1.0.5" "$sout" check_eq $S/version-stderr "" "$serr" # -version WINS OVER AN OPTION THAT WOULD OTHERWISE CONNECT, because :350 is # above :355. Without this the suite would not have measured that the answer is # an exit rather than a flag consulted after the connect. split 30 -version -all check_eq $S/version-first-rc "0" "$rc" check_eq $S/version-first-stdout "xstdcmap 1.0.5" "$sout" # ...AND IT IS STILL ANSWERED WITH A DISPLAY SET, which is the same statement # from the other side: -display is consumed by parse() and never acted on. withdisp 15 :99 -version check_eq $S/version-with-display-rc "0" "$rc" check_eq $S/version-with-display-stdout "xstdcmap 1.0.5" "$sout" split 30 -display 127.0.0.1:9 -version check_eq $S/version-after-display-opt-rc "0" "$rc" check_eq $S/version-after-display-opt-stdout "xstdcmap 1.0.5" "$sout" # --- 4. -help: the usage text, asserted once --------------------------------- # usage(0) at :347: STDERR, and exit 0. Not stdout and not 1. That is upstream's # choice and the suite asserts what ships rather than what would be conventional. # refutes-help-stream / refutes-help-status below say so as named cases, because # this ticket was written expecting the other answer. split 30 -help check_eq $S/help-rc "0" "$rc" check_eq $S/help-stdout "" "$sout" check_eq $S/help-first "$USAGE1" "$(first "$serr")" bst_ok $S/refutes-help-stream # measured: usage goes to stderr, asserted above bst_ok $S/refutes-help-status # measured: -help exits 0, asserted above # The option list itself, in one pass. Every name here is an option the sweep in # section 8 drives, so a help text that stopped documenting one of them would be # caught beside the option still working. for _o in -all -best -blue -default -delete -display -gray -green -red \ -verbose -version; do case "$serr" in *" $_o "*) bst_ok "$S/help-documents$_o" ;; *) bst_notok "$S/help-documents$_o" "not in the usage text" ;; esac done # -help IS NOT ITSELF DOCUMENTED, which is upstream's omission and not this # port's to correct. Asserting it keeps the loop above honest: without this case # a reader cannot tell whether -help was left out of the list or out of the loop. case "$serr" in *" -help "*) bst_notok $S/help-undocumented \ "usage_message grew a -help line -- upstream changed, and the loop above should take it" ;; *) bst_ok $S/help-undocumented ;; esac # The block's shape, which is what says the whole text arrived rather than just # its first line. usage() writes 15 lines -- the usage line, a blank, `where # options include:', eleven options, and a trailing blank -- but $serr comes # from a command substitution, which strips trailing newlines, so the captured # form is 14. Measured, not counted from the source: an expectation of 15 was # written first and the x86-64 control run said 14. check_eq $S/help-lines "14" "$(printf '%s\n' "$serr" | wc -l | tr -d ' ')" case "$serr" in *"where options include:"*) bst_ok $S/help-header ;; *) bst_notok $S/help-header "no 'where options include:' line" ;; esac # --- 5. no arguments at all: also usage, also exit 0 ------------------------- # parse() at :124-125 is `if (argc == 1) usage(0)'. So a bare `xstdcmap' NEVER # reaches the connect, and is the one invocation of this program that neither # refuses nor does anything. It is worth its own case because it is the # invocation a person types first, and because the status is 0. split 30 check_eq $S/bare-rc "0" "$rc" check_eq $S/bare-stdout "" "$sout" check_eq $S/bare-first "$USAGE1" "$(first "$serr")" case "$serr" in *"cannot open display"*) bst_notok $S/bare-no-connect \ "a bare xstdcmap reached XOpenDisplay -- the :124 early usage is gone" ;; *) bst_ok $S/bare-no-connect ;; esac # --- 6. the documented refusal, with no DISPLAY ------------------------------ split 30 -default check_eq $S/nodisplay-rc "1" "$rc" check_eq $S/nodisplay-stderr "$(refusal '')" "$serr" check_eq $S/nodisplay-stdout "" "$sout" # BY ABSOLUTE PATH, which is a different program_name and must report the same # one: :339-342 basenames argv[0]. xsetroot does not, so this case is genuinely # about this program rather than copied from next door. bst_bound 30 env -u DISPLAY /usr/bin/xstdcmap -default >"$W/o" 2>"$W/e" check_eq $S/bypath-rc "1" "$?" check_eq $S/bypath-stderr "$(refusal '')" "$(cat "$W/e")" # TWICE, IDENTICALLY. A connection setup that scribbles -- an uninitialised # sockaddr, a struct whose wasm32 packing differs from the host's -- can produce # the right answer once and a different one next time. Two runs agreeing is # cheap and is the only case here that could catch that. split 30 -default _firstrun=$serr split 30 -default check_eq $S/nodisplay-repeatable "$_firstrun" "$serr" # --- 7. the refusal with a DISPLAY nothing is listening on ------------------- # TWO TRANSPORTS, and they are different code in libX11's xtrans: a TCP display # goes through a connect(2) to a port, a `:N' display through a unix socket in # /tmp/.X11-unix. Either could HANG rather than refuse on a target whose socket # layer is new, and a hang is the failure this bound exists to name. # # 15 seconds is far more than a refusal needs -- it took 2 ms on the authoring # host -- and far less than a stalled connect would take; a 124 here IS the # finding, and it is reported as one rather than as a wrong string. withdisp 15 127.0.0.1:9 -default check_eq $S/tcp-rc "1" "$rc" check_eq $S/tcp-stderr "$(refusal '127.0.0.1:9')" "$serr" check_eq $S/tcp-stdout "" "$sout" withdisp 15 :99 -default check_eq $S/unix-rc "1" "$rc" check_eq $S/unix-stderr "$(refusal ':99')" "$serr" check_eq $S/unix-stdout "" "$sout" # `-display ' on the COMMAND LINE, which is a different variable from the # environment one (display_name at :163 rather than XDisplayName's getenv), and # it must win. The refusal naming the argument is what proves the option was # consumed rather than ignored. split 15 -display 127.0.0.1:9 -default check_eq $S/opt-display-rc "1" "$rc" check_eq $S/opt-display-stderr "$(refusal '127.0.0.1:9')" "$serr" # The command line beats the environment, in that order. withdisp 15 :99 -display 127.0.0.1:9 -default check_eq $S/opt-display-wins "$(refusal '127.0.0.1:9')" "$serr" # --- 8. the option sweep ----------------------------------------------------- # Every option the parser accepts, each asserted to be CONSUMED (no usage block, # no argument error) and to reach the same refusal. This is what keeps an # option-parsing failure distinguishable from a connection failure -- and here # that distinction can be made ONLY by the text, because both exit 1. refuses opt-all 30 -all refuses opt-best 30 -best refuses opt-blue 30 -blue refuses opt-default 30 -default refuses opt-gray 30 -gray refuses opt-green 30 -green refuses opt-red 30 -red refuses opt-verbose 30 -verbose # -delete takes an argument, and each of the six nicknames propertyTable # declares (:68-73) is accepted. The nicknames are the SHORT names -- `default', # not `RGB_DEFAULT_MAP' -- which is a fact about this program's table rather # than about X, and a table that lost an entry would show up here. refuses opt-delete-default 30 -delete default refuses opt-delete-best 30 -delete best refuses opt-delete-gray 30 -delete gray refuses opt-delete-red 30 -delete red refuses opt-delete-green 30 -delete green refuses opt-delete-blue 30 -delete blue # `all' is a seventh accepted word, handled at :156-157 rather than by the # table, and it means every property. refuses opt-delete-all 30 -delete all # Options combine, and a combination is still consumed whole. refuses opt-two 30 -best -gray refuses opt-all-verb 30 -all -verbose refuses opt-mixed 30 -delete default -best -verbose # --- 9. the argument errors, which all land on usage(1) ---------------------- # XrmParseCommand leaves an argument it did not consume in argv, and parse()'s # `if (--argc) usage(1)' at :130-131 is what turns that into the error. There is # no per-option message: every one of these produces the same usage block, which # is why they are asserted through `rejects' rather than against distinct texts. rejects unknown-opt 30 -bogus rejects unknown-double 30 --version rejects unknown-case 30 -Version rejects trailing-operand 30 -default extra # `-grey' IS NOT AN OPTION, and that is worth a case of its own rather than # being folded into unknown-opt: the property is spelled RGB_GRAY_MAP and # xsetroot next door accepts BOTH spellings, so a reader who knows that suite # would expect this one to as well. optionTable (:97-111) has only `-gray'. rejects no-grey 30 -grey # A SepArg option with nothing after it. ParseCmd.c:171-177: when the option is # the last word, XrmParseCommand does NOT consume it and writes it back into # argv, so it survives to the `--argc' test and takes the usage path. That is # the mechanism, and these two cases are the measurement of it. rejects miss-delete 30 -delete rejects miss-display 30 -display # --- 10. the precedence orderings, which are not what they look like --------- # -help BEATS -version, in either argv order, because main() tests help at :346 # and version at :350 -- the ORDER IN THE SOURCE decides, not the order on the # command line. Both of these produce the usage block and exit 0. split 30 -help -version check_eq $S/prec-help-first-rc "0" "$rc" check_eq $S/prec-help-first-first "$USAGE1" "$(first "$serr")" check_eq $S/prec-help-first-out "" "$sout" split 30 -version -help check_eq $S/prec-version-first-rc "0" "$rc" check_eq $S/prec-version-first-first "$USAGE1" "$(first "$serr")" check_eq $S/prec-version-first-out "" "$sout" # AN ARGUMENT ERROR BEATS BOTH, whichever way round, because parse() runs before # either test and usage(1) exits from inside it. This is the case that separates # this program from xsetroot, where -version wins over a later error because the # answer is given from inside the argv loop. rejects prec-version-then-bogus 30 -version -bogus rejects prec-bogus-then-version 30 -bogus -version rejects prec-help-then-bogus 30 -help -bogus # --- 11. the two silent acceptances, which are this ticket's real answer ----- # Both are `refutes-' cases: the ticket asked for a diagnostic and there is # none. They are asserted as the ABSENCE of one, which is the strongest true # statement available, plus the presence of the refusal that arrives instead. # A MAP NAME THAT IS NOT ONE OF THE STANDARD ONES. parse() :151-158 compares the # argument against each nickname and against "all", and does nothing at all when # none matches -- no else, no diagnostic, no status. So the word is accepted, # every delete flag stays clear, and the program goes on to the connect. split 30 -delete nosuchmap check_eq $S/refutes-delete-bad-rc "1" "$rc" check_eq $S/refutes-delete-bad-stderr "$(refusal '')" "$serr" case "$serr" in *"$USAGE1"*) bst_notok $S/refutes-delete-bad-no-usage \ "an unknown map name reached usage() -- parse() grew a diagnostic upstream never had" ;; *) bst_ok $S/refutes-delete-bad-no-usage ;; esac # THE SAME ANSWER AS A VALID MAP NAME, asserted directly rather than left to a # reader comparing two cases. This is the single case that says "not # reportable", and it is the one that would go red the day this image grows a # server -- which is the right time to be told. split 30 -delete default _gooddel=$serr split 30 -delete nosuchmap check_eq $S/refutes-delete-indistinguishable "$_gooddel" "$serr" # A MALFORMED DISPLAY SPECIFICATION. XDisplayName() returns the argument # verbatim (:357), so the refusal merely quotes it back. There is no parse of # the spec ahead of the connect and no separate diagnostic for a bad one. split 15 -display 'not:a:display' -default check_eq $S/refutes-display-bad-rc "1" "$rc" check_eq $S/refutes-display-bad-stderr "$(refusal 'not:a:display')" "$serr" check_eq $S/refutes-display-bad-stdout "" "$sout" # ...and it has the SAME SHAPE as an address nothing is listening on, differing # only in the string quoted back. Two inputs a reachable parser would tell # apart, one answer. split 15 -display 'not:a:display' -default _bad=$serr split 15 -display '127.0.0.1:9' -default check_eq $S/refutes-display-same-shape \ "${_bad%\"not:a:display\".}" "${serr%\"127.0.0.1:9\".}" rm -rf "$W" 2>/dev/null