# BST-TIMEOUT: 300 # 47 cases and ~35 execs -- SEVENTEEN runs of the four # # binaries, nine cats of an expected file, four readlinks, # # two greps of the oracle header, a find, a head and the # # console probe. On this arm a process's memory is not # # returned when it exits and /bin/sh is a 16 MB wasm module, # # so a suite's cost is dominated by how many programs it # # starts: at the ~1.5 s per exec 99-fbset-modes measured # # (56 cases, 13 s, five execs) that is ~52 s, which does not # # clear the 60 s default at all. Same value and same reason # # as 99-con2fbmap and 99-fbset-modes; this suite runs more # # programs than either because the package ships four. # # fb-test 1.1.0 -- github.com/ponty/fb-test-app at SRCREV 063ec650, packaged by # meta-openembedded/meta-oe recipes-test/fbtest. FOUR programs, not one: # # fb-test fills the framebuffer with a test pattern or a solid colour # fb-offset pans the display by setting var.xoffset/yoffset # fb-perf benchmarks pixel and line reads/writes into a logfile # fb-rect fills random rectangles forever # # THIS IMAGE HAS NO FRAMEBUFFER, and all four hardcode /dev/fb%d -- -f selects # the NUMBER, never the path. Every one of them opens the device before it draws # anything, so the drawing is unreachable here BY CONSTRUCTION and this suite is # arranged around saying which side of that open each case speaks for. # # ASSERTED FOR REAL -- everything in front of the open, byte-for-byte against an # x86-64 oracle the recipe takes at do_install from the same sources, and the # open's own failure. That is a bigger surface than it sounds, because the four # programs AGREE ABOUT NOTHING: -h exits 0 and an unknown option exits 1 in # fb-test; every arity error exits 1 in fb-offset; fb-perf prints usage and exits # 0; and against a missing device the four fail in four different ways, two of # them by not checking open(2) at all. Those disagreements are pinned as # FINDINGS about upstream, not as endorsements. # # PROBED, NOT ASSUMED -- the drawing arms run only if a framebuffer is actually # there, and skip only on a probe that says it is not. The probe is three # independent facts (the device node, /proc/fb, /sys/class/graphics), not one, # because "the node is missing" and "there is no framebuffer subsystem at all" # are different statements and only the second justifies the skip. It matters # here more than usual: wasm32_defconfig carries CONFIG_FB=y, CONFIG_DRM=y and # CONFIG_DRM_FBDEV_EMULATION=y, so the absence is a driver that does not probe # rather than a subsystem left out, and it could change without this file moving. # # WHAT THE SKIPS DO NOT COVER, AND WHERE THAT IS COVERED INSTEAD. No test on this # machine can put the ioctl payloads on the wire, so struct fb_var_screeninfo and # struct fb_fix_screeninfo's layouts and the three request numbers are asserted # at BUILD time by the recipe's do_compile:append -- and on this ILP32 arm # fb_fix_screeninfo is genuinely a different 68-byte shape from x86-64's 80. # Read the skips below as "not reachable here", not as "not measured anywhere". . "$BST_LIB" S=fb-test # THE CANARY, which asserts nothing about fb-test. # # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and NOT a defect in the host tool # (BRINTOS-1149 established that and filed it as linux-in-the-browser#177). The # harness reports the deficit but computes its pass count as total-minus- # failures, so THE DROPPED LINE IS COUNTED AS A PASS. A nothing-case therefore # stands in that position and the eaten line costs nothing. It sits ahead of the # `require' guards deliberately, because a `require' that skips would emit its # skip as the first line and that line would be eaten too. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $S/suite-canary O=/usr/libexec/fb-test-tests require find $S/find-present require readlink $S/readlink-present # ------------------------------------------------------------- what shipped fbt=$(command -v fb-test 2>/dev/null) if [ -n "$fbt" ]; then bst_ok $S/present else bst_notok $S/present "no fb-test on PATH" return 2>/dev/null || exit 0 fi # THE BASENAMES ARE PART OF THE CRITERION, not a detail. getopt(3)'s error line # and fb-offset's/fb-perf's usage all print argv[0] verbatim, and the ORACLE was # taken with argv[0] = ./ -- which for three of the four is a # name meta-oe's do_install invents (perf/rect/offset are renamed on the way in, # to avoid colliding with perf and mesa-demos). If the package ever installed # them under other names the byte comparisons below would fail for a reason that # has nothing to do with either architecture. Asserting the names first is what # keeps that failure legible. fb_dir=${fbt%/*} check_eq $S/basename 'fb-test' "${fbt##*/}" check_eq $S/bindir '/usr/bin' "$fb_dir" # The renamed three. Each must be present, and present AT THE SAME PLACE -- # a stray copy elsewhere on PATH would be run by the cases below instead. for p in fb-offset fb-perf fb-rect; do q=$(command -v "$p" 2>/dev/null) if [ "$q" = "$fb_dir/$p" ]; then bst_ok "$S/present-$p" else bst_notok "$S/present-$p" "want:'$fb_dir/$p' got:'$q'" fi done # Not alternatives symlinks into busybox, and not wrappers. busybox 1.36.1 has # no applet by any of these names, so each tool on PATH must be this package's # own ELF. `readlink -f' prints a path it could not resolve rather than failing, # so the existence test comes first. for p in fb-test fb-offset fb-perf fb-rect; do if [ ! -e "$fb_dir/$p" ]; then bst_notok "$S/not-busybox-$p" "$fb_dir/$p is on PATH but does not exist" else r=$(readlink -f "$fb_dir/$p" 2>/dev/null) case "$r" in */busybox*) bst_notok "$S/not-busybox-$p" "$p resolves to $r; busybox has no such applet, so something else has taken the name" ;; *) bst_ok "$S/not-busybox-$p" ;; esac fi done # The payload, and the package that must NOT have produced anything else here. # %y catches a symlink where a regular file is meant, %m the mode -- neither of # which `test -f' can see. Asserting the total is what makes the absences # provable from one measurement: a find without -printf produces nothing, # silently, and every case below would then read ''. # Held in a variable rather than a file: the fields carry no whitespace, so # `for' over it splits by line, and that saves both a temp file and the execs a # `wc'/`grep' pass over one would have cost. inv=$(find "$fb_dir/fb-test" "$fb_dir/fb-offset" "$fb_dir/fb-perf" \ "$fb_dir/fb-rect" "$O/oracle.txt" "$O/test-help.expected" \ /usr/share/doc/fb-test -maxdepth 0 -printf '%p|%y|%m\n' 2>/dev/null) inv_n=0 bin_line= for l in $inv; do inv_n=$((inv_n + 1)) case "$l" in "$fb_dir/fb-test|"*) bin_line=$l ;; esac done check_eq $S/inventory-n 6 "$inv_n" check_eq $S/binary-type "$fb_dir/fb-test|f|755" "$bin_line" # fb-string IS BUILT BY UPSTREAM'S MAKEFILE AND NOT INSTALLED by meta-oe's # do_install -- five programs compiled, four shipped. Pinned as a finding so the # day meta-oe fixes it (or we do) this case says so, rather than the gap simply # persisting unremarked. Its absence is the expectation TODAY, not the desired # state. if command -v fb-string >/dev/null 2>&1; then bst_notok $S/fb-string-not-installed "fb-string is on PATH; meta-oe's do_install ships four of the five programs the Makefile builds, so something has changed and the recipe comment is now wrong" else bst_ok $S/fb-string-not-installed fi # ------------------------------------------------ the argv arms, for real # EVERY RUN BELOW GOES THROUGH THIS ONE HELPER so that argv[0] is `./' on # this arm exactly as it was on the oracle's. The subshell is not cosmetic # either: a `cd' in the suite's own shell would leak into every later case. # # THE CAPTURE IS A VARIABLE, NOT A FILE, and the exit status is folded into it # as a final `rc=N' line rather than kept beside it. Two reasons, both about # this arm. Every `cat' of a capture file is another 16 MB /bin/sh, and there # would be thirteen of them; and command substitution strips trailing newlines, # so a capture compared as a bare string would silently lose a trailing blank # line. The `rc=' line is what stops the strip from reaching the output -- it is # a sentinel as much as a value, and the oracle is written by the same # construction so the two agree byte for byte. fbrun() { _b=$1; shift _r=$( ( cd "$fb_dir" && "./$_b" "$@" ) 2>&1; echo "rc=$?" ) FB_GOT=$_r FB_RC=${_r##*rc=} # no output of these tools contains 'rc=' FB_OUT=${_r%rc=*} } # : run it and compare the whole capture against the # oracle's. A missing expectation file is a red rather than a skip -- the # fb-test-tests package is on the image's install list, so its absence is a # packaging regression, not a legitimate absence. fbcmp() { _c=$1; _b=$2; shift 2 fbrun "$_b" "$@" if [ -f "$O/$_c.expected" ]; then check_eq "$S/$_c" "$(cat "$O/$_c.expected")" "$FB_GOT" else bst_notok "$S/$_c" "$O/$_c.expected is missing; fb-test-tests did not install" fi } # fb-test: the two arms that return before fb_open(). show_help() writes to # STDOUT and returns 0; getopt's default arm exits EXIT_FAILURE after getopt(3) # has printed its own diagnostic. Compared whole against the x86-64 capture of # the same sources: a real cross-arch statement about printf formatting, about # glibc's getopt error text, and about the exit status. fbcmp test-help fb-test -h fbcmp test-badopt fb-test -z # fb-offset: three arity errors and one bad option, all four reaching the same # usage line from two DIFFERENT branches -- getopt's default and the # `argc - optind != 2' check. Saying that they agree is the point; a port where # they had diverged would mean one of the two branches had been lost. fbcmp offset-noargs fb-offset fbcmp offset-onearg fb-offset 1 fbcmp offset-threeargs fb-offset 1 2 3 fbcmp offset-badopt fb-offset -z # fb-perf: argc != 3, from below and from above. UPSTREAM RETURNS 0 HERE -- a # usage error reported as success -- and that is a finding this suite pins so a # later change to it is visible. It is not an endorsement: anything scripting # fb-perf and checking its status cannot tell a benchmark that ran from one that # printed its usage and did nothing. fbcmp perf-noargs fb-perf fbcmp perf-onearg fb-perf 0 fbcmp perf-threeargs fb-perf 0 /dev/null 3 # THE STATUSES, RE-STATED AS CASES OF THEIR OWN. The whole-capture comparisons # above already cover them through the `rc=' sentinel, but a status folded into # a byte comparison reads as a formatting difference when it fails. These name # the three that a caller actually branches on. fbrun fb-test -h check_eq $S/help-rc 0 "$FB_RC" fbrun fb-test -z check_eq $S/badopt-rc 1 "$FB_RC" fbrun fb-perf check_eq $S/perf-usage-rc-is-zero 0 "$FB_RC" # THE HELP TEXT WENT TO STDOUT, which the whole-capture comparison above cannot # see -- it folds both streams together. show_help() uses printf(3), and a tool # whose help lands on stderr is a different tool for anything that pipes it. fb_help_err=$( ( cd "$fb_dir" && ./fb-test -h 2>&1 >/dev/null ) ) check_eq $S/help-on-stdout '' "$fb_help_err" # ---------------------------------------------------- is there a framebuffer # THREE INDEPENDENT FACTS, because they fail differently. A missing /dev/fb0 # with a populated /proc/fb would mean the device node had not been created -- a # different finding from a kernel whose framebuffer driver never probed, and # only the second is grounds for skipping the drawing arms below. if [ -e /dev/fb0 ]; then FB_NODE=yes; else FB_NODE=no; fi if [ -s /proc/fb ]; then FB_PROC=yes; else FB_PROC=no; fi FB_SYS=no for d in /sys/class/graphics/fb[0-9]*; do [ -e "$d" ] && FB_SYS=yes done # Recorded as cases rather than only used as a condition: the skips below are # only trustworthy if the probe that produced them is itself in the report. check_eq $S/probe-dev-fb0 'no' "$FB_NODE" check_eq $S/probe-proc-fb 'no' "$FB_PROC" check_eq $S/probe-sys-fb 'no' "$FB_SYS" if [ "$FB_NODE" = yes ] || [ "$FB_SYS" = yes ]; then FB=yes; else FB=no; fi # ------------------------------------------------- the device arm, as it is if [ "$FB" = no ]; then # FOUR PROGRAMS, FOUR DIFFERENT FAILURES against the same missing device, and # that is exactly what is worth pinning: anything scripting this package on a # machine without a framebuffer sees all four. # fb-test IS THE ONE OF THE FOUR THAT CAN TAKE THE WHOLE CAPTURE WITH IT, and # that is why it is guarded where the other three are not. fb_open() opens # /dev/tty1 and issues KDSETMODE/KD_GRAPHICS on it BEFORE it touches the # framebuffer, ignoring the result of the open -- and it never restores # KD_TEXT, because on a working target the drawing is the point and the caller # is expected to. A VT in graphics mode suppresses console output. So if this # guest's console IS a virtual console, running fb-test here would silence # every line the harness has not yet read: not this suite's results only, but # every sibling suite that runs after it in a batch image. That is the wedge # this phase quarantines a suite for, and it is cheaper to ask first. # # THE QUESTION IS "IS THE CONSOLE A VT", NOT "DOES tty1 EXIST". /dev/tty1 may # exist perfectly safely while the console is hvc0, which is the expected shape # here: wasm32_defconfig carries CONFIG_VT=y and CONFIG_VT_CONSOLE=y alongside # CONFIG_VIRTIO_CONSOLE=y. Both sources are consulted because either may be # absent, and an UNREADABLE answer is treated as a VT -- an unmeasured case # costs this ticket one result, and a wedged capture costs the whole batch its # round. CON=$(cat /sys/class/tty/console/active 2>/dev/null) [ -n "$CON" ] || CON=$(cut -d' ' -f1 /proc/consoles 2>/dev/null) if [ -z "$CON" ]; then CON_VT=unknown else CON_VT=no for c in $CON; do case "$c" in tty[0-9]*) CON_VT=yes ;; esac done fi bst_ok "$S/console-is-vt-$CON_VT" if [ "$CON_VT" = no ]; then # THE FILE NAME IS MATCHED LOOSELY ON PURPOSE. The ASSERT macro renders # __FILE__, which is whatever path the compiler was handed -- `common.c' # when make builds in ${S}, but an absolute path under a different # invocation and a mapped one under -fmacro-prefix-map. Asserting the bare # basename would make this case a statement about the build's cwd rather # than about the program; measured while dry-running this suite on x86-64, # where an absolute-path build failed it for exactly that reason. fbrun fb-test case "$FB_GOT" in *"assert("*"common.c:"*": No such file or directory"*) bst_ok $S/test-nodev ;; *) bst_notok $S/test-nodev "want an ASSERT on the /dev/fb0 open, got:'$FB_GOT'" ;; esac check_eq $S/test-nodev-rc 1 "$FB_RC" # WHICH WAY THE tty1 ARM WENT, recorded rather than assumed. Whether # KDSETMODE succeeds on a tty1 that is not the console is a separate # question from whether one exists, and either answer is legitimate. What # would NOT be legitimate is the two disagreeing -- silence from a tty that # is not there, since fb_open() then issues KDSETMODE on fd -1, which cannot # succeed. if [ -c /dev/tty1 ]; then TTY1=yes; else TTY1=no; fi case "$FB_GOT" in *"Failed to set graphics mode on tty1"*) TTY1_MSG=yes ;; *) TTY1_MSG=no ;; esac if [ "$TTY1" = no ] && [ "$TTY1_MSG" = no ]; then bst_notok $S/tty1-arm "no /dev/tty1 character device, yet fb-test did not report failing to set graphics mode on it -- fb_open() ignores the open and issues KDSETMODE on fd -1, which cannot succeed" else bst_ok $S/tty1-arm fi bst_ok "$S/tty1-node-$TTY1" else bst_skip $S/test-nodev "the console is a virtual console (console=$CON, probe=$CON_VT), and fb-test sets KD_GRAPHICS on /dev/tty1 without restoring KD_TEXT -- running it would suppress console output and take this capture, and every sibling suite after it, with it. The other three programs do not touch the tty and are measured below." bst_skip $S/test-nodev-rc "not run; see test-nodev" bst_skip $S/tty1-arm "not run; see test-nodev" fi # fb-rect: the ONE program of the four that checks its open, and it is also # the one whose exit status is worst. open_fb() prints its own message and # calls exit(-1), which the shell reports as 255. # The capture carries rect's own version banner ahead of the message, so it is # matched rather than compared whole -- there is no oracle for this program to # take the banner's exact bytes from, and hardcoding them here would be a # second, drifting copy of a string the Makefile owns. fbrun fb-rect case "$FB_OUT" in "rect "*"Error opening device /dev/fb0 : No such file or directory"*) bst_ok $S/rect-nodev ;; *) bst_notok $S/rect-nodev "want open_fb's own message after the banner, got:'$FB_OUT'" ;; esac check_eq $S/rect-nodev-rc 255 "$FB_RC" # fb-offset DOES NOT CHECK open(2) AT ALL -- `fd = open(...)' with no test, # straight into FBCTL(FBIOGET_VSCREENINFO), which is ioctl(-1, ...) and EBADF. # An upstream bug, pinned as a finding: the message names an ioctl failure # where the real fault was the open, which is what anyone debugging this on a # real target would be sent chasing. fbrun fb-offset 0 0 case "$FB_OUT" in *"fbtest error in line main:"*"Bad file descriptor"*) bst_ok $S/offset-nodev ;; *) bst_notok $S/offset-nodev "want an EBADF ioctl failure from the unchecked open, got:'$FB_OUT'" ;; esac check_eq $S/offset-nodev-rc 1 "$FB_RC" # fb-perf has the same unchecked open and a different ending: no message at # all, and `return -1' from main, which is 255. So the one program of the four # that can be given a logfile writes nothing, says nothing, and exits 255. # SILENCE IS THE ASSERTION HERE, so it is compared exactly rather than matched: # a glob on the banner would pass on any amount of text after it, which is the # one thing this case is about. The banner is taken from the oracle's own # capture instead of retyped, so the two cannot drift. fbrun fb-perf 0 /dev/null check_eq $S/perf-nodev-rc 255 "$FB_RC" if [ -f "$O/perf-noargs.expected" ]; then check_eq $S/perf-nodev-silent "$(head -n 1 "$O/perf-noargs.expected") " "$FB_OUT" else bst_notok $S/perf-nodev-silent "$O/perf-noargs.expected is missing; fb-test-tests did not install" fi bst_skip $S/draw-pattern "no framebuffer on this image (/dev/fb0 absent, /proc/fb empty, no /sys/class/graphics/fbN) -- fb-test has nothing to draw on. The FBIOGET_VSCREENINFO/FBIOGET_FSCREENINFO payload layouts are asserted at build time instead; see the recipe's do_compile:append." bst_skip $S/draw-solid "no framebuffer on this image -- fb-test -r has nothing to fill. Same build-time assertion as draw-pattern." bst_skip $S/offset-pan "no framebuffer on this image -- FBIOPUT_VSCREENINFO has nothing to answer it. Same build-time assertion as draw-pattern." bst_skip $S/rect-draw "no framebuffer on this image -- and fb-rect would not be run unbounded even if there were one: upstream's loop is \`for (i = 0; 1 || i < 10000; i++)', which never terminates. Reaching this arm needs a timeout around it first." else # THE PROBE FOUND ONE. Everything below is unreached on this image today and # is written so that the day it is reached it measures rather than skips. # THE SAME CONSOLE GUARD AS THE OTHER BRANCH, and it matters MORE here: on a # machine that has a framebuffer, fbcon is bound to it, so KD_GRAPHICS on the # console VT genuinely blanks the console rather than being absorbed by # dummycon -- and fb-test still never restores KD_TEXT. Written now rather # than left for the day this branch is first reached, because that day it # would be discovered by wedging a batch. CON=$(cat /sys/class/tty/console/active 2>/dev/null) [ -n "$CON" ] || CON=$(cut -d' ' -f1 /proc/consoles 2>/dev/null) if [ -z "$CON" ]; then CON_VT=unknown else CON_VT=no for c in $CON; do case "$c" in tty[0-9]*) CON_VT=yes ;; esac done fi bst_ok "$S/console-is-vt-$CON_VT" if [ "$CON_VT" = no ]; then fbrun fb-test -p 0 case "$FB_OUT" in *"fb res "*"x"*" virtual "*) bst_ok $S/draw-pattern ;; *) bst_notok $S/draw-pattern "rc=$FB_RC out:'$FB_OUT'" ;; esac check_eq $S/draw-pattern-rc 0 "$FB_RC" fbrun fb-test -r check_eq $S/draw-solid 0 "$FB_RC" else bst_skip $S/draw-pattern "the console is a virtual console (console=$CON) and fbcon is bound to the framebuffer; fb-test sets KD_GRAPHICS on /dev/tty1 and never restores KD_TEXT, so running it would blank the console this capture is read from" bst_skip $S/draw-solid "not run; see draw-pattern" fi # Pan to 0,0 -- the one offset that is legal on every geometry, so the case is # idempotent on a machine we do not own. fbrun fb-offset 0 0 check_eq $S/offset-pan 0 "$FB_RC" # fb-rect is STILL SKIPPED, and not for want of a device: upstream's main loop # is `for (i = 0; 1 || i < 10000; i++)' with no exit, so running it here would # hang the suite and take every sibling's results with it. Wiring it up needs a # bounded runner, which is a change to the port rather than to this file. bst_skip $S/rect-draw "fb-rect's fill loop never terminates upstream (\`for (i = 0; 1 || i < 10000; i++)'); running it unbounded would wedge the guest" bst_skip $S/test-nodev "a framebuffer is present, so the open(2) failure path is not reachable here" bst_skip $S/rect-nodev "a framebuffer is present, so the open(2) failure path is not reachable here" bst_skip $S/offset-nodev "a framebuffer is present, so the unchecked-open EBADF path is not reachable here" bst_skip $S/perf-nodev-silent "a framebuffer is present, so the unchecked-open path is not reachable here" fi # ----------------------------------------------------------- the oracle itself # The reference is a build artifact, so a case says which one was used. Without # it a suite that compared against a stale or empty expectation would read # exactly like one that compared against the right one. if [ -f "$O/oracle.txt" ]; then or_ver=$(grep '^version=' "$O/oracle.txt"); or_ver=${or_ver#version=} or_fb=$(grep '^fbpath=' "$O/oracle.txt"); or_fb=${or_fb#fbpath=} check_eq $S/oracle-version '1.1.0' "$or_ver" check_eq $S/oracle-fbpath '/dev/fb0' "$or_fb" else bst_notok $S/oracle-version "$O/oracle.txt is missing" bst_notok $S/oracle-fbpath "$O/oracle.txt is missing" fi