. "$BST_LIB" # BST-TIMEOUT: 300 # # 81 cases. Every one is a single exec of one 216 kB binary with no corpus to # stage; the four device arms are individually bounded at 60 s by bst_probe, so # the suite's own bound only has to cover their sum. 300 leaves room for the # host's worst day and is still short enough to be a hang detector. # # yavta (0.0, SRCREV 65f740aa, meta-openembedded/meta-oe # recipes-multimedia/v4l2apps) on wasm32-hwjs glibc -- Yet Another V4L2 Test # Application, the capture-and-control test tool for Video4Linux2 devices. ONE # source file, one binary, /usr/bin/yavta. NO BBAPPEND AND NO PATCH: the stock # recipe cross-compiles unmodified here. # # --------------------------------------------------------------------------- # THERE IS NO VIDEO DEVICE BEHIND THIS MACHINE, AND THAT IS WHAT THIS SUITE # MEASURES RATHER THAN WORKS AROUND. # # yavta is a device tool with an unusually large pure-userland surface, and the # split is exact because main() is written in one pass: the whole getopt_long # loop, every value parser it calls, and two of its precondition checks run to # completion BEFORE video_open() is reached. So the format table, the size, # time-per-frame, buffer-type, field-order, timestamp-source, control-name and # file-descriptor parsers are all fully exercisable here, over well-formed and # malformed values, with the diagnostics the source prints asserted verbatim. # # What needs a device is everything after `ret = video_open(&dev, argv[optind])`. # That call is asserted to REFUSE -- bounded, with the documented message and a # non-zero status -- rather than to enumerate an empty anything. Section 6 is its # positive control: the day this platform gains a V4L2 device, the refusal cases # and the absence probes go red together and say so. # # --------------------------------------------------------------------------- # THE TWO REFUSALS ARE DIFFERENT ANSWERS AND ARE ASSERTED SEPARATELY. # # /dev/video0 does not exist. open(2) returns ENOENT and video_open prints # its own diagnostic naming the device, the strerror text and # the errno, then returns -1 and main returns 1. Nothing is # allocated, because nothing past the open runs. # # /dev/null exists and opens. This is the arm the ticket is really about: # video_querycap()'s VIDIOC_QUERYCAP ioctl fails, and querycap # RETURNS 0 ANYWAY without touching *capabilities -- # # ret = ioctl(dev->fd, VIDIOC_QUERYCAP, &cap); # if (ret < 0) # return 0; # # -- so main keeps the V4L2_CAP_VIDEO_CAPTURE it initialised the # variable with, cap_get_buf_type() answers VIDEO_CAPTURE from # that, and video_get_format() then issues VIDIOC_G_FMT on a # character device that has no such ioctl. G_FMT fails, so # `struct v4l2_format fmt` is never filled -- and the function # prints "Unable to get format" and RETURNS BEFORE reading one # field of it. With no -c, main then closes and returns 0. # # An exit of 0 here is not the tool succeeding; it is the tool # declining to invent a format. That is the whole distinction, # and it is asserted three ways: the status, the diagnostic, and # the ABSENCE of a "Video format:" line -- because a build that # read the unfilled structure would print one, full of zeros or # of stack. # # --------------------------------------------------------------------------- # THE FORMAT-TABLE COUNT IS EXACT, NOT A FLOOR. `-f help` walks pixel_formats[] # once and prints one line per entry; the table is 76 entries at this SRCREV and # a floor would pass on a table the linker had folded or truncated. A recipe # bump is expected to move this number, deliberately and visibly. bst_ok yavta/suite-canary # first line: absorbs brintos/linux#96, which drops it require yavta yavta/present W=${TMPDIR:-/tmp}/bst-yavta.$$ rm -rf "$W" if mkdir -p "$W" 2>/dev/null; then bst_ok yavta/workdir else bst_notok yavta/workdir "cannot create working directory $W" exit 0 fi export W nl=' ' # has : ok iff contains the literal . has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want:'$(_bst_1line "$2")' got:'$(_bst_1line "$3")'" ;; esac; } # hasnt : the negative, for a split that must hold. hasnt() { case "$3" in *"$2"*) bst_notok "$1" "present and must not be: $(_bst_1line "$2")" ;; *) bst_ok "$1" ;; esac; } # run : keep a command's stdout, stderr and status as # $W/.out, $W/.err and $W/.rc, and always exit 0 itself. That is # what lets bst_probe BOUND the device arms below while their own status stays # readable -- all four are expected to be judged on a status bst_probe would # otherwise score as a red. cat > "$W/run" <<'EOS' #!/bin/sh tag=$1; shift "$@" >"$W/$tag.out" 2>"$W/$tag.err" printf '%s\n' $? >"$W/$tag.rc" exit 0 EOS chmod +x "$W/run" # --- 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. yavta's do_install is hand-written and installs exactly one file, so the # one-line list is itself the assertion -- a recipe that had grown a second # artifact would show up here and nowhere else on this image. check yavta/install-path test -x /usr/bin/yavta check_eq yavta/which "/usr/bin/yavta" "$(command -v yavta)" pkglist=$(cat /var/lib/dpkg/info/yavta.list 2>/dev/null) if [ -n "$pkglist" ]; then bst_ok yavta/pkg-list hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not listed: $2" ;; esac; } hasline yavta/pkg-binary /usr/bin/yavta "$pkglist" check_eq yavta/pkg-one-binary 1 \ "$(printf '%s\n' "$pkglist" | LC_ALL=C grep -c '^/usr/bin/')" else bst_notok yavta/pkg-list "no /var/lib/dpkg/info/yavta.list -- the packaging cases below cannot run" fi # --- 2. the help screen and the format table --------------------------------- # Both exit 0 and both run before anything looks for a device. usage() is reached # from `case 'h'` and returns 0 directly; `-f help` calls list_formats() and does # the same. These are the only two invocations of yavta that succeed on this # machine, and asserting the option names keeps a silently-rebased SRCREV from # passing the rest of this suite by accident. help_out=$(yavta -h 2>&1) check_rc yavta/help-rc 0 yavta -h has yavta/help-usage "Usage: " "$help_out" has yavta/help-device " device" "$help_out" has yavta/help-capture "-c, --capture" "$help_out" has yavta/help-format "-f, --format format" "$help_out" has yavta/help-controls "-l, --list-controls" "$help_out" has yavta/help-size "-s, --size WxH" "$help_out" has yavta/help-enum " --enum-formats" "$help_out" has yavta/help-field " --field field" "$help_out" fmt_out=$(yavta -f help 2>&1) check_rc yavta/fmt-help-rc 0 yavta -f help # One line per pixel_formats[] entry, `NAME ("cccc", N planes)`. The count is # exact (see the header); the three spot checks pin the fourcc rendering, which # is four byte extractions from one u32 and the only formatting in the function. check_eq yavta/fmt-help-count 76 "$(printf '%s\n' "$fmt_out" | LC_ALL=C grep -c ' planes)$')" has yavta/fmt-help-yuyv 'YUYV ("YUYV", 1 planes)' "$fmt_out" has yavta/fmt-help-nv12m 'NV12M ("NM12", 2 planes)' "$fmt_out" has yavta/fmt-help-yuv420m 'YUV420M ("YM12", 3 planes)' "$fmt_out" # --- 3. the value parsers, over malformed input ------------------------------ # Every case here returns 1 from inside the getopt_long loop, so none of them # reaches video_open and none needs a device. The diagnostics are quoted from # main() verbatim -- these are the strings the tool is documented by. check_rc yavta/fmt-bad-rc 1 yavta -f nosuchformat has yavta/fmt-bad-msg "Unsupported video format 'nosuchformat'" "$(yavta -f nosuchformat 2>&1)" # -s parses WxH with two strtol calls; both arms of the guard are exercised. # `640` fails on the missing 'x', `640x480x` on the trailing byte after H. check_rc yavta/size-no-x-rc 1 yavta -s 640 has yavta/size-no-x-msg "Invalid size '640'" "$(yavta -s 640 2>&1)" check_rc yavta/size-trail-rc 1 yavta -s 640x480x has yavta/size-trail-msg "Invalid size '640x480x'" "$(yavta -s 640x480x 2>&1)" check_rc yavta/size-empty-rc 1 yavta -s x480 has yavta/size-empty-msg "Invalid size 'x480'" "$(yavta -s x480 2>&1)" # -t is num/denom, parsed the same way and with the same two failure arms. check_rc yavta/tpf-sep-rc 1 yavta -t 1-25 has yavta/tpf-sep-msg "Invalid time per frame '1-25'" "$(yavta -t 1-25 2>&1)" check_rc yavta/tpf-trail-rc 1 yavta -t 1/25x has yavta/tpf-trail-msg "Invalid time per frame '1/25x'" "$(yavta -t 1/25x 2>&1)" # -B is a table lookup, and buf_types[] carries a `supported` flag as well as a # name. "overlay" IS in the table and is flagged unsupported, so it is refused # with the same message an unknown name gets -- which is the flag's only visible # effect and is asserted here because nothing else on this image would show it. check_rc yavta/buftype-bad-rc 1 yavta -B nosuchtype has yavta/buftype-bad-msg 'Bad buffer type "nosuchtype"' "$(yavta -B nosuchtype 2>&1)" check_rc yavta/buftype-overlay-rc 1 yavta -B overlay has yavta/buftype-overlay-msg 'Bad buffer type "overlay"' "$(yavta -B overlay 2>&1)" check_rc yavta/field-bad-rc 1 yavta --field nosuchfield has yavta/field-bad-msg "Invalid field order 'nosuchfield'" "$(yavta --field nosuchfield 2>&1)" check_rc yavta/tstamp-bad-rc 1 yavta --timestamp-source middle has yavta/tstamp-bad-msg "Invalid timestamp source middle" "$(yavta --timestamp-source middle 2>&1)" # -r takes a numeric control id through strtol base 0; -w takes the same id # followed by a space and a value, so a name with no space fails on the SAME # diagnostic by a different guard (`*endptr != ' '` rather than `*endptr != 0`). check_rc yavta/getctrl-bad-rc 1 yavta -r brightness has yavta/getctrl-bad-msg "Invalid control name 'brightness'" "$(yavta -r brightness 2>&1)" check_rc yavta/setctrl-bad-rc 1 yavta -w brightness has yavta/setctrl-bad-msg "Invalid control name 'brightness'" "$(yavta -w brightness 2>&1)" check_rc yavta/fd-negative-rc 1 yavta --fd -1 has yavta/fd-negative-msg "Bad file descriptor -1" "$(yavta --fd -1 2>&1)" # opterr is cleared, so getopt_long returns '?' silently and yavta prints its own # pair of lines. The second is the one that makes the first useful. check_rc yavta/badopt-rc 1 yavta -Z has yavta/badopt-msg "Invalid option -?" "$(yavta -Z 2>&1)" has yavta/badopt-hint " -h for help." "$(yavta -Z 2>&1)" # Two preconditions checked after the loop and still before video_open: -C # without -u is refused outright, and a bare invocation with no device operand # prints the usage screen and returns 1 -- the same text as -h, the opposite # status, which is the distinction this pair pins. check_rc yavta/overrun-needs-userptr-rc 1 yavta -C /dev/video0 has yavta/overrun-needs-userptr-msg "Buffer overrun can only be checked in USERPTR mode." \ "$(yavta -C /dev/video0 2>&1)" check_rc yavta/no-device-rc 1 yavta has yavta/no-device-usage "Usage: " "$(yavta 2>&1)" # --- 4. well-formed values reach the device, which is where they stop --------- # The control for section 3: each of these values PARSES, so the run gets past # the getopt loop and fails at video_open instead. Without them a parser that # rejected everything would pass every case above. has yavta/size-good-parses "Error opening device" "$(yavta -s 640x480 /dev/video0 2>&1)" has yavta/tpf-good-parses "Error opening device" "$(yavta -t 1/25 /dev/video0 2>&1)" has yavta/buftype-good-parses "Error opening device" "$(yavta -B capture /dev/video0 2>&1)" has yavta/field-good-parses "Error opening device" "$(yavta --field interlaced /dev/video0 2>&1)" has yavta/fmt-good-parses "Error opening device" "$(yavta -f YUYV /dev/video0 2>&1)" # --- 5. the absent device node, bounded -------------------------------------- # bst_probe scores the WRAPPER, which always exits 0; yavta's own status is read # from the .rc file afterwards. The bound is what turns a hang into a named case # rather than a suite the runner kills with no transcript. bst_probe yavta/open-bounded "$W/open.probe" 60 "$W/run" open yavta /dev/video0 open_rc=$(cat "$W/open.rc" 2>/dev/null) open_out=$(cat "$W/open.out" 2>/dev/null) check_eq yavta/open-status "1" "$open_rc" has yavta/open-msg "Error opening device /dev/video0: No such file or directory (2)." "$open_out" hasnt yavta/open-not-opened "Device /dev/video0 opened." "$open_out" # A capture requested against the same absent node. video_open is called from the # same place whatever the mode, so this fails identically -- and the point of the # case is the two absences: no format was read and no buffer was requested, # because main returns before video_prepare_capture is reached at all. bst_probe yavta/capture-bounded "$W/cap.probe" 60 "$W/run" cap yavta -c1 /dev/video0 cap_rc=$(cat "$W/cap.rc" 2>/dev/null) cap_out=$(cat "$W/cap.out" 2>/dev/null) check_eq yavta/capture-status "1" "$cap_rc" has yavta/capture-msg "Error opening device /dev/video0" "$cap_out" hasnt yavta/capture-no-buffers "buffers requested" "$cap_out" hasnt yavta/capture-no-reqbufs "Unable to request buffers" "$cap_out" hasnt yavta/capture-no-format "Video format:" "$cap_out" # --enum-formats and --list-controls are acted on AFTER video_open, so neither # prints its heading here. That is the "no device" answer stated positively: # yavta does not enumerate an empty list, it never gets as far as enumerating. bst_probe yavta/enum-bounded "$W/enum.probe" 60 "$W/run" enum yavta --enum-formats /dev/video0 enum_rc=$(cat "$W/enum.rc" 2>/dev/null) enum_out=$(cat "$W/enum.out" 2>/dev/null) check_eq yavta/enum-status "1" "$enum_rc" hasnt yavta/enum-no-heading "- Available formats:" "$enum_out" # --- 6. a node that exists but is not a V4L2 device -------------------------- # The unfilled-format arm, described at the head of this file. /dev/null opens, # every ioctl on it fails, and the run must end by SAYING SO and exiting 0 -- # not by printing a format it never read. bst_probe yavta/null-bounded "$W/null.probe" 60 "$W/run" null yavta /dev/null null_rc=$(cat "$W/null.rc" 2>/dev/null) null_out=$(cat "$W/null.out" 2>/dev/null) check_eq yavta/null-status "0" "$null_rc" has yavta/null-opened "Device /dev/null opened." "$null_out" has yavta/null-no-format "Unable to get format:" "$null_out" hasnt yavta/null-invents-format "Video format:" "$null_out" # --- 7. the positive control: there is no video device here ------------------- # Section 5 asserts a refusal, and a refusal is only evidence while the premise # holds. These four cases are the premise, asserted rather than assumed, so that # a platform which grows a V4L2 device turns this suite red HERE as well and the # refusals above are not left reading as a regression. check yavta/no-video0-node test ! -e /dev/video0 check_eq yavta/no-video-nodes 0 \ "$(LC_ALL=C ls -1d /dev/video* 2>/dev/null | LC_ALL=C awk 'END{print NR+0}')" check_eq yavta/no-v4l-dir 0 \ "$(LC_ALL=C ls -1d /dev/v4l 2>/dev/null | LC_ALL=C awk 'END{print NR+0}')" check_eq yavta/no-v4l2-class 0 \ "$(LC_ALL=C ls -1 /sys/class/video4linux 2>/dev/null | LC_ALL=C awk 'END{print NR+0}')" rm -rf "$W"