. "$BST_LIB" # BRINTOS-WORKAROUND: brintos/linux-in-the-browser#138 remove -- the FIRST line a # suite writes after the runner's ==BST-BEGIN== is swallowed when a kernel printk # lands on the console in between, which it reliably does here (the pending-signal # region diagnostic, ~270 chars). The result was a scored case vanishing from the # transcript while the runner's own cases= count still included it: 157 captured of # 158 emitted, reproducibly, across three runs. This absorbs the loss with a line # nobody scores, so the ==BST-*== protocol lines and every case survive. It is not # cosmetic -- a harness that counts result lines was reading 157 of 157 green and # was wrong about COVERAGE, which is the direction nobody checks. Drop this once # #138 lands; the arithmetic that detects the loss (cases= vs BST lines) stays # useful either way. printf '#diag: libevdev suite start (this line is a console-loss absorber, not a case)\n' # libevdev 1.13.1 -- the wrapper library for the kernel evdev input protocol # (poky meta/recipes-support/libevdev), what turns the EVIOC* ioctls and the # struct input_event stream into a typed API. What ships here is # /usr/lib/libevdev.a, /usr/lib/pkgconfig/libevdev.pc, the two headers under # /usr/include/libevdev-1.0/libevdev/, three programs in /usr/bin, and the # probe this suite scores. # # --------------------------------------------------------------------------- # THE LIBRARY DIVIDES IN TWO, AND ONLY ONE HALF NEEDS A DEVICE. # # * THE NAME TABLES -- libevdev_event_type_get_name and its five siblings, # resolving EV_*/KEY_*/REL_*/ABS_*/INPUT_PROP_* both ways over a static # table. No descriptor, no syscall, no device. Groups A/B/C/D/E/F below. # * THE DEVICE LAYER -- libevdev_new_from_fd and everything reachable from it, # which is EVIOCGBIT/EVIOCGNAME/EVIOCGID against an evdev chardev. Group G # drives its documented REFUSALS, which need no device either; group H # reports whether this guest has one at all. # # THE TICKET ASKS FOR EXACTLY THOSE TWO DEVICE-FREE HALVES, plus a third that # depends on the kernel: creating a synthetic device through uinput, writing # events into it and reading them back. wasm32_defconfig sets CONFIG_INPUT=y, # CONFIG_INPUT_EVDEV=y and CONFIG_VIRTIO_INPUT=y but NOT CONFIG_INPUT_UINPUT # (which needs CONFIG_INPUT_MISC=y first), so /dev/uinput does not exist and # that half cannot run. Group U asserts it anyway, scored so that OK is GREEN -- # it flips by itself the day the config lands and needs no edit here. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86-64 RUN, AND HERE THAT IS THE RIGHT POSTURE. # # Every value pinned below is what a native x86-64 build of libevdev_probe.c # produced against libevdev 1.13.1. libevdev/event-names.h is GENERATED at build # time by make-event-names.py from the kernel's own input-event-codes.h, so the # wasm32 build and the x86-64 build read the same input and must produce the same # table: a mismatch is a real defect, the same posture as 79-libndp next door and # the opposite of 79-libuv, where the values the port turns on are REQUIRED to # differ. # # It is also the assertion a target-only run cannot make. Round-tripping a name # through the library proves the two directions agree with each other; it does # not prove either is right, and a table generated with the same fault in both # directions passes every round trip. That is why BOTH halves are pinned to the # native run -- the name string in the C group and the numeric code in the D # group -- rather than only asserting that C and D agree. # # The numbers were checked against the kernel uapi rather than merely copied out # of the native run: KEY_A=30, KEY_ESC=1, KEY_ENTER=28, BTN_LEFT=0x110=272, # BTN_TOUCH=0x14a=330, REL_WHEEL=8, ABS_MT_POSITION_X=0x35=53, MSC_SCAN=4, # LED_CAPSL=1, SND_BELL=1, SYN_DROPPED=3, and the maxima KEY_MAX=0x2ff=767, # REL_MAX=0x0f=15, ABS_MAX=0x3f=63, SYN_MAX=0xf=15, LED_MAX=0x0f=15, # SW_MAX=0x10=16 (include/uapi/linux/input-event-codes.h). PROBE=${BST_LIBEVDEV_PROBE:-/usr/libexec/libevdev-tests/libevdev-probe} ARCHIVE=/usr/lib/libevdev.a PCFILE=/usr/lib/pkgconfig/libevdev.pc INC=/usr/include/libevdev-1.0/libevdev # The version the recipe pins. Asserted rather than read back, so a recipe # bumped without updating this suite is a red here instead of a silent change of # subject -- the pinned table above is the oracle for THIS version only. WANT_VERSION=1.13.1 if [ ! -f "$ARCHIVE" ]; then bst_skip libevdev/present "libevdev not installed on this image" exit 0 fi bst_ok libevdev/present # --- the package is whole ----------------------------------------------------- check libevdev/staticlib test -s "$ARCHIVE" check libevdev/pkgconfig test -s "$PCFILE" check libevdev/hdr-libevdev test -s "$INC/libevdev.h" check libevdev/hdr-uinput test -s "$INC/libevdev-uinput.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libevdev/no-shared "/usr/lib/libevdev.so" "/usr/lib/libevdev.so.2" # The .pc must name the version the recipe pinned, or a consumer resolving # libevdev through pkg-config gets a different answer from the one this suite # pinned its table against. The include path matters as much as the version # here: libevdev's headers do NOT sit at the top of ${includedir}, and a .pc # that forgot the libevdev-1.0 component would leave every consumer's # unresolvable while the files were all present. pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libevdev/pkgconfig-version "$WANT_VERSION" "$pcver" check_out libevdev/pkgconfig-libs "-levdev" cat "$PCFILE" check_out libevdev/pkgconfig-incdir "libevdev-1.0" cat "$PCFILE" # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes. -a where the grep supports it -- # on this arm the archive members are LLVM IR bitcode (ThinLTO), so a grep that # suppresses matches in a file containing NULs reports nothing; GNU grep needs # -a, busybox grep as shipped does not. Probing keeps this measuring libevdev # rather than measuring the image's grep. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in libevdev_new libevdev_new_from_fd libevdev_free libevdev_set_fd \ libevdev_get_fd libevdev_get_name libevdev_next_event \ libevdev_has_event_type libevdev_has_event_code \ libevdev_event_type_get_name libevdev_event_code_get_name \ libevdev_event_value_get_name libevdev_property_get_name \ libevdev_event_type_from_name libevdev_event_code_from_name \ libevdev_event_value_from_name libevdev_property_from_name \ libevdev_event_type_from_code_name libevdev_event_code_from_code_name \ libevdev_event_type_get_max libevdev_enable_event_code \ libevdev_uinput_create_from_device libevdev_uinput_write_event \ libevdev_uinput_get_devnode libevdev_uinput_destroy; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libevdev/sym-$sym" else bst_notok "libevdev/sym-$sym" "$sym absent from $ARCHIVE" fi done # --- the three shipped programs ----------------------------------------------- # All three take a /dev/input/eventN path and do nothing without a device, so # what is measurable here is that they LINK AND START -- which on this platform # is exactly the thing that tends not to hold (libuv's loop, libdaq's thread). # Each exits 1 and prints its usage when given no device, so the pair (rc, text) # separates "ran and refused" from "could not start at all". check_out is not # usable: it requires exit 0. tool_usage() { # [args...] _tid=$1; _tneedle=$2; shift 2 if [ ! -x "$1" ]; then bst_notok "$_tid" "$1 absent or not executable" return fi _tout=$("$@" 2>&1); _trc=$? if bst_execfail "$(printf '%s' "$_tout" | head -c 200)"; then bst_unrun "$_tid" "$_tout" elif [ "$_trc" -eq 1 ] && [ "${_tout#*$_tneedle}" != "$_tout" ]; then bst_ok "$_tid" else bst_notok "$_tid" "rc=$_trc want 1 + '$_tneedle': $(_bst_1line "$_tout")" fi } tool_usage libevdev/tool-edge-detector "Usage: touchpad-edge-detector" \ /usr/bin/touchpad-edge-detector tool_usage libevdev/tool-mouse-dpi "Usage: mouse-dpi-tool" \ /usr/bin/mouse-dpi-tool tool_usage libevdev/tool-tweak-device "libevdev-tweak-device --abs" \ /usr/bin/libevdev-tweak-device # A path that is not a device: the tool must report the open failure and exit, # not crash. This is the shipped programs' one reachable functional path here. tool_usage libevdev/tool-mouse-dpi-badpath "Error opening the device" \ /usr/bin/mouse-dpi-tool /nonexistent-device # --- the probe ---------------------------------------------------------------- if [ ! -x "$PROBE" ]; then bst_skip libevdev/probe-runs "$PROBE absent from the image" exit 0 fi pout=${TMPDIR:-/tmp}/bst-libevdev-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libevdev/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libevdev/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libevdev/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libevdev/probe-complete else bst_notok libevdev/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # val -- the probe's value for one case, empty when the probe stopped # first. Matched on the ID in field 1 rather than taken by position, since the # probe's output is captured with 2>&1 and the library may write to stderr. val() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$pout"; } # want -- score one probe value against a pinned answer. # A missing value is reported as the probe having stopped early rather than as a # mismatch against '', which is a different fault and localises to a different # line. The probe prints EMPTY for a legitimately empty string, so nothing it # actually emitted can arrive here as ''. want() { _got=$(val "$1") if [ -z "$_got" ]; then bst_notok "libevdev/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libevdev/$2" else bst_notok "libevdev/$2" "want:'$3' got:'$_got'" fi } # --- A/B: the event-type table, both directions ------------------------------- want A01 type-name-syn "EV_SYN" want A02 type-name-key "EV_KEY" want A03 type-name-rel "EV_REL" want A04 type-name-abs "EV_ABS" want A05 type-name-msc "EV_MSC" want A06 type-name-sw "EV_SW" want A07 type-name-led "EV_LED" want A08 type-name-snd "EV_SND" want A09 type-name-rep "EV_REP" want A10 type-name-ff "EV_FF" want A11 type-name-pwr "EV_PWR" want A12 type-name-ff-status "EV_FF_STATUS" # The reverse direction. The gaps are the point: EV_SW is 5 and EV_LED is 0x11, # so 6..16 are undefined and a table that packed the types contiguously would # answer 6 here and pass every forward case above. want B01 type-from-name-syn "0" want B02 type-from-name-key "1" want B03 type-from-name-rel "2" want B04 type-from-name-abs "3" want B05 type-from-name-msc "4" want B06 type-from-name-sw "5" want B07 type-from-name-led "17" want B08 type-from-name-snd "18" want B09 type-from-name-rep "20" want B10 type-from-name-ff "21" want B11 type-from-name-pwr "22" want B12 type-from-name-ff-status "23" # --- C/D: the event-code tables, across six type spaces ----------------------- want C01 code-name-syn-report "SYN_REPORT"; want D01 code-from-name-syn-report "0" want C02 code-name-syn-dropped "SYN_DROPPED"; want D02 code-from-name-syn-dropped "3" want C03 code-name-key-esc "KEY_ESC"; want D03 code-from-name-key-esc "1" want C04 code-name-key-a "KEY_A"; want D04 code-from-name-key-a "30" want C05 code-name-key-enter "KEY_ENTER"; want D05 code-from-name-key-enter "28" want C06 code-name-btn-left "BTN_LEFT"; want D06 code-from-name-btn-left "272" want C07 code-name-btn-touch "BTN_TOUCH"; want D07 code-from-name-btn-touch "330" want C08 code-name-rel-x "REL_X"; want D08 code-from-name-rel-x "0" want C09 code-name-rel-wheel "REL_WHEEL"; want D09 code-from-name-rel-wheel "8" want C10 code-name-abs-x "ABS_X"; want D10 code-from-name-abs-x "0" want C11 code-name-abs-mt-x "ABS_MT_POSITION_X"; want D11 code-from-name-abs-mt-x "53" want C12 code-name-msc-scan "MSC_SCAN"; want D12 code-from-name-msc-scan "4" want C13 code-name-led-capsl "LED_CAPSL"; want D13 code-from-name-led-capsl "1" want C14 code-name-sw-lid "SW_LID"; want D14 code-from-name-sw-lid "0" want C15 code-name-snd-bell "SND_BELL"; want D15 code-from-name-snd-bell "1" want C16 code-name-rep-delay "REP_DELAY"; want D16 code-from-name-rep-delay "0" # The type-less lookups have to resolve a bare code name across the WHOLE table # and recover its type. SYN_REPORT, REL_X and ABS_X are all code 0 in different # type spaces, so these are the cases that show the type is genuinely recovered # and not defaulted. want D20 code-from-code-name-key-a "30" want D21 type-from-code-name-key-a "1" want D22 code-from-code-name-rel-wheel "8" want D23 type-from-code-name-rel-wheel "2" want D24 code-from-code-name-abs-mt-x "53" want D25 type-from-code-name-abs-mt-x "3" # --- E: the documented refusals ----------------------------------------------- # libevdev.h: "libevdev_event_code_get_name() for an undefined type or code will # always return NULL", and the *_from_name family returns -1. 0x1e is INSIDE the # EV_MAX bound and undefined, which is what separates a real table lookup from a # bounds check that happens to return NULL past the end. want E01 undef-type-inside-bound "NULL" want E02 undef-type-past-max "NULL" want E03 undef-type-huge "NULL" want E04 undef-code-in-def-type "NULL" want E05 code-past-type-max "NULL" want E06 code-huge "NULL" want E07 code-under-undef-type "NULL" want E08 unknown-type-name "-1" want E09 unknown-code-name "-1" want E10 empty-type-name "-1" want E11 empty-code-name "-1" # The cross-type refusals. A lookup that ignored its type argument would answer # 30 at E12 and still pass every case above -- this is the one assertion in the # file that the type argument is used at all. want E12 key-name-under-rel "-1" want E13 rel-name-under-key "-1" want E14 key-name-under-undef "-1" want E15 code-name-to-type-lookup "-1" want E16 type-name-to-code-lookup "-1" want E17 type-name-to-type-code "-1" # The _n variants take a length instead of a terminator: "KEY_AX" cut at 5 is # KEY_A, and at 6 is nothing. E20 does the same to a type name. want E18 code-from-name-n-truncated "30" want E19 code-from-name-n-full "-1" want E20 type-from-name-n-truncated "1" # --- F: per-type maxima, properties, and the value table ---------------------- want F01 max-syn "15" want F02 max-key "767" want F03 max-rel "15" want F04 max-abs "63" want F05 max-led "15" want F06 max-sw "16" want F07 max-undef-type-inside "-1" want F08 max-undef-type-past "-1" want F10 prop-name-pointer "INPUT_PROP_POINTER" want F11 prop-name-direct "INPUT_PROP_DIRECT" want F12 prop-name-buttonpad "INPUT_PROP_BUTTONPAD" want F13 prop-name-past-max "NULL" want F14 prop-from-name-pointer "0" want F15 prop-from-name-buttonpad "2" want F16 prop-from-name-unknown "-1" # The value table exists for exactly ONE (type, code) pair -- # libevdev_event_value_from_name_n refuses anything that is not # EV_ABS/ABS_MT_TOOL_TYPE before it even looks at the name. Both sides are # pinned: the pair that has names, and an ordinary pair that does not. want F17 value-name-mt-finger "MT_TOOL_FINGER" want F18 value-name-mt-pen "MT_TOOL_PEN" want F19 value-from-name-mt-finger "0" want F20 value-from-name-mt-pen "1" want F21 value-from-name-unknown "-1" want F22 value-name-wrong-pair "NULL" want F23 value-from-name-wrong-pair "-1" # --- G: libevdev_new_from_fd against things that are not input devices -------- # The ticket's second half. libevdev_set_fd's first act is # ioctl(fd, EVIOCGBIT(0, ...)) and it returns -errno from the failure path # (libevdev.c: `return rc ? -errno : 0`), so on anything that is not an evdev # chardev the documented answer is -ENOTTY -- and the assertion is that it is # that, rather than a crash. want G01 new-allocates "OK" want G02 fresh-dev-fd "-1" want G03 fresh-dev-name "EMPTY" want G04 fresh-dev-bustype "0" want G05 fresh-dev-has-ev-key "0" # The control: a negative fd is refused BEFORE any ioctl, with a different errno # from the three below. Without it, an ENOTTY at G07 would be equally consistent # with libevdev refusing every fd it is handed. want G06 from-fd-negative "EBADF" want G07 from-fd-regular-file "ENOTTY" want G08 from-fd-dev-null "ENOTTY" want G09 from-fd-pipe "ENOTTY" # A failed set_fd must leave the device UNinitialized, so a second attempt is # refused for the same reason as the first and not as "already initialized". want G10 set-fd-negative "EBADF" want G11 set-fd-negative-again "EBADF" want G12 free-null-is-noop "OK" # --- H: the real device, if this guest has one -------------------------------- # CONFIG_INPUT_EVDEV=y and CONFIG_VIRTIO_INPUT=y, so /dev/input/event0 exists # whenever the host attaches a virtio-input device. The local-verify runner and # the browser both boot this guest with none, so this is a DIAGNOSTIC rather than # a requirement -- reported, and green either way, because the ticket's coverage # is explicitly the device-free surface. A device that IS present and cannot be # opened through libevdev is the one outcome that is a real failure. h01=$(val H01) case "$h01" in ENOENT|ENXIO|ENODEV) bst_ok libevdev/device-absent ;; OK) bst_ok libevdev/device-absent want H02 device-opens "OK" ;; "") bst_notok libevdev/device-absent "no value on target (probe stopped before H01)" ;; *) bst_notok libevdev/device-absent "/dev/input/event0 answered $h01, which is neither absent nor openable" ;; esac printf '#diag: libevdev event0=%s name=%s uinput=%s\n' \ "$(val H01)" "$(val H03)" "$(val U01)" # --- U: the synthetic device, if this kernel carries uinput ------------------- # The ticket's third half, explicitly conditioned on the guest carrying uinput. # Scored the way 79-libnet scores its AF_PACKET group: OK is GREEN, so the case # flips by itself the day CONFIG_INPUT_UINPUT lands and needs no edit here; the # exact tracked errno is a red that names its issue; anything else is an # UNTRACKED red, because an attribution matching "anything not OK" would swallow # a genuinely new failure (a native run as an unprivileged user returns EACCES, # which must not be absorbed into this). u01=$(val U01) case "$u01" in OK) bst_ok libevdev/uinput-present want U02 uinput-device-described "OK" want U03 uinput-create "OK" want U05 uinput-devnode-opens "OK" want U06 uinput-devnode-is-evdev "OK" want U07 uinput-name-round-trip "brintos-libevdev-probe" want U08 uinput-has-key-a "1" want U09 uinput-lacks-key-b "0" want U10 uinput-key-events-read "2" want U11 uinput-syn-events-read "2" ;; ENOENT|ENODEV) bst_notok libevdev/uinput-present "open(/dev/uinput) -> $u01; wasm32_defconfig sets CONFIG_INPUT=y, CONFIG_INPUT_EVDEV=y and CONFIG_VIRTIO_INPUT=y but not CONFIG_INPUT_UINPUT (which needs CONFIG_INPUT_MISC=y first), so no guest can create a synthetic input device -- brintos/linux#82 (https://brintos.io/brintos/linux/issues/82)" ;; "") bst_notok libevdev/uinput-present "no value on target (probe stopped before U01)" ;; *) bst_notok libevdev/uinput-present "open(/dev/uinput) -> $u01, which is neither OK nor the tracked ENOENT/ENODEV" ;; esac rm -f "$pout"