. "$BST_LIB" # BST-TIMEOUT: 420 # ~99 cases, each one exec of a multi-MB wasm image; that # exec is the whole cost here -- 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-xrandr.test -- xrandr 1.5.2 (poky meta/recipes-graphics/xorg-app): the # RandR command-line interface. It queries and changes screen resolution, # orientation, reflection and output layout. See # recipes-graphics/xorg-app/xrandr_%.bbappend for what the port changes -- a # static-link closure, and no patch at all. # # THERE ARE NO OUTPUTS TO ENUMERATE WITHOUT A SERVER, so the documented refusal # IS the assertion for every mode this program has. What makes that worth a # suite is that the whole Xlib connection path runs to produce it: # XOpenDisplay(), the socket address, the setup handshake. A wasm32 # socket-address length or struct-packing mistake lands exactly there, and it # lands as a crash, a hang, or a diagnostic that is not this one. Every # connection case below therefore asserts four things at once -- the stream, the # exact bytes, the status, and a bound -- because a red in any one of them names # a different fault. # # THIS IS THE ONE PORT IN THE ROUND THAT NEEDED NO PATCH, and that is not a # convenience -- it is what lets this suite assert a real option-parser surface # rather than an absence. xprop (BRINTOS-2461), xlsfonts (BRINTOS-2464) and # xkbcomp (BRINTOS-2454) each open the display before their option loop and had # to be patched before an image with no server could observe ANY option; xset # (BRINTOS-2466) splits its parse in two around the open. xrandr's main() runs # ONE argument loop, xrandr.c:2632-3267, and XOpenDisplay() is at xrandr.c:3279, # after all of it. So every parser-side behaviour is already observable here as # shipped. # # WHY THAT MATTERS BEYOND THIS TICKET. BRINTOS-2309 brings up the server, and # xrandr is how anyone will ask it what modes it has. Pinning the tool's own # behaviour now means that when there IS a server, a surprise is about the # server rather than about the tool. # # THE REFUSAL IS Xlib's, NOT THIS PROGRAM'S OWN WORDING. xrandr.c:3282 is # # fprintf (stderr, "Can't open display %s\n", XDisplayName(display_name)); # # so it is spelled `Can't open display ' -- ONE space each side, an # apostrophe in `Can't', no program-name prefix and no quotes around the name. # xlsatoms (BRINTOS-2465) writes `xlsatoms: unable to open display ""' from # libxcb and xprop writes single quotes from dsimple.c; a case copied from # either suite would go red on the wording alone. # # WITH NO DISPLAY IN THE ENVIRONMENT THE NAME IS EMPTY, so the line ends in a # single trailing SPACE. That space is asserted, byte for byte, and it is built # with printf below rather than typed into a literal where an editor would eat # it. S=xrandr require xrandr $S/present W=${TMPDIR:-/tmp}/bst-xrandr.$$ mkdir -p "$W" 2>/dev/null check $S/tmp-writable sh -c "printf x > $W/.w && rm -f $W/.w" # The exact refusal, built from the display name the run should report. # # XDisplayName(NULL) returns getenv("DISPLAY"), or "" when that is unset -- so # the empty argument here is the state this image actually boots in. refusal() { printf "Can't open display %s" "$1"; } # The exact version line. xrandr.c:3277 is # printf("xrandr program version " VERSION "\n"); # -- SEVEN spaces of padding, which is asserted rather than normalised away # because it is the literal in the source and a reflow of it would be a change # to the program's output. verline() { printf 'xrandr program version 1.5.2'; } # nodisp : run xrandr with DISPLAY REMOVED from the # environment, bounded, folding stderr into stdout; sets $out and $rc. # # `env -u DISPLAY' rather than `DISPLAY= ': XDisplayName maps both to "", but # only the absent one is the state this image ships in, and asserting the state # we ship is the point. nodisp() { _t=$1; shift; bst_run "$_t" env -u DISPLAY xrandr "$@"; } # split : the same run with the two streams kept APART; sets # $sout, $serr and $rc. # # It exists because half of what this suite asserts is WHICH STREAM the text # came out on: the refusal and every argerr() diagnostic are stderr, while the # version line and the usage block are stdout. That split is the whole # difference between "the program answered" and "the program complained", and a # folded capture cannot tell a caller which happened. split() { _t=$1; shift # BST-REDIRECT-OK: both files are under $TMPDIR, never the rootfs. bst_bound "$_t" env -u DISPLAY xrandr "$@" >"$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-REDIRECT-OK: both files are under $TMPDIR, never the rootfs. bst_bound "$_t" env DISPLAY="$_dpy" xrandr "$@" >"$W/o" 2>"$W/e" rc=$? sout=$(cat "$W/o" 2>/dev/null); serr=$(cat "$W/e" 2>/dev/null) } # --- 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/xrandr check_eq $S/which "/usr/bin/xrandr" "$(command -v xrandr)" check $S/man-installed test -s /usr/share/man/man1/xrandr.1 pkglist=$(cat /var/lib/dpkg/info/xrandr.list 2>/dev/null) if [ -n "$pkglist" ]; then bst_ok $S/pkg-list nl=' ' 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/xrandr "$pkglist" # xorg-app-common.inc's FILES:${PN} would also take ${libdir}/X11/xrandr and # the app-defaults directory if the package had grown either; xrandr ships # neither. What it DOES ship beside the binary is `xkeystone', a nickle # script generated from keystone.5c by the Makefile -- so the binary count # here is TWO entries under /usr/bin, not one, and that is the shape # asserted rather than a copy of the sibling ports' `1'. check_eq $S/pkg-bin-count 2 \ "$(printf '%s\n' "$pkglist" | LC_ALL=C grep -c '^/usr/bin/')" hasline $S/pkg-xkeystone /usr/bin/xkeystone "$pkglist" else bst_notok $S/pkg-list "no /var/lib/dpkg/info/xrandr.list -- the packaging cases cannot run" fi # --- 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. xrandr failing to # open a display is a status the PROGRAM chose, so a status of 1 with xrandr's # own diagnostic on stderr is proof that the image bound libXrandr, libXrender, # libXext, libX11, libxcb, libXau and libXdmcp and entered main(). If the # .bbappend's XRANDR_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. nodisp 30 check_eq $S/linked-and-ran "1" "$rc" # --- 3. the documented refusal, with no DISPLAY ------------------------------ # A bare `xrandr' is the documented default: with no action requested, # xrandr.c:3269 sets query = True, so this is the program's MAIN mode reaching # its documented refusal, not an edge case. split 30 check_eq $S/nodisplay-rc "1" "$rc" check_eq $S/nodisplay-stderr "$(refusal '')" "$serr" check_eq $S/nodisplay-stdout "" "$sout" # THE TRAILING SPACE, ON ITS OWN. The case above would also pass if both sides # lost it to the same editor; this one cannot. check_eq $S/nodisplay-name-empty "19" "$(printf '%s' "$serr" | wc -c | tr -d ' ')" # 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 _first=$serr split 30 check_eq $S/nodisplay-repeatable "$_first" "$serr" # NOTHING IS WRITTEN ON THE WAY OUT. Every printing path in this program is # downstream of the connect, so a refusing run must produce no stdout at all -- # asserted above -- and must not have got far enough to emit a partial dump. nodisp 30 check_eq $S/refusal-is-one-line "1" "$(printf '%s\n' "$out" | wc -l | tr -d ' ')" # --- 4. the refusal with a DISPLAY nothing is listening on ------------------- # TWO TRANSPORTS, and they are different code in libxcb underneath Xlib: 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 and far less than a stalled # connect would take; a 124 here IS the finding. # # The name in the message comes from getenv("DISPLAY") here, so these also # assert that the reported name is the one that was actually tried. withdisp 15 127.0.0.1:9 --query 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 --query check_eq $S/unix-rc "1" "$rc" check_eq $S/unix-stderr "$(refusal ':99')" "$serr" check_eq $S/unix-stdout "" "$sout" # --- 5. --version: printed BEFORE the connection, and NOT a protocol version -- # xrandr.c:3276-3277 prints the program version just above XOpenDisplay(), so # the line appears on an image with no server. It does NOT exit: the flag also # sets action_requested, execution falls through to the open, and the run ends # in the refusal with status 1. Both halves are asserted, because a build that # reordered main() would change one without the other. split 30 --version check_eq $S/version-rc "1" "$rc" check_eq $S/version-stdout "$(verline)" "$sout" check_eq $S/version-stderr "$(refusal '')" "$serr" split 30 -v check_eq $S/v-short-rc "1" "$rc" check_eq $S/v-short-stdout "$(verline)" "$sout" # THE VERSION IS THE RECIPE'S PV, END TO END -- configure.ac's AC_INIT through # the .bb's PV to the shipped binary. A recipe bumped without its suite would # land here. case "$sout" in *" 1.5.2") bst_ok $S/version-is-pv ;; *) bst_notok $S/version-is-pv "unexpected: $sout" ;; esac # NO RandR PROTOCOL VERSION IS PRINTED, and this is asserted as an ABSENCE # because the ticket that commissioned this port expected one. The only such # line in the program is xrandr.c:4036's `Server reports RandR version %d.%d', # printed from XRRQueryVersion()'s answer -- a fact about the SERVER, reachable # only after a successful connect. On an image with no server it cannot arrive, # and a suite that expected it would be red for the life of the port. case "$sout$serr" in *"RandR version"*) bst_notok $S/version-no-protocol-line "printed a protocol version: $sout$serr" ;; *) bst_ok $S/version-no-protocol-line ;; esac case "$sout$serr" in *"Server reports"*) bst_notok $S/version-no-server-report "printed a server report: $sout$serr" ;; *) bst_ok $S/version-no-server-report ;; esac # IT STILL PRINTS WITH A DISPLAY SET TO SOMETHING DEAD, because the print is # upstream of the connect. Without this, `version-stdout' above would be equally # consistent with a program that connects first and happens to find no server. withdisp 15 127.0.0.1:9 --version check_eq $S/version-beats-connect-rc "1" "$rc" check_eq $S/version-beats-connect-stdout "$(verline)" "$sout" check_eq $S/version-beats-connect-stderr "$(refusal '127.0.0.1:9')" "$serr" # --- 6. --help: the one invocation that succeeds on this image --------------- # xrandr.c:2639-2641 calls usage() and exit(0) from inside the loop, so --help # is answered with no server AND with status 0 -- the only case in this suite # that is not a refusal. usage() uses printf(3), so the block is STDOUT; a port # that sent it to stderr would still "work" interactively and would be wrong. split 30 --help check_eq $S/help-rc "0" "$rc" check_eq $S/help-stderr "" "$serr" case "$sout" in "usage: xrandr [options]"*) bst_ok $S/help-header ;; *) bst_notok $S/help-header "unexpected first line: $(printf '%s' "$sout" | head -1)" ;; esac # `-help' with one dash is accepted by the same branch. Upstream's choice, and # the kind of thing a later "cleanup" would quietly drop. split 30 -help check_eq $S/help-single-dash-rc "0" "$rc" # Option lines out of the usage body, so a truncated or garbled block is a red # rather than a pass on the header alone. One is taken from each of the three # indentation levels the block uses, because they are emitted from one string # literal and a mangled one would lose a level whole. helplists() { case "$sout" in *"$2"*) bst_ok "$S/help-lists-$1" ;; *) bst_notok "$S/help-lists-$1" "usage does not list '$2'" ;; esac } helplists query " -q or --query" helplists mode " --mode " helplists monitors " --listmonitors" # The block ends where the source says it does, so a capture truncated by the # runner is distinguishable from a program that printed less. case "$sout" in *"--delmonitor "*) bst_ok $S/help-complete ;; *) bst_notok $S/help-complete "usage block does not reach its last line" ;; esac # --- 7. every mode is ACCEPTED by the parser and reaches the SAME refusal ----- # This is the section the ticket is really about. Each of these is a distinct # code path through the one option loop -- a distinct flag set, a distinct # operand grammar, and for the --output cases a config_output allocated and # populated -- and every one of them must come out at xrandr.c:3279 with the # identical Xlib refusal. That identity is what keeps an option-parsing failure # DISTINGUISHABLE from a connection failure: a parse error would produce # xrandr's own `xrandr: ' prefix and the `Try ...' second line instead, which # section 8 asserts positively. # # accepted : parses, reaches the open, refuses there. accepted() { _id=$1; shift split 30 "$@" if [ "$rc" != "1" ]; then bst_notok "$S/accept-$_id" "rc=$rc, expected 1" elif [ -n "$sout" ]; then bst_notok "$S/accept-$_id" "wrote to stdout: $sout" elif [ "$serr" != "$(refusal '')" ]; then bst_notok "$S/accept-$_id" "not the connection refusal: $serr" else bst_ok "$S/accept-$_id" fi } accepted query --query accepted q-short -q accepted current --current accepted listmonitors --listmonitors accepted listactive --listactivemonitors accepted listproviders --listproviders accepted props --prop accepted verbose --verbose accepted screen --screen 0 accepted size --size 0 accepted rate --rate 60 accepted orientation -o normal accepted reflect-x -x accepted noprimary --noprimary accepted fb --fb 1920x1080 accepted dpi --dpi 96 # The --output family. Each needs a preceding --output, and each writes a # different member of the same config_output -- so a struct-layout fault on # wasm32 would show up as one of these diverging from its siblings rather than # as all of them failing together. accepted output-bare --output HDMI-1 accepted output-mode --output HDMI-1 --mode 1920x1080 accepted output-rotate --output HDMI-1 --rotate left accepted output-reflect --output HDMI-1 --reflect x accepted output-leftof --output HDMI-1 --left-of VGA-1 accepted output-primary --output HDMI-1 --primary accepted output-off --output HDMI-1 --off accepted output-auto --output HDMI-1 --auto accepted output-pos --output HDMI-1 --pos 0x0 accepted output-scale --output HDMI-1 --scale 2x2 # The mode-table family. --newmode takes TEN operands and --addmode two, and # both build a umode_t on the heap before the connect -- the largest allocation # this program makes without a server. accepted newmode --newmode brintos-test 148.50 1920 2008 2052 2200 1080 1084 1089 1125 +HSync +VSync accepted newmode-noflags --newmode brintos-test 148.50 1920 2008 2052 2200 1080 1084 1089 1125 accepted addmode --addmode HDMI-1 brintos-test accepted rmmode --rmmode brintos-test accepted delmode --delmode HDMI-1 brintos-test # Two modes composed in ONE invocation, which is how xrandr is actually used and # is the only case here that exercises the loop's carry-over between operands. accepted composed --output HDMI-1 --mode 1920x1080 --rotate left --primary --output VGA-1 --off # --dryrun does NOT skip the connect. It is a flag consumed by the loop # (xrandr.c:2652) and read only by code downstream of the open, so on this image # it changes nothing -- asserted, because "dry run" reads like it should. accepted dryrun --dryrun --output HDMI-1 --off # --- 8. the error paths: parsed, diagnosed, and NEVER connected -------------- # Every one of these is argerr(), xrandr.c:194-205: `: ' then the # message then a SECOND line `Try ' --help' for more information.' # then exit(1). All of it on stderr, all of it before XOpenDisplay(). # # program_name is argv[0] with no basename() (xrandr.c:2631), so it reads # `xrandr' only because every invocation here goes through PATH. A case that ran # /usr/bin/xrandr by path would have to expect the path. # # argerr : the diagnostic, the Try line, no stdout, # status 1, and -- the point of the section -- NOT the connection refusal. argerr() { _id=$1; _msg=$2; shift 2 split 30 "$@" _want="xrandr: $_msg Try 'xrandr --help' for more information." if [ "$rc" != "1" ]; then bst_notok "$S/argerr-$_id" "rc=$rc, expected 1" elif [ -n "$sout" ]; then bst_notok "$S/argerr-$_id" "wrote to stdout: $sout" elif [ "$serr" = "$(refusal '')" ]; then bst_notok "$S/argerr-$_id" "reached the connection instead of diagnosing" elif [ "$serr" != "$_want" ]; then bst_notok "$S/argerr-$_id" "want:'$_want' got:'$serr'" else bst_ok "$S/argerr-$_id" fi } argerr unknown-option "unrecognized option '--brintos-no-such-option'" \ --brintos-no-such-option argerr mode-without-output "--mode must be used after --output" \ --mode 1920x1080 argerr rotate-without-output "--rotate must be used after --output" \ --rotate left argerr rotate-bad-direction "--rotate: invalid argument 'sideways'" \ --output HDMI-1 --rotate sideways argerr reflect-bad "--reflect: invalid argument 'diagonal'" \ --output HDMI-1 --reflect diagonal argerr size-not-numeric "failed to parse 'notanumber' as a number" \ --size notanumber argerr size-negative "--size argument must be nonnegative" \ --size -1 argerr rate-not-numeric "failed to parse 'sixty' as a number" \ --rate sixty argerr pos-malformed "failed to parse 'nonsense' as a position" \ --output HDMI-1 --pos nonsense argerr newmode-short "failed to parse 'brintos-test' as a mode specification" \ --newmode brintos-test argerr newmode-bad-number "failed to parse 'xxxx' as a number" \ --newmode brintos-test 148.50 xxxx 2008 2052 2200 1080 1084 1089 1125 argerr display-no-argument "--display requires an argument" \ --display argerr screen-no-argument "--screen requires an argument" \ --screen # THE SAME DIAGNOSTIC WINS OVER A LIVE-LOOKING DISPLAY. With DISPLAY set to an # address nothing is listening on, a program that connected first would produce # the refusal; this one still produces the parse error, which is the positive # form of "the parse is upstream of the connect". withdisp 15 127.0.0.1:9 --brintos-no-such-option check_eq $S/argerr-beats-connect-rc "1" "$rc" case "$serr" in "xrandr: unrecognized option"*) bst_ok $S/argerr-beats-connect-msg ;; *) bst_notok $S/argerr-beats-connect-msg "unexpected: $serr" ;; esac # AND IT IS NOT A CRASH. Status 1 is the program choosing; a wasm trap or an # abort would arrive as something else entirely, and the bound above would name # a hang. Asserted once over the whole family rather than per case. split 30 --brintos-no-such-option check_eq $S/argerr-not-a-crash "1" "$rc" # `--newmode' AS THE LAST ARGUMENT reads argv[argc], which C guarantees is NULL, # and prints it through %s. Under glibc that is the literal `(null)' rather than # a fault -- upstream's bug, harmless here, and asserted so that a libc change # or a wasm32 printf that faulted on it would be a red rather than a surprise. split 30 --newmode check_eq $S/newmode-bare-rc "1" "$rc" case "$serr" in "xrandr: failed to parse "*" as a mode specification"*) bst_ok $S/newmode-bare-msg ;; *) bst_notok $S/newmode-bare-msg "unexpected: $serr" ;; esac # --- 9. what xrandr does NOT validate, asserted as an ABSENCE ---------------- # The ticket asks for the modeline arithmetic to be asserted "where the tool # does so". It does not do so. xrandr.c:3085-3125 parses --newmode's ten # operands with check_strtod/check_strtol and stores them into an XRRModeInfo # with no further checking: not the ordering (hsync-start < hsync-end <= # htotal, and the vertical triple), not the clock's plausibility, not even # non-negativity. Those are the SERVER's to reject. # # Asserting the refusal for these is the strongest TRUE statement available; # asserting a diagnostic that cannot arrive would be a case that fails for the # wrong reason for the life of the port. accepted newmode-reversed-h \ --newmode bad 148.50 1920 2052 2008 2200 1080 1084 1089 1125 accepted newmode-reversed-v \ --newmode bad 148.50 1920 2008 2052 2200 1080 1089 1084 1125 accepted newmode-zero-clock \ --newmode bad 0 1920 2008 2052 2200 1080 1084 1089 1125 accepted newmode-negative \ --newmode bad 148.50 -1 -1 -1 -1 -1 -1 -1 -1 accepted newmode-sync-past-total \ --newmode bad 148.50 1920 9000 9001 2200 1080 1084 1089 1125 # Nor is an OUTPUT name validated: it is a string until the server is asked. accepted output-empty-name --output "" --off # --- 10. the ticket's own premise, as a case --------------------------------- # "There are no outputs to enumerate without a server." Nothing in the image # provides one, and the two packages this port adds ship no server, no driver # and no socket -- so the refusals above are the image's real state rather than # a misconfiguration. A day when this stops being true is the day BRINTOS-2309 # lands, and this case is where that shows up first. check $S/no-x11-socket-dir sh -c '! test -d /tmp/.X11-unix || test -z "$(ls -A /tmp/.X11-unix 2>/dev/null)"' check_eq $S/no-display-in-env "" "${DISPLAY:-}" rm -rf "$W" 2>/dev/null