. "$BST_LIB" # BST-TIMEOUT: 300 # # ~110 cases, all of them file reads and string compares except one probe # execution, which bst_probe bounds at 60 s of its own. 300 leaves ample room # and stays useful as a hang detector. # # libftdi 1.5 on wasm32-hwjs glibc -- the FTDI USB serial-converter userland # library (meta-openembedded/meta-oe, recipes-support/libftdi; one meta-brintos # bbappend and one patch). # # --------------------------------------------------------------------------- # WHAT THIS PORT SHIPS, AND WHY THERE IS NO libftdi PACKAGE OF THAT NAME. # # libftdi-dev + libftdi-staticdev, and no main package. libftdi's own # src/CMakeLists.txt builds the library with add_library(... SHARED) and the # meta-oe recipe passes -DSTATICLIBS=off, so upstream offers exactly one shape: # a .so and nothing else. That link cannot be made here -- wasmld refuses a # side module that owns thread-local storage, and libusb's usbi_get_tid() # carries one thread_local that the merged .so inherits along with the archive. # The port therefore patches a SHAREDLIBS option into upstream's cmake # (defaulted ON, so nobody else's build changes) and turns it off here, with # STATICLIBS back on. The deliverable is one archive, one header, one .pc, one # -config script and the find_package(LibFTDI1) files. # # So there is no libftdi1.so on this image and no bare `libftdi` package, and # both are asserted below rather than merely noted -- exactly as 99-libusb1 # does for the same reason. # # --------------------------------------------------------------------------- # THERE IS NO USB BUS BEHIND THIS MACHINE, AND THE SUITE IS BUILT AROUND THAT # RATHER THAN AROUND PRETENDING OTHERWISE. # # The kernel has a USB core -- usb, hub, usbfs and ftdi_sio are registered and # /sys/bus lists usb-serial -- but there is no host controller, so # /sys/bus/usb/devices is empty and /dev/bus does not exist (measured under # BRINTOS-1338, re-asserted below as this suite's positive control). # # That reaches further into libftdi than into most consumers, because # ftdi_init's FIRST fallible act is libusb_init: # # if (libusb_init(&ftdi->usb_ctx) < 0) # ftdi_error_return(-3, "libusb_init() failed"); # # and on this image libusb1 is built with the udev PACKAGECONFIG cleared # (BRINTOS-1576: eudev's do_compile is refused by this toolchain and nothing # here provides udev). Without HAVE_LIBUDEV, find_usbfs_path() looks for # /dev/bus/usb, then for /dev/usbdev*.*, and returns NULL rather than assuming # the devtmpfs path -- so op_init() logs "could not find usbfs" and libusb_init # returns LIBUSB_ERROR_OTHER. Both arms were measured natively under # BRINTOS-1576 against exactly this guest's /dev and /sys state. # # The consequence is that ftdi_init returns -3 and ftdi_new returns NULL on # this machine. That is one step EARLIER than "an empty device list", which is # what a reader would expect, and the difference is the whole shape of the # probe below: ftdi_usb_find_all hands ftdi->usb_ctx straight to # libusb_get_device_list, and after a failed init that member is NULL, which # libusb 1.0.27 resolves to a default context nothing created. So the empty # enumeration is NOT reachable here and the probe declines to call it rather # than dereferencing null to prove the point. # # The refusal cases are asserted with the bus state as their control, so the # day this platform grows a host controller the two go red TOGETHER and say # why. A machine with USB is a HEALTHIER machine and must not turn this suite # red silently. # # --------------------------------------------------------------------------- # WHAT THE PROBE COVERS THAT NO FILE CHECK CAN. # # data/libftdi-probe is cross-linked against the very archive this package # installs and the header it installs, exactly as a downstream consumer would # reach them. Beyond the init arm above it drives the parts of the API that are # pure arithmetic over a caller-owned struct ftdi_context and need no device at # all: ftdi_set_interface's interface/index/endpoint table for INTERFACE_ANY # and A..D (the endpoint numbers are the chip's, so they are constants), its # two refusal paths, and the read/write chunk-size accessors including the # Linux-only clamp of the read chunk size to 16384 -- which is inside # `#ifdef __linux__`, and so is a statement about this target's preprocessor as # well as about libftdi. # # ftdi_set_baudrate's divisor arithmetic is the obvious further target and is # deliberately NOT covered: the function returns early unless ftdi->usb_dev is # a real libusb handle, and ftdi_convert_baudrate, which does the arithmetic, # is static. Reaching it would require fabricating a device handle, which would # crash in libusb_control_transfer. It is left uncovered rather than covered # dishonestly. bst_ok libftdi/suite-canary # first line: absorbs brintos/linux#96, which drops it tmp=${TMPDIR:-/tmp} nl=' ' hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } nofile() { _id=$1; shift; if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } nothas() { case "$3" in *"$2"*) bst_notok "$1" "found and must not be: $2" ;; *) bst_ok "$1" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not present: $2" ;; esac; } noline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_notok "$1" "present and must not be: $2" ;; *) bst_ok "$1" ;; esac; } inc=/usr/include/libftdi1/ftdi.h lib=/usr/lib/libftdi1.a pc=/usr/lib/pkgconfig/libftdi1.pc cfg=/usr/bin/libftdi1-config cm=/usr/lib/cmake/libftdi1 # --- the shipped files --------------------------------------------------------- hasfile libftdi/file-header "$inc" hasfile libftdi/file-archive "$lib" hasfile libftdi/file-pc "$pc" hasfile libftdi/file-config "$cfg" hasfile libftdi/file-cmake-config "$cm/LibFTDI1Config.cmake" hasfile libftdi/file-cmake-version "$cm/LibFTDI1ConfigVersion.cmake" hasfile libftdi/file-cmake-use "$cm/UseLibFTDI1.cmake" if [ -x "$cfg" ]; then bst_ok libftdi/config-executable else bst_notok libftdi/config-executable "not executable: $cfg"; fi # libftdipp1.pc ships even though the C++ binding is not built -- upstream's # top-level CMakeLists installs both .pc files unconditionally, guarded by # nothing. It is inert (nothing can consume it: there is no libftdipp1.a) and # it is asserted as PRESENT rather than quietly ignored, so that a future # upstream that fixes this is noticed here rather than silently. hasfile libftdi/file-pc-cpp-shipped-inert /usr/lib/pkgconfig/libftdipp1.pc # --- what must NOT be here ----------------------------------------------------- # # No shared object of any name: this is the port's central decision and the # thing a regression would undo first. for so in /usr/lib/libftdi1.so /usr/lib/libftdi1.so.2 /usr/lib/libftdi1.so.2.5.0 \ /lib/libftdi1.so /usr/lib/libftdipp1.so; do nofile "libftdi/no-so-${so##*/}" "$so" done nofile libftdi/no-cpp-archive /usr/lib/libftdipp1.a # PACKAGECONFIG is empty, so none of the optional halves is built. nofile libftdi/no-ftdi-eeprom /usr/bin/ftdi_eeprom nofile libftdi/no-eeprom-example /usr/share/libftdi/examples for b in simple bitbang bitbang2 find_all serial_test stream_test baud_test; do nofile "libftdi/no-example-$b" "/usr/bin/$b" done # The python binding: upstream defaults PYTHON_BINDINGS OFF and the recipe does # not turn it on, so no ftdi1.py / _ftdi1.so is installed anywhere. if [ -n "$(find /usr/lib -name 'ftdi1.py' -o -name '_ftdi1*' 2>/dev/null)" ]; then bst_notok libftdi/no-python-binding "a python binding is installed" else bst_ok libftdi/no-python-binding fi # --- the pkg-config file, line for line ---------------------------------------- pcbody=$(cat "$pc" 2>/dev/null) hasline libftdi/pc-prefix 'prefix=/usr' "$pcbody" hasline libftdi/pc-libdir 'libdir=/usr/lib' "$pcbody" hasline libftdi/pc-includedir 'includedir=/usr/include/libftdi1' "$pcbody" hasline libftdi/pc-name 'Name: libftdi1' "$pcbody" hasline libftdi/pc-version 'Version: 1.5' "$pcbody" hasline libftdi/pc-requires 'Requires: libusb-1.0' "$pcbody" hasline libftdi/pc-libs 'Libs: -L${libdir} -lftdi1' "$pcbody" hasline libftdi/pc-cflags 'Cflags: -I${includedir}' "$pcbody" # THE .pc's Requires IS THE HALF THAT MATTERS ON THIS DISTRO. libusb1 stages its # archive in /lib while its header is under /usr (BRINTOS-1576, asserted by # 99-libusb1), and libusb-1.0.pc carries `libdir=/lib` to make that workable. # A consumer that resolves libftdi through pkg-config therefore reaches libusb # only if this Requires line is intact. if command -v pkg-config >/dev/null 2>&1; then o=$(pkg-config --modversion libftdi1 2>&1); r=$? if [ "$r" -eq 0 ] && [ "$o" = "1.5" ]; then bst_ok libftdi/pkg-config-modversion else bst_notok libftdi/pkg-config-modversion "rc=$r out=$o"; fi o=$(pkg-config --libs libftdi1 2>&1); r=$? case "$o" in *-lftdi1*) bst_ok libftdi/pkg-config-libs ;; *) bst_notok libftdi/pkg-config-libs "rc=$r out=$o" ;; esac o=$(pkg-config --cflags libftdi1 2>&1); r=$? case "$o" in *-I/usr/include/libftdi1*) bst_ok libftdi/pkg-config-cflags ;; *) bst_notok libftdi/pkg-config-cflags "rc=$r out=$o" ;; esac else bst_skip libftdi/pkg-config-modversion "pkg-config not installed on this image" bst_skip libftdi/pkg-config-libs "pkg-config not installed on this image" bst_skip libftdi/pkg-config-cflags "pkg-config not installed on this image" fi # --- libftdi1-config, which is a shell script and therefore runnable here ------- o=$("$cfg" --version 2>&1); r=$? if [ "$r" -eq 0 ] && [ "$o" = "1.5" ]; then bst_ok libftdi/config-version else bst_notok libftdi/config-version "rc=$r out=$o"; fi o=$("$cfg" --libs 2>&1); r=$? case "$r:$o" in 0:*-lftdi1*) bst_ok libftdi/config-libs ;; *) bst_notok libftdi/config-libs "rc=$r out=$o" ;; esac o=$("$cfg" --prefix 2>&1); r=$? if [ "$r" -eq 0 ] && [ "$o" = "/usr" ]; then bst_ok libftdi/config-prefix else bst_notok libftdi/config-prefix "rc=$r out=$o"; fi # No argument is a usage error and exits non-zero -- the one refusal path this # script has, and the one a mangled configure_file would break. o=$("$cfg" 2>&1); r=$? case "$r:$o" in 0:*) bst_notok libftdi/config-usage-refuses "exited 0 with no argument: $o" ;; *Usage*) bst_ok libftdi/config-usage-refuses ;; *) bst_notok libftdi/config-usage-refuses "rc=$r out=$o" ;; esac # --- the cmake package files --------------------------------------------------- # # The recipe's do_install:append seds ${RECIPE_SYSROOT} out of # LibFTDI1Config.cmake. If that ever stops matching, a build-host path ships to # the target -- so both halves are asserted: the target paths are right AND no # build path survives. cmbody=$(cat "$cm/LibFTDI1Config.cmake" 2>&1) has libftdi/cmake-include-dir 'set ( LIBFTDI_INCLUDE_DIR "/usr/include/libftdi1" )' "$cmbody" has libftdi/cmake-library 'set ( LIBFTDI_LIBRARY "ftdi1" )' "$cmbody" has libftdi/cmake-library-dirs 'set ( LIBFTDI_LIBRARY_DIRS "/usr/lib" )' "$cmbody" # The static list names libusb by its REAL on-target path, /lib/libusb-1.0.a, # which is where poky's libusb1 puts it and not where a reader would guess. has libftdi/cmake-static-libs '/lib/libusb-1.0.a' "$cmbody" case "$cmbody" in *"$(printf 'recipe-sysroot')"*|*/tmp-glibc/*|*/home/*) bst_notok libftdi/cmake-no-build-path "a build-host path survived do_install:append" ;; *) bst_ok libftdi/cmake-no-build-path ;; esac vbody=$(cat "$cm/LibFTDI1ConfigVersion.cmake" 2>&1) has libftdi/cmake-version-1.5 '1.5' "$vbody" # --- the header ---------------------------------------------------------------- hdr=$(cat "$inc" 2>&1) has libftdi/hdr-guard '__libftdi_h__' "$hdr" # ftdi.h does NOT include libusb.h. MEASURED on the installed header, which is # byte-identical to upstream 1.5's src/ftdi.h in its include block: stdint.h, # sys/time.h, and windows.h under _WIN32. It names libusb's types as opaque # pointers -- struct libusb_context *, struct libusb_device_handle * -- and # leaves the include to the consumer, which is exactly what makes the .pc's # `Requires: libusb-1.0` above load-bearing rather than decorative: a consumer # that reaches libftdi without it compiles this header fine and then fails to # resolve LIBUSB_REQUEST_TYPE_VENDOR at line 163. # # Both halves are asserted, so a future upstream that adds the include is # noticed here rather than passing silently. has libftdi/hdr-libusb-opaque 'struct libusb_context *usb_ctx;' "$hdr" nothas libftdi/hdr-no-libusb-include 'include ' "$hdr" has libftdi/hdr-decl-init 'int ftdi_init(struct ftdi_context *ftdi);' "$hdr" has libftdi/hdr-decl-new 'struct ftdi_context *ftdi_new(void);' "$hdr" has libftdi/hdr-decl-findall 'int ftdi_usb_find_all(struct ftdi_context *ftdi, struct ftdi_device_list **devlist,' "$hdr" has libftdi/hdr-enum-iface 'INTERFACE_ANY = 0' "$hdr" has libftdi/hdr-struct-ctx 'struct ftdi_context' "$hdr" # --- the archive --------------------------------------------------------------- # # libftdi1.a is LLVM IR bitcode, not native objects, so its string table carries # identifiers and source paths but NOT the C string literals -- the same shape # 99-libusb1 records. Symbol names and TU paths are therefore what can be # asserted, and they are the stronger statement anyway. # # The octal `tr -c` class is the portable flatten this corpus uses; a flatten # that silently produced nothing would take every assertion below with it, which # is what the floor in sym-complete guards against. flat=$tmp/libftdi-flat.$$ ( tr -c '\40-\176\n' '\n' < "$lib" > "$flat" 2>/dev/null ) hasfile libftdi/archive-flattened "$flat" # Both translation units, by the debug path each one records. Two is the whole # library: ftdi.c and ftdi_stream.c. tuf=$tmp/libftdi-tu.$$ printf '%s\n' \ '/usr/src/debug/libftdi/1.5/src/ftdi.c' \ '/usr/src/debug/libftdi/1.5/src/ftdi_stream.c' \ > "$tuf" tufound=$(grep -oFf "$tuf" "$flat" 2>/dev/null | sort -u) hasline libftdi/tu-ftdi '/usr/src/debug/libftdi/1.5/src/ftdi.c' "$tufound" hasline libftdi/tu-ftdi-stream '/usr/src/debug/libftdi/1.5/src/ftdi_stream.c' "$tufound" # ftdi_i.h is NOT among them and cannot be: it is a private header both TUs # include, and the bitcode's string table carries one source path per object, # not a DWARF file table. MEASURED on the shipped archive -- the only two # /usr/src/debug/libftdi paths in it are the two above. What IS assertable # about that header is that it stays private, which is a packaging property # and a real regression signal: upstream installs only ftdi.h, and a # do_install that started shipping ftdi_i.h would leak libftdi's internals # into the -dev package. nofile libftdi/no-private-hdr /usr/include/libftdi1/ftdi_i.h # COMPLETENESS: every function the INSTALLED header declares must be in the # archive. The extraction is over the header ON THE IMAGE, so a header/archive # skew fails this rather than passing on a stale list. 1.5 declares 70. namef=$tmp/libftdi-names.$$ grep -oE '\bftdi_[A-Za-z0-9_]*[[:space:]]*\(' "$inc" 2>/dev/null \ | sed -E 's/[[:space:]]*\(.*$//' \ | sort -u > "$namef" # -w is load-bearing: without it a declaration for a name that is a strict # PREFIX of a real symbol passes the lookup vacuously (measured on target for # 99-libusb1, and the same grep is used here). found=$(grep -owFf "$namef" "$flat" 2>/dev/null | sort -u) ndecl=$(grep -c . "$namef"); nfound=$(printf '%s' "$found" | grep -c .) if [ "$ndecl" -ge 60 ] && [ "$nfound" -eq "$ndecl" ]; then bst_ok libftdi/sym-complete else bst_notok libftdi/sym-complete "declared=$ndecl found=$nfound"; fi # A named sample as well, so a failure names the area that broke rather than # only reporting a count. for s in ftdi_init ftdi_deinit ftdi_new ftdi_free ftdi_set_interface \ ftdi_usb_find_all ftdi_list_free ftdi_usb_open ftdi_usb_open_desc \ ftdi_usb_close ftdi_set_baudrate ftdi_set_line_property \ ftdi_read_data ftdi_write_data ftdi_read_data_set_chunksize \ ftdi_write_data_set_chunksize ftdi_set_bitmode ftdi_read_pins \ ftdi_set_latency_timer ftdi_poll_modem_status ftdi_setflowctrl \ ftdi_read_eeprom ftdi_write_eeprom ftdi_eeprom_build \ ftdi_eeprom_decode ftdi_eeprom_initdefaults ftdi_get_library_version \ ftdi_get_error_string ftdi_readstream ftdi_transfer_data_done; do hasline "libftdi/sym-$s" "$s" "$found" done # libusb is NOT merged into this archive: libftdi1.a carries libftdi's own two # TUs and nothing else, and a consumer links libusb itself (which is what the # .pc's Requires and the cmake static list both say). A libusb symbol appearing # here would mean the archive was built wrong. lu=$(grep -owF 'libusb_init' "$flat" 2>/dev/null | sort -u) if [ -z "$lu" ]; then bst_ok libftdi/archive-excludes-libusb else bst_notok libftdi/archive-excludes-libusb "libusb_init is defined in $lib"; fi # --- the bus, which is this suite's positive control --------------------------- devtmpfs=absent; [ -d /dev/bus/usb ] && devtmpfs=present procusb=absent; [ -d /proc/bus/usb ] && procusb=present usbcore=absent; [ -d /sys/bus/usb ] && usbcore=present ndev=0 if [ -d /sys/bus/usb/devices ]; then for d in /sys/bus/usb/devices/*; do [ -e "$d" ] && ndev=$((ndev + 1)); done fi busstate="usbfs:/dev/bus/usb=$devtmpfs /proc/bus/usb=$procusb sysfs:/sys/bus/usb=$usbcore devices=$ndev" # The state itself is never asserted -- a machine that grows a host controller is # a healthier machine. It is carried in the detail of every case below that # depends on it, so a red always names the bus state that produced it. # --- the probe ----------------------------------------------------------------- out=$tmp/libftdi-probe.$$ probe=$tmp/bst-libftdi-probe.$$ if cp "$BST_DATA/libftdi-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then bst_ok libftdi/probe-staged bst_probe libftdi/probe-runs "$out" 60 "$probe" prc=$? else bst_notok libftdi/probe-staged "cannot stage $BST_DATA/libftdi-probe" bst_notok libftdi/probe-runs "probe was never staged" prc=127 fi if [ "$prc" -eq 0 ] || [ -s "$out" ]; then pbody=$(cat "$out" 2>/dev/null) # Arm 1: everything that needs no libusb context. These must be green on any # machine, with or without a bus. for c in version-1.5 \ iface-any iface-a iface-b iface-c iface-d \ iface-bogus-refused iface-null-context-refused \ error-string-of-null-context-is-empty \ read-chunksize-set-4096 read-chunksize-get-4096 \ read-chunksize-clamped-to-16384 \ write-chunksize-roundtrips-unclamped \ read-chunksize-null-context-refused; do case "$nl$pbody$nl" in *"${nl}PROBE-OK $c$nl"*) bst_ok "libftdi/probe-$c" ;; *"${nl}PROBE-FAIL $c "*) bst_notok "libftdi/probe-$c" \ "$(sed -n "s/^PROBE-FAIL $c //p" "$out" | head -n 1)" ;; *) bst_notok "libftdi/probe-$c" "the probe emitted no line for this case" ;; esac done initrc=$(sed -n 's/^PROBE-INFO ftdi_init_rc=//p' "$out" | head -n 1) initerr=$(sed -n 's/^PROBE-INFO ftdi_init_err=//p' "$out" | head -n 1) newres=$(sed -n 's/^PROBE-INFO ftdi_new=//p' "$out" | head -n 1) enum=$(sed -n 's/^PROBE-INFO enumeration=//p' "$out" | head -n 1) if [ "$devtmpfs" = absent ] && [ "$procusb" = absent ]; then # Arm 2, no-bus arm. libusb_init refuses, so ftdi_init returns -3 with its # own message and ftdi_new frees and returns NULL. Both are libftdi doing # exactly what its source says for the answer libusb gave it. if [ "$initrc" = "-3" ]; then bst_ok libftdi/probe-init-refuses-no-usbfs else bst_notok libftdi/probe-init-refuses-no-usbfs "ftdi_init_rc=$initrc -- $busstate"; fi if [ "$initerr" = "libusb_init() failed" ]; then bst_ok libftdi/probe-init-error-string else bst_notok libftdi/probe-init-error-string "ftdi_init_err=$initerr"; fi if [ "$newres" = "NULL" ]; then bst_ok libftdi/probe-new-returns-null else bst_notok libftdi/probe-new-returns-null "ftdi_new=$newres"; fi if [ "$enum" = "not-attempted-no-libusb-context" ]; then bst_ok libftdi/probe-enumeration-declined else bst_notok libftdi/probe-enumeration-declined "enumeration=$enum" fi bst_skip libftdi/probe-enumerate-against-bus \ "no USB device tree, so ftdi_init refuses at libusb_init and enumeration is unreachable -- $busstate" else # A machine that grew a host controller. The refusal cases above would be # wrong there, so they are skipped and the enumeration is asserted instead. bst_skip libftdi/probe-init-refuses-no-usbfs \ "a usbfs tree exists, so libusb_init is expected to succeed -- $busstate" bst_skip libftdi/probe-init-error-string "see probe-init-refuses-no-usbfs" bst_skip libftdi/probe-new-returns-null "see probe-init-refuses-no-usbfs" bst_skip libftdi/probe-enumeration-declined "see probe-init-refuses-no-usbfs" if [ "$initrc" = "0" ] && [ "$newres" = "context" ] && [ "$enum" = "attempted" ]; then bst_ok libftdi/probe-enumerate-against-bus else bst_notok libftdi/probe-enumerate-against-bus \ "init_rc=$initrc new=$newres enumeration=$enum -- $busstate" fi fi else for c in version-1.5 iface-any iface-a iface-b iface-c iface-d \ iface-bogus-refused iface-null-context-refused \ error-string-of-null-context-is-empty \ read-chunksize-set-4096 read-chunksize-get-4096 \ read-chunksize-clamped-to-16384 \ write-chunksize-roundtrips-unclamped \ read-chunksize-null-context-refused \ init-refuses-no-usbfs init-error-string new-returns-null \ enumeration-declined enumerate-against-bus; do bst_notok "libftdi/probe-$c" "the probe produced no transcript (rc=$prc)" done fi rm -f "$flat" "$tuf" "$namef" "$out" "$out.err" "$probe"