. "$BST_LIB" # libgpiod -- the C library and its six tools on a machine with NO GPIO # controller. Every expectation below was MEASURED on this image (2026-08-26, # build 5c9234aa170f) before it was written down; none was read off the source. # # WHAT THIS MACHINE CAN AND CANNOT TEST. There is no GPIO hardware here and there # is no software substitute: no /dev/gpiochip*, no /sys/bus/gpio, no # /sys/class/gpio, and nothing named gpio in /proc/devices. So the half of # libgpiod that reads and drives lines cannot run, and this suite does not pretend # otherwise -- it asserts the half that CAN: the chip-and-line LOOKUP path, the # argument parser, the diagnostics and the documented exit codes. That is every # line of the tools up to the point where a descriptor would be opened, and it is # reached on every invocation. # # THE HEADLINE FINDING IS A SILENCE, AND IT IS EASY TO GET BACKWARDS. On a machine # with no chips, `gpiodetect` and `gpioinfo` print NOTHING and exit 0 -- they do # not report "no chips found" and they do not fail. A suite written from the # ticket's framing ("assert them on an empty system") would naturally look for a # diagnostic or a non-zero status, and would redden a tool that is behaving # exactly as upstream intends: an empty enumeration is an empty list, not an # error. Both halves are asserted here -- the status AND the emptiness -- because # asserting only the status would pass on a build that printed a spurious line, # and asserting only the output would pass on one that failed silently. # # EVERY HARDWARE-DEPENDENT EXPECTATION IS PROBED AT RUNTIME, NEVER HARDCODED. If # this machine ever gains a gpiochip, the enumeration cases flip to asserting a # non-empty listing instead of an empty one, with no edit to this file, and the # suite does not redden for having been right yesterday. # # GPIO-SIM IS PROBED AND SKIPPED, NOT DROPPED AND NOT REDDENED. Upstream's own # test suite is built on the gpio-sim kernel module, which this kernel cannot # provide -- see the gpio-sim section at the end, which reports what it actually # found rather than assuming the answer. Those cases are `skip` with the reason # attached, so they are visible in the report instead of being absent from it. # # THE C++ BINDINGS ARE ASSERTED ABSENT, and that is a real requirement rather than # bookkeeping. meta-oe enables them by default; this arm is exceptions-off and # libgpiod's C++ half reports every error by throwing, so shipping it would put a # library on the image whose only failure path aborts. libgpiod_%.bbappend turns # the opt-in off, and the three cases at the end are what would notice if a future # recipe change turned it back on -- the build would still succeed, so nothing # else would. bst_ok libgpiod/suite-canary # first line: absorbs brintos/linux#96, which drops it require gpiodetect libgpiod/present # --- helpers ---------------------------------------------------------------- # check_out asserts rc=0 AND a needle; most of what this suite asserts is a # DIAGNOSTIC on a non-zero exit, so it needs the pair asserted together. Running # the command once and testing both halves of the result is also what stops a # tool that emits the right message on the wrong status from passing. gp_expect() { # gp_expect _id=$1; _want=$2; _needle=$3; shift 3 _out=$("$@" 2>&1); _got=$? if [ "$_got" != "$_want" ]; then bst_notok "$_id" "rc=$_got want=$_want: $(_bst_1line "$_out")" elif [ -n "$_needle" ] && ! printf '%s\n' "$_out" | grep -qF "$_needle"; then bst_notok "$_id" "rc=$_got as wanted but no '$_needle': $(_bst_1line "$_out")" else bst_ok "$_id" fi } gp_silent() { # gp_silent -- asserts NO output at all _id=$1; _want=$2; shift 2 _out=$("$@" 2>&1); _got=$? if [ "$_got" != "$_want" ]; then bst_notok "$_id" "rc=$_got want=$_want: $(_bst_1line "$_out")" elif [ -n "$_out" ]; then bst_notok "$_id" "expected silence, got: $(_bst_1line "$_out")" else bst_ok "$_id" fi } TOOLS="gpiodetect gpioinfo gpioget gpioset gpiomon gpionotify" # --- 1. the port shipped what it claims to ship ------------------------------ for t in $TOOLS; do if command -v "$t" >/dev/null 2>&1; then bst_ok "libgpiod/tool-$t" else bst_notok "libgpiod/tool-$t" "$t is not on PATH -- libgpiod-tools is incomplete" fi done # --- 2. identity ------------------------------------------------------------- # The needle is the FAMILY string, not the version number. Pinning v2.1.3 here # would turn a poky/meta-oe version bump into a red that says "libgpiod is # broken" when it means "libgpiod moved" -- the version itself is reported on the # diag line below, where a reader can see it drift without the suite lying. for t in $TOOLS; do gp_expect "libgpiod/version-$t" 0 "(libgpiod) v" "$t" --version done GP_VER=$(gpiodetect --version 2>&1 | sed -n '1s/.*(libgpiod) //p') gp_expect libgpiod/version-license 0 "GPL-2.0-or-later" gpiodetect --version # --- 3. what the kernel actually offers, measured now ------------------------- HAVE_CHIP= for d in /dev/gpiochip*; do [ -e "$d" ] && HAVE_CHIP=1 done [ -d /sys/bus/gpio/devices ] && SYSBUS=yes || SYSBUS=no [ -d /sys/class/gpio ] && SYSCLASS=yes || SYSCLASS=no grep -qi gpio /proc/devices 2>/dev/null && PROCDEV=yes || PROCDEV=no echo "#diag: libgpiod probe: version=${GP_VER:-unknown} chardev=${HAVE_CHIP:+yes}${HAVE_CHIP:-no} /sys/bus/gpio=$SYSBUS /sys/class/gpio=$SYSCLASS /proc/devices=$PROCDEV" # --- 4. enumeration on an empty system -- the point of the ticket ------------- # Probed, not hardcoded: with a chip present these assert the other branch. if [ -n "$HAVE_CHIP" ]; then gp_expect libgpiod/detect-empty 0 "gpiochip" gpiodetect gp_expect libgpiod/info-empty 0 "line" gpioinfo bst_skip libgpiod/detect-silent "a gpiochip is present, so an empty enumeration is not the case under test" bst_skip libgpiod/info-silent "a gpiochip is present, so an empty enumeration is not the case under test" else # An empty enumeration is an empty list and a SUCCESS. Both halves asserted: # rc alone passes on a spurious line, output alone passes on a silent failure. gp_silent libgpiod/detect-silent 0 gpiodetect gp_silent libgpiod/info-silent 0 gpioinfo bst_skip libgpiod/detect-empty "no gpiochip on this machine -- nothing to enumerate; libgpiod/detect-silent is the case that applies" bst_skip libgpiod/info-empty "no gpiochip on this machine -- nothing to enumerate; libgpiod/info-silent is the case that applies" fi # gpiodetect accepts explicit chip arguments; naming one that cannot exist is the # lookup path with the enumeration taken out of it, and it answers the same way # whether or not this machine has hardware. gp_expect libgpiod/detect-named-absent 1 "cannot find GPIO chip character device 'nosuchchip'" gpiodetect nosuchchip # --- 5. name lookup: gpiofind in v1, gpioinfo in v2 ------------------- # THE TICKET ASKS FOR gpiofind AND THERE IS NO gpiofind HERE. libgpiod v2 removed # the separate finder and folded the lookup into `gpioinfo `; meta-oe # carries both 1.6.5 and 2.1.3 and nothing pins a version, so the layer takes the # highest. Rather than assert a tool's absence -- which would redden the day # somebody legitimately pins 1.6.5 -- this probes for the finder and asserts # whichever one is actually here. Either way the ticket's requirement is covered: # looking a line up by name on a system that has no lines. if command -v gpiofind >/dev/null 2>&1; then gp_expect libgpiod/find-missing 1 "" gpiofind NOSUCHLINE bst_skip libgpiod/info-by-name "gpiofind is present (libgpiod 1.x), so the v2 gpioinfo name lookup is not the path in use" else gp_expect libgpiod/info-by-name 1 "cannot find line 'NOSUCHLINE'" gpioinfo NOSUCHLINE bst_skip libgpiod/find-missing "gpiofind was removed in libgpiod v2 (${GP_VER:-v2.x}); the lookup is gpioinfo , asserted as libgpiod/info-by-name" fi # --- 6. chip lookup fails identically across every tool ---------------------- # One diagnostic, one exit code, five entry points. Asserting them together is # what would catch a tool that grew its own divergent error path. gp_expect libgpiod/chip-absent-info 1 "cannot find GPIO chip character device 'nosuchchip'" gpioinfo --chip nosuchchip gp_expect libgpiod/chip-absent-get 1 "cannot find GPIO chip character device 'nosuchchip'" gpioget -c nosuchchip 0 gp_expect libgpiod/chip-absent-set 1 "cannot find GPIO chip character device 'nosuchchip'" gpioset -c nosuchchip 0=1 gp_expect libgpiod/chip-absent-mon 1 "cannot find GPIO chip character device 'nosuchchip'" gpiomon -c nosuchchip 0 gp_expect libgpiod/chip-absent-notify 1 "cannot find GPIO chip character device 'nosuchchip'" gpionotify -c nosuchchip 0 # A chip may be named by number, by name, or by path -- upstream's own help says # so. The path form resolves through the same lookup and reports the string it was # given, which is the detail a reader would otherwise assume differs. if [ -n "$HAVE_CHIP" ]; then bst_skip libgpiod/chip-absent-path "a gpiochip is present, so /dev/gpiochip0 is not an absent-chip case" else gp_expect libgpiod/chip-absent-path 1 "cannot find GPIO chip character device '/dev/gpiochip0'" gpioget -c /dev/gpiochip0 0 fi # --- 7. argument validation --------------------------------------------------- # gpioset's wording differs from the other three -- "line value" rather than # "line" -- because it takes assignments rather than names. That asymmetry is # asserted deliberately: it is exactly the kind of detail a reader assumes is # uniform, and a suite that asserted the shorter needle everywhere would pass # without ever having checked it. gp_expect libgpiod/noargs-get 1 "at least one GPIO line must be specified" gpioget gp_expect libgpiod/noargs-mon 1 "at least one GPIO line must be specified" gpiomon gp_expect libgpiod/noargs-notify 1 "at least one GPIO line must be specified" gpionotify gp_expect libgpiod/noargs-set 1 "at least one GPIO line value must be specified" gpioset # gpiodetect and gpioinfo take no mandatory argument at all, which is why they are # the silent-success cases in section 4 rather than appearing here. for t in $TOOLS; do gp_expect "libgpiod/badopt-$t" 1 "unrecognized option '--bogus'" "$t" --bogus gp_expect "libgpiod/badopt-hint-$t" 1 "try $t --help" "$t" --bogus done # --- 8. help and exit codes --------------------------------------------------- for t in $TOOLS; do gp_expect "libgpiod/help-$t" 0 "Usage: $t" "$t" --help done gp_expect libgpiod/help-detect-text 0 "List GPIO chips, print their labels and number of GPIO lines." gpiodetect --help # --- 9. the library the tools were built from -------------------------------- # This arm links statically, so libgpiod.a IS the shipped library and there is no # shared object by design -- a .so appearing here would mean the distro-wide # --disable-shared stopped reaching this recipe. if [ -s /usr/lib/libgpiod.a ]; then bst_ok libgpiod/staticlib else bst_notok libgpiod/staticlib "/usr/lib/libgpiod.a is missing or empty -- libgpiod-staticdev did not ship the library" fi if [ -s /usr/include/gpiod.h ]; then bst_ok libgpiod/header else bst_notok libgpiod/header "/usr/include/gpiod.h is missing -- libgpiod-dev did not ship the C header" fi bst_lib_shape libgpiod/no-shared "/usr/lib/libgpiod.so*" # --- 10. the C++ bindings are off, and stay off ------------------------------- # meta-oe's PACKAGECONFIG defaults to "cxx", and this arm is exceptions-off while # libgpiod's C++ half reports every error by throwing. If a future recipe change # re-enabled the opt-in the build would still succeed and the image would grow a # library whose only error path aborts -- these three cases are the only thing # that would say so. if [ -e /usr/include/gpiod.hpp ]; then bst_notok libgpiod/no-cxx-header "/usr/include/gpiod.hpp is present -- the C++ bindings opt-in is back on" else bst_ok libgpiod/no-cxx-header fi _cxx=$(echo /usr/lib/libgpiodcxx*) if [ "$_cxx" = '/usr/lib/libgpiodcxx*' ]; then bst_ok libgpiod/no-cxx-lib else bst_notok libgpiod/no-cxx-lib "a C++ binding library was shipped ($_cxx) -- exceptions are off on this arm" fi if [ -e /usr/lib/pkgconfig/libgpiodcxx.pc ]; then bst_notok libgpiod/no-cxx-pc "libgpiodcxx.pc is present -- a downstream recipe could pick up the C++ bindings" else bst_ok libgpiod/no-cxx-pc fi # --- 11. gpio-sim: probed, reported, and skipped on confirmed absence --------- # Upstream's own test suite drives a virtual chip provided by the gpio-sim kernel # module through configfs. The ticket asks that this be PROBED rather than assumed # -- so the four cases below report what was actually found. They are `skip`, not # `not ok`: an absent module is a property of this kernel, not a defect in the # port, and a red here would be a standing false alarm. They are also not omitted: # a skip with its reason is visible in the report, and an omission is not. # # Note this suite would not have run upstream's tests even with the module here -- # they are ptest content, DISTRO_FEATURES is "" so PTEST_ENABLED is 0, and the # bbappend's PACKAGECONFIG = "" leaves --disable-tests set. The probe is about # whether the CAPABILITY exists, which is the question the ticket asked. SIM_MOD=no; [ -d /sys/module/gpio_sim ] && SIM_MOD=yes SIM_CFG=no; [ -d /sys/kernel/config/gpio-sim ] && SIM_CFG=yes SIM_CONFIGFS=no; grep -qw configfs /proc/filesystems 2>/dev/null && SIM_CONFIGFS=yes SIM_MODPROBE=no; command -v modprobe >/dev/null 2>&1 && SIM_MODPROBE=yes SIM_MODULES=no; [ -r /proc/modules ] && SIM_MODULES=yes SIM_NMODULES=$(wc -l < /proc/modules 2>/dev/null || echo n/a) echo "#diag: libgpiod gpio-sim probe: module=$SIM_MOD configfs-fs=$SIM_CONFIGFS configfs-dir=$SIM_CFG modprobe=$SIM_MODPROBE /proc/modules=$SIM_MODULES loaded=$SIM_NMODULES" SIM_WHY="gpio-sim is not available on this kernel (module=$SIM_MOD, configfs=$SIM_CONFIGFS, gpio-sim configfs dir=$SIM_CFG, loaded modules=$SIM_NMODULES) -- upstream's chip/line tests need a virtual controller and there is none" if [ "$SIM_MOD" = yes ] && [ "$SIM_CFG" = yes ]; then # The capability appeared. Assert it for real rather than skipping -- and say # so loudly, because it would mean this kernel gained module loading. gp_expect libgpiod/sim-detect 0 "gpiochip" gpiodetect bst_ok libgpiod/sim-available bst_skip libgpiod/sim-line-read "gpio-sim is present; driving a simulated line is upstream ptest content and is not built here (PACKAGECONFIG = \"\", --disable-tests)" bst_skip libgpiod/sim-line-write "gpio-sim is present; driving a simulated line is upstream ptest content and is not built here (PACKAGECONFIG = \"\", --disable-tests)" else bst_skip libgpiod/sim-available "$SIM_WHY" bst_skip libgpiod/sim-detect "$SIM_WHY" bst_skip libgpiod/sim-line-read "$SIM_WHY" bst_skip libgpiod/sim-line-write "$SIM_WHY" fi