# BST-TIMEOUT: 600 # five awk passes over 9.1 MB of tables, the largest 5.5 MB; # # measured 61s on target (==BST-SUITE, 2026-08-20), so 600 # # is ~10x headroom -- sized for the host's worst day . "$BST_LIB" # hwdata is data only -- the numeric-ID-to-name tables that every # device-inspection tool on a Linux system reads. There is no tool to `require`: # what ships is five tables plus a modprobe blacklist and a pkg-config file, and # not one byte of the package is code. # # THIS SUITE PARSES, IT DOES NOT STAT. Asserting that five files exist is # compatible with every one of them being a half-fetched prefix, which is # precisely the failure an allarch data recipe produces when it produces one. # So each table is read with its OWN grammar, its records are counted, its # version stamp is checked against the SRCREV that froze it, and a handful of # real IDs are resolved end to end -- with a negative control beside each lookup, # because a filter that had silently stopped filtering would answer the first # record to every query and "Intel Corporation" would then prove nothing. # # The counts are EXACT rather than floors. They are frozen by the recipe's # SRCREV (44635308, hwdata 0.381), the rootfs is read-only at browser runtime so # nothing here can repair a short file, and an exact count is the only form that # catches a table truncated in its MIDDLE -- a floor and a `test -s` both pass on # one of those. A recipe bump is expected to move them, deliberately and # visibly; that is the point of pinning them. HW=/usr/share/hwdata PC=/usr/share/pkgconfig/hwdata.pc MPD=/usr/lib/modprobe.d/dist-blacklist.conf # --- section 1: the payload arrived, whole ---------------------------------- # `wc -c "$f"` and not `wc -c < "$f"`: a top-level stdin redirect from the # virtiofs root makes dup2(2) return the kernel's restart-park sentinel as 513, # and ash treats that as fatal and EXITS -- taking every case below this line # with it while the run still reads as a clean pass (brintos/linux#73, and the # packager README's table of the two shapes). size() { LC_ALL=C wc -c "$1" 2>/dev/null | LC_ALL=C awk '{print $1; exit}'; } check hwdata/datadir test -d "$HW" check_eq hwdata/size-pci-ids 1444058 "$(size "$HW/pci.ids")" check_eq hwdata/size-usb-ids 728224 "$(size "$HW/usb.ids")" check_eq hwdata/size-oui-txt 5510620 "$(size "$HW/oui.txt")" check_eq hwdata/size-iab-txt 1779268 "$(size "$HW/iab.txt")" check_eq hwdata/size-pnp-ids 63075 "$(size "$HW/pnp.ids")" # The one file the package installs outside ${datadir}. It is why the recipe # passes --libdir at all, and it is the half of FILES:${PN} that a datadir-only # packaging change would silently drop. check hwdata/modprobe-d test -f "$MPD" check_eq hwdata/size-blacklist 884 "$(size "$MPD")" # Nothing else ships a /usr/share/hwdata, so the directory holds exactly the # five tables and nothing has been packaged into it by accident. check_eq hwdata/datadir-count 5 \ "$(LC_ALL=C ls -1 "$HW" 2>/dev/null | LC_ALL=C awk 'END{print NR+0}')" # Everything below needs a parser. If awk is somehow absent the lookups SKIP # with the reason rather than reporting a data fault they never measured. if ! command -v awk >/dev/null 2>&1; then bst_skip hwdata/parse "no awk on this image; nothing here can read the tables" exit 0 fi # Pull one tagged key out of a captured tagged block. The tables are read ONCE # each into these blocks -- oui.txt alone is 5.5 MB and a pass per assertion # would be twenty passes over it. val() { printf '%s\n' "$2" | LC_ALL=C awk -v k="$1" \ 'index($0,k"=")==1{print substr($0,length(k)+2); exit}'; } # --- section 2: pci.ids ------------------------------------------------------ # Grammar: `xxxx Vendor` at column 0, `\txxxx Device` under it, `\t\txxxx yyyy # Subsystem` under that, and a separate class block introduced by `C xx Name`. # The parse is STATE-AWARE: device lines are counted only while inside a vendor # section, so the class block's own two-hex subclass lines cannot be mistaken for # devices. `bad` counts every non-comment, non-blank line the grammar does not # account for -- it is the assertion that this is a complete parse of the file # and not a lucky pattern match over part of it. PCI=$(LC_ALL=C awk ' /^#/ { next } /^[ \t]*$/ { next } substr($0,1,1)=="\t" { if (substr($0,2,1)=="\t") { if (inv && match(substr($0,3), /^[0-9a-f][0-9a-f][0-9a-f][0-9a-f] [0-9a-f][0-9a-f][0-9a-f][0-9a-f] /)) sb++ next } if (inv && match(substr($0,2), /^[0-9a-f][0-9a-f][0-9a-f][0-9a-f] /)) { dev++ id = substr($0,2,4) if (curv=="8086" && id=="1237") d1 = substr($0,8) if (curv=="1af4" && id=="1000") d2 = substr($0,8) if (curv=="8086" && id=="ffff") dn = substr($0,8) } next } match($0, /^[0-9a-f][0-9a-f][0-9a-f][0-9a-f] /) { ven++; inv=1; curv = substr($0,1,4) if (curv=="8086") v1 = substr($0,7) if (curv=="10de") v2 = substr($0,7) if (curv=="zzzz") vn = substr($0,7) next } match($0, /^C [0-9a-f][0-9a-f] /) { cls++; inv=0; next } { bad++ } /^#[ \t]*Version:/ { } END { printf "ven=%d\ndev=%d\nsub=%d\ncls=%d\nbad=%d\n", ven+0, dev+0, sb+0, cls+0, bad+0 printf "v8086=%s\nv10de=%s\nvabsent=%s\n", v1, v2, vn printf "d8086_1237=%s\nd1af4_1000=%s\ndabsent=%s\n", d1, d2, dn }' "$HW/pci.ids" 2>&1) check_eq pci-ids/vendors 2378 "$(val ven "$PCI")" check_eq pci-ids/devices 18685 "$(val dev "$PCI")" check_eq pci-ids/subsystems 16183 "$(val sub "$PCI")" check_eq pci-ids/classes 22 "$(val cls "$PCI")" # Zero unaccounted lines: every byte of the file fell into one of the four # productions above. This is what makes the counts a measurement of the table # rather than of the regex. check_eq pci-ids/grammar-complete 0 "$(val bad "$PCI")" # The lookups the ticket asks for -- a vendor, and one of that vendor's devices. check_eq pci-ids/vendor-8086 "Intel Corporation" "$(val v8086 "$PCI")" check_eq pci-ids/vendor-10de "NVIDIA Corporation" "$(val v10de "$PCI")" check_eq pci-ids/device-8086-1237 "440FX - 82441FX PMC [Natoma]" "$(val d8086_1237 "$PCI")" # A device under a DIFFERENT vendor, so the device lookup is shown to be scoped # by the vendor section it sits in and not to be a file-wide search for "1000". check_eq pci-ids/device-1af4-1000 "Virtio network device" "$(val d1af4_1000 "$PCI")" # The negative controls for both levels. check_eq pci-ids/vendor-absent "" "$(val vabsent "$PCI")" check_eq pci-ids/device-absent "" "$(val dabsent "$PCI")" # The version stamp is the PCI ID Project's own, frozen by the SRCREV. It is NOT # the recipe's PV -- see section 6. check_out pci-ids/version "Version: 2024.03.31" head -6 "$HW/pci.ids" check_out pci-ids/date "Date: 2024-03-31" head -6 "$HW/pci.ids" # --- section 3: usb.ids ------------------------------------------------------ # Same two-level vendor/device grammar, but the file continues past the vendor # table into ten prefixed section blocks (C, AT, HID, R, BIAS, PHY, HUT, L, VT, # HCC) whose entries are also hex-and-two-spaces. `inv` is cleared by any # unindented line that is not a vendor, which is what keeps a HID usage or a # language code out of the device count. USB=$(LC_ALL=C awk ' /^#/ { next } /^[ \t]*$/ { next } substr($0,1,1)=="\t" { if (substr($0,2,1)=="\t") { next } if (inv && match(substr($0,2), /^[0-9a-f][0-9a-f][0-9a-f][0-9a-f] /)) { dev++ id = substr($0,2,4) if (curv=="1d6b" && id=="0002") d1 = substr($0,8) if (curv=="046d" && id=="c31c") d2 = substr($0,8) if (curv=="1d6b" && id=="ffff") dn = substr($0,8) } next } match($0, /^[0-9a-f][0-9a-f][0-9a-f][0-9a-f] /) { ven++; inv=1; curv = substr($0,1,4) if (curv=="1d6b") v1 = substr($0,7) if (curv=="046d") v2 = substr($0,7) next } { inv=0; sect++ } END { printf "ven=%d\ndev=%d\nsect=%d\n", ven+0, dev+0, sect+0 printf "v1d6b=%s\nv046d=%s\n", v1, v2 printf "d1d6b_0002=%s\nd046d_c31c=%s\ndabsent=%s\n", d1, d2, dn }' "$HW/usb.ids" 2>&1) check_eq usb-ids/vendors 3423 "$(val ven "$USB")" check_eq usb-ids/devices 20464 "$(val dev "$USB")" # The ten section headers the file carries after the vendor table. Pinned so a # truncation that lopped the tail off would not read as a clean vendor table. check_eq usb-ids/sections 306 "$(val sect "$USB")" check_eq usb-ids/vendor-1d6b "Linux Foundation" "$(val v1d6b "$USB")" check_eq usb-ids/vendor-046d "Logitech, Inc." "$(val v046d "$USB")" check_eq usb-ids/device-1d6b-0002 "2.0 root hub" "$(val d1d6b_0002 "$USB")" check_eq usb-ids/device-046d-c31c "Keyboard K120" "$(val d046d_c31c "$USB")" check_eq usb-ids/device-absent "" "$(val dabsent "$USB")" check_out usb-ids/version "Version: 2024.03.18" head -14 "$HW/usb.ids" # THE OTHER usb.ids ON THIS IMAGE. usbids (meta-oe recipes-support/usbids, the # same porting round) installs a bare /usr/share/usb.ids -- a DIFFERENT and newer # table at a different path. The two packages' file lists are disjoint, which is # the whole of why they can coexist, and this pair of cases is where that is # asserted rather than assumed. If usbids is not on this image the second case # skips; what must never happen is the two ending up at one path. check hwdata/usb-ids-own-path test -f "$HW/usb.ids" if [ -f /usr/share/usb.ids ]; then a=$(size "$HW/usb.ids"); b=$(size /usr/share/usb.ids) if [ "$a" != "$b" ]; then bst_ok hwdata/usb-ids-distinct-from-usbids else bst_notok hwdata/usb-ids-distinct-from-usbids \ "/usr/share/usb.ids and $HW/usb.ids are the same size ($a) -- one may have overwritten the other"; fi else bst_skip hwdata/usb-ids-distinct-from-usbids \ "usbids is not on this image; hwdata owns the only usb.ids" fi # --- section 4: oui.txt and iab.txt ----------------------------------------- # The IEEE registries. Grammar: a record is a `XX-XX-XX (hex) Organization` # line followed by a `XXXXXX (base 16) Organization` line and an indented # address block. Counting BOTH forms and asserting they agree is the # cross-representation check -- a file cut mid-record would leave the two counts # one apart, which neither count alone would show. OUI=$(LC_ALL=C awk ' match($0, /^[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][ \t]+\(hex\)/) { n++; pre = substr($0,1,8); org = $0; sub(/^.*\(hex\)[ \t]+/, "", org) if (pre=="00-50-56") a = org if (pre=="00-1B-21") b = org if (pre=="08-00-27") c = org if (pre=="ZZ-ZZ-ZZ") z = org next } match($0, /^[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][ \t]+\(base 16\)/) { m++; next } END { printf "hex=%d\nbase16=%d\nvmware=%s\nintel=%s\npcs=%s\nabsent=%s\n", n+0, m+0, a, b, c, z } ' "$HW/oui.txt" 2>&1) check_eq oui/records 35297 "$(val hex "$OUI")" check_eq oui/hex-base16-agree "$(val hex "$OUI")" "$(val base16 "$OUI")" # The MAC-prefix lookup the ticket asks for, three real assignments and a control. check_eq oui/00-50-56 "VMware, Inc." "$(val vmware "$OUI")" check_eq oui/00-1B-21 "Intel Corporate" "$(val intel "$OUI")" check_eq oui/08-00-27 "PCS Systemtechnik GmbH" "$(val pcs "$OUI")" check_eq oui/absent "" "$(val absent "$OUI")" check_out oui/header "OUI/MA-L" head -2 "$HW/oui.txt" # iab.txt is the MA-S registry: a 24-bit prefix SHARED between assignees, each # holding a 12-bit range of it. Its second line is therefore a RANGE # (`0D7000-0D7FFF`), not a bare base-16 prefix, which is the one grammatical # difference from oui.txt and the reason it gets its own parse. IAB=$(LC_ALL=C awk ' match($0, /^[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][ \t]+\(hex\)/) { n++; org = $0; sub(/^.*\(hex\)[ \t]+/, "", org) if (n==1) { p1 = substr($0,1,8); o1 = org } next } match($0, /^[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]-[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][ \t]+\(base 16\)/) { m++; if (m==1) { r1 = $0; sub(/[ \t]+\(base 16\).*$/, "", r1) } next } END { printf "hex=%d\nrange=%d\nfirstpre=%s\nfirstorg=%s\nfirstrange=%s\n", n+0, m+0, p1, o1, r1 } ' "$HW/iab.txt" 2>&1) check_eq iab/records 4575 "$(val hex "$IAB")" check_eq iab/hex-range-agree "$(val hex "$IAB")" "$(val range "$IAB")" check_eq iab/first-prefix "40-D8-55" "$(val firstpre "$IAB")" check_eq iab/first-org "Avant Technologies" "$(val firstorg "$IAB")" check_eq iab/first-range "0D7000-0D7FFF" "$(val firstrange "$IAB")" check_out iab/header "IAB Range" head -2 "$HW/iab.txt" # --- section 5: pnp.ids ----------------------------------------------------- # The legacy EISA/PnP three-letter manufacturer codes -- a flat # `AAAOrganization` table with no header and no version stamp of any kind, # which is why it is counted and looked up but has no version case. PNP=$(LC_ALL=C awk ' match($0, /^[A-Z][A-Z][A-Z]\t/) { n++; id = substr($0,1,3) if (id=="IBM") a = substr($0,5) if (id=="DEL") b = substr($0,5) next } /^[ \t]*$/ { next } { bad++; if (bad<=2) badline[bad] = substr($0,1,3) } END { printf "n=%d\nbad=%d\nibm=%s\ndel=%s\nbad1=%s\nbad2=%s\n", n+0, bad+0, a, b, badline[1], badline[2] } ' "$HW/pnp.ids" 2>&1) check_eq pnp/records 2544 "$(val n "$PNP")" check_eq pnp/IBM "IBM Brasil" "$(val ibm "$PNP")" check_eq pnp/DEL "Dell Inc." "$(val del "$PNP")" # TWO RECORDS IN pnp.ids DO NOT MATCH ITS OWN GRAMMAR, and that is upstream's # table rather than a fault in this port: `inu` is lower-case and `VQ@` is not # three letters at all. Both are in the source at this SRCREV, both reproduce on # any host, and neither is anything brintOS can fix -- so they are PINNED here, # by count and by id, instead of being left to read as a red or quietly excluded # by a laxer regex. The day upstream corrects them this case goes red, which is # the prompt to re-read this comment and drop the exception. check_eq pnp/malformed-upstream-count 2 "$(val bad "$PNP")" check_eq pnp/malformed-upstream-1 "inu" "$(val bad1 "$PNP")" check_eq pnp/malformed-upstream-2 "VQ@" "$(val bad2 "$PNP")" # --- section 6: the pkg-config file ----------------------------------------- # THIS IS THE ONE WAY AN allarch DATA RECIPE GENUINELY GOES WRONG. hwdata.pc is # generated on the BUILD host at do_configure from hwdata.pc.in; it is the single # file in the package whose contents could carry a build-host path instead of a # target one, and nothing about the tables above would show it if they did. check hwdata/pc-file test -f "$PC" if ! command -v pkg-config >/dev/null 2>&1; then bst_skip hwdata/pc-query "no pkg-config on this image; the .pc cannot be resolved here" else # `--modversion` reads the Version field, which comes from hwdata's own # release number and IS the recipe's PV. This is the assertion against PV; # the table stamps in sections 2-3 are assertions against the SRCREV, and # comparing either to the other would be asserting a relationship that does # not exist -- hwdata is a redistribution and its tables are older than it. check_eq hwdata/pc-modversion "0.381" "$(LC_ALL=C pkg-config --modversion hwdata 2>&1)" pkgdatadir=$(LC_ALL=C pkg-config --variable=pkgdatadir hwdata 2>&1) # THE LITERAL ANSWER CARRIES A DOUBLED LEADING SLASH, and it is pinned rather # than normalised away. hwdata.pc spells the variable # `pkgdatadir=${pc_sysrootdir}${datadir}/hwdata` so that pkgconf substitutes a # sysroot at QUERY time instead of baking one in at configure time, and pkgconf # defaults pc_sysrootdir to "/" when the environment names none -- so the # answer is `//usr/share/hwdata`. Linux resolves that identically to the # single-slash form. Pinning it means a change in either half of that # mechanism is visible here instead of being absorbed by a tolerant matcher. check_eq hwdata/pc-pkgdatadir-literal "//usr/share/hwdata" "$pkgdatadir" # And the assertion that actually matters: whatever string it answered, the # directory that string names is the one the payload landed in. This is the # case that goes red if a build-host path is ever baked into the .pc -- the # literal case above would go red too, but this one says WHY it matters. check hwdata/pc-pkgdatadir-resolves test -f "$pkgdatadir/pci.ids" check_eq hwdata/pc-pkgdatadir-is-datadir "$(size "$HW/pci.ids")" "$(size "$pkgdatadir/pci.ids")" # No build-host path anywhere in the shipped file. The tmp- work tree and # the recipe workdir are the two shapes a leaked configure-time path takes. leak=$(LC_ALL=C awk '/tmp-glibc|tmp-musl|\/work\/|recipe-sysroot|\/build-/{n++} END{print n+0}' "$PC" 2>&1) check_eq hwdata/pc-no-buildhost-path 0 "$leak" # hwdata.pc is reachable on pkgconf's DEFAULT search path with no # PKG_CONFIG_PATH set, which is not a given: it installs to # ${datadir}/pkgconfig, not ${libdir}/pkgconfig where every compiled port on # this image puts its .pc, and poky's pkgconf is configured with both only # because of --with-pkg-config-dir. Asserted by NAME resolution, which is the # part that belongs to this port and passes. check hwdata/pc-default-search-path env -u PKG_CONFIG_PATH -u PKG_CONFIG_LIBDIR \ pkg-config --exists hwdata # THIS CASE IS EXPECTED RED, AND IT IS NOT ABOUT hwdata. # # Enumerating the default search path is the stronger statement -- it says # hwdata is visible to a consumer that DISCOVERS packages rather than one that # already knows the name -- and it cannot be made on this image today, because # `pkg-config --list-all` dies partway through with SIGILL (rc 132) after # printing ~337 of the image's 505 packages. The cause is nothing to do with # this port: the coroutine frame arena bump-extends to a 252 MB high-water on # ELEVEN live frames over ~1.04M allocations, hits the 256 MiB per-process # userMaxBytes ceiling, and traps instead of returning failure the way an # ordinary malloc at the very same ceiling does (gawk reports a clean ENOMEM # in the same boot). Measured with hwdata excluded entirely # (PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig, 499 packages, none of them hwdata's) # -- it reds identically, so hwdata neither causes nor worsens it. # # BRINTOS-WORKAROUND: brintos/linux#95 remove -- left RED and attributed # rather than skipped or asserted-as-broken, so it flips GREEN by itself the # day the frame arena stops trapping on OOM. When it does, delete this comment # and this note in docs/brintos-test-plan.md 364.1; do not "fix" the case by # weakening it. check_out hwdata/pc-list-all "hwdata" pkg-config --list-all fi # --- section 7: what this image deliberately does NOT carry ------------------ # Recorded rather than left to be discovered, the same way 99-iso-codes.test # records its missing locale catalogues. The natural readers of these tables are # lspci and lsusb; neither pciutils nor usbutils is ported, and a target with no # PCI or USB bus to enumerate would have nothing for them to report anyway. So # the image gains a data set and no new behaviour in anything already installed. # A skip, not a red -- and the place that decision is written down. if command -v lspci >/dev/null 2>&1 || command -v lsusb >/dev/null 2>&1; then bst_ok hwdata/consumer-present else bst_skip hwdata/consumer-present \ "no lspci/lsusb on this image (pciutils and usbutils are not ported); the tables ship for future consumers" fi