# BST-TIMEOUT: 120 # MEASURED, not scaled. The batch-1448 shared image reported
# # `==BST-SUITE== mobile-broadband-provider-info elapsed=12s
# # bound=600s cases=81` on 2026-08-26, so the scaled 600 s this
# # suite shipped with was 50x the real cost and bounded nothing.
# # 120 s is 10x the measurement rather than the ~3x the previous
# # note asked for: it is ONE observation, taken on an image six
# # suites shared, and that same capture logged two hwjs worker
# # stalls (1.5 s and 1.0 s) -- so the run-to-run spread is real
# # and a 36 s bound would red on contention rather than on a hang.
# # Re-tighten once several runs agree. The cost is bytes rather
# # than cases: six full parses of a 364 kB XML off the virtiofs
# # root -- three xmllint validations, two xsltproc census passes
# # and one sha256sum, roughly 2.2 MB read. Native x86-64 runs the
# # whole file in 0.08 s, which bounds nothing here and is recorded
# # only to say the suite itself is not doing arithmetic it should not.
#
# mobile-broadband-provider-info 20251101 (git 18cb9751) -- the GNOME mobile
# broadband service-provider database. poky meta, recipes-connectivity.
#
# WHAT SHIPS: three files under /usr/share/mobile-broadband-provider-info.
# serviceproviders.xml (364 kB) is the catalogue itself -- 154 countries, 700
# operators, 1281 APNs -- and serviceproviders.2.dtd is the grammar its own
# DOCTYPE names. Both are COPIED verbatim by meson's install_data, so their
# bytes are upstream's and are pinned below against the tarball at SRCREV.
#
# THE THIRD FILE IS THE ONE WORTH A SUITE. apns-conf.xml is GENERATED at build
# time: meson runs xsltproc (from libxslt-native) over apns-conf.xsl and the
# catalogue to flatten the nested database into Android's flat form.
# It is the only artifact here that a cross build can get wrong, and a silently
# shorter one is invisible -- nothing on this image reads it, so a client that
# could not find an operator is the first symptom, months away. The recipe's
# `DEPENDS += "libxslt-native"` is what makes the generator a host tool; the
# ticket's stated risk was that the arm's install set or record count would
# differ from a native build's, and the census below is the measurement of that.
#
# THE REFERENCE IS A NATIVE x86-64 RUN OF UPSTREAM'S OWN ROUTE, not a re-parse
# of our own .deb: the tarball at SRCREV 18cb9751 extracted on x86-64, upstream's
# apns-conf.xsl put through xsltproc, and the result censused with the same two
# stylesheets this suite runs (2026-08-25). Every count below came from there.
#
# ALL THREE FILES ARE PINNED BY DIGEST, INCLUDING THE GENERATED ONE, and that
# was not a foregone conclusion. The reference apns-conf.xml was produced under
# libxslt 1.1.39 and the build's libxslt-native is 1.1.43; the two outputs are
# BYTE-IDENTICAL -- 151,659 bytes, same sha256 -- measured on the wasm32 .deb
# against the native run (2026-08-25). The transform is deterministic across that
# version gap, so the digest pins the catalogue's content rather than the
# generator's version, and it is worth having.
#
# THE CENSUS IS NOT MADE REDUNDANT BY IT and is the reason the cases below exist
# at all. A digest and a missing file produce the same red with the same message;
# the census fails BY CAUSE -- which record class went short, which attribute
# arrived stripped -- and it is what would survive a libxslt release that DID
# move the output's whitespace, at which point the digest becomes the case to
# re-measure and the counts stay the statement about content. Should that day
# come, re-take the reference rather than deleting the case.
#
# THE HEADLINE IS THE CROSS-HALF INVARIANT, and it is why both halves are
# censused rather than only the generated one: the flat file must carry exactly
# one per / in the catalogue -- 1281 = 1281 -- computed from the
# two files as installed. A generator that dropped the tail of the catalogue
# passes every count taken from the flat file alone and fails this one.
#
# NOTHING HERE DIALS ANYTHING and nothing on this image reads the database. The
# consumer is a connection manager (ModemManager, ofono, connmand); none is
# installed, this platform models no modem, and the port does not imply one. The
# consumer block at the bottom asserts that absence rather than assuming it, so
# the day a consumer lands the suite says so instead of going quietly stale.
. "$BST_LIB"
S=mobile-broadband-provider-info
# THE CANARY, which asserts nothing about this package.
#
# A suite's FIRST BST result line is lost between the guest's printf and the
# host's read -- per-suite, deterministic, and not a defect in the host tool
# (brintos/linux-in-the-browser#177, established by BRINTOS-1149). The harness
# reports the deficit but computes its pass count as total-minus-failures, so THE
# DROPPED LINE IS COUNTED AS A PASS. A nothing-case therefore stands in that
# position and the eaten line costs nothing. It sits ahead of the `require`
# guards deliberately: a `require` that skipped would emit its skip as the first
# line and that line would be eaten too.
#
# Remove this case when linux-in-the-browser#177 is fixed.
bst_ok $S/suite-canary
D=/usr/share/mobile-broadband-provider-info
SP=$D/serviceproviders.xml
DTD=$D/serviceproviders.2.dtd
AP=$D/apns-conf.xml
PC=/usr/share/pkgconfig/mobile-broadband-provider-info.pc
tmp=${TMPDIR:-/tmp}
W=$tmp/bst-mbpi.$$
require xmllint $S/xmllint-present
require xsltproc $S/xsltproc-present
require sha256sum $S/sha256sum-present
require find $S/find-present
# ------------------------------------------------------------- payload shape
# ONE find over every path this suite has an expectation about, the two it must
# NOT have produced included. %y catches a symlink where a regular file is meant
# and %m the mode -- neither of which `test -f` can see.
find "$D" "$SP" "$DTD" "$AP" "$PC" /usr/share/doc/mobile-broadband-provider-info \
-maxdepth 0 -printf '%p %y %m\n' 2>/dev/null | LC_ALL=C sort > "$W.top"
# The directory's whole contents as one (type, mode) census. One line of output
# means one class across all of it, so a stray 0755 data file or a symlink makes
# this two lines and fails by cause rather than only shifting a count.
find "$D" -mindepth 1 -printf '%y %m\n' 2>/dev/null | LC_ALL=C sort | uniq -c \
| LC_ALL=C sed 's/^ *//' > "$W.cls"
for pair in "dir:$D" "sp:$SP" "dtd:$DTD" "apns:$AP"; do
nm=${pair%%:*}; f=${pair#*:}
if [ -e "$f" ]; then bst_ok "$S/present-$nm"
else bst_notok "$S/present-$nm" "$f does not exist"; fi
done
if [ ! -f "$SP" ] || [ ! -f "$AP" ] || [ ! -f "$DTD" ]; then
bst_notok $S/scan-prereq "a payload file is missing; the census below cannot run"
rm -f "$W.top" "$W.cls"
return 2>/dev/null || exit 0
fi
check_eq $S/dir-is-dir "$D d 755" "$(grep "^$D " "$W.top")"
check_eq $S/sp-is-file "$SP f 644" "$(grep "^$SP " "$W.top")"
check_eq $S/dtd-is-file "$DTD f 644" "$(grep "^$DTD " "$W.top")"
check_eq $S/apns-is-file "$AP f 644" "$(grep "^$AP " "$W.top")"
# EXACTLY THREE FILES, all regular and all 0644. The install set is the ticket's
# first question -- "does the meson build install the same set on this arm as it
# does natively" -- and this is the whole of the answer for the runtime package.
check_eq $S/dir-uniform '3 f 644' "$(cat "$W.cls")"
check_eq $S/dir-one-class 1 "$(wc -l < "$W.cls")"
# THE .pc BELONGS TO -dev AND -dev IS NOT INSTALLED. `pkgconfig.generate(...
# dataonly: true)` puts it under ${datadir}/pkgconfig, which Yocto packages into
# ${PN}-dev; the image installs the runtime package only. Its presence here would
# mean the packaging split did not take.
check_eq $S/no-pc-in-runtime '' "$(grep "^$PC " "$W.top")"
# The recipe installs nothing under ${docdir}, so no -doc package is built at all.
check_eq $S/no-doc-dir '' "$(grep '^/usr/share/doc/' "$W.top")"
# --------------------------------------------------- the catalogue, as XML
# THE DTD IS RESOLVED FROM BESIDE THE DOCUMENT. The DOCTYPE names it by a
# relative SYSTEM id, so validation passing here is also a statement that the
# grammar landed in the same directory the catalogue expects it in -- which is
# not a given for a two-file install_data.
check $S/xml-valid xmllint --valid --noout "$SP"
check $S/apns-wf xmllint --noout "$AP"
# The DTD parses on its own terms. A truncated grammar can still let a valid
# document through if the truncation lands past the elements the document uses.
check $S/dtd-parses xmllint --noout --dtdvalid "$DTD" "$SP"
# ---------------------------------------- the copied files, byte for byte
# Against the tarball at SRCREV 18cb9751, hashed on x86-64. install_data copies
# these, so any difference is transport damage -- a text-mode copy, a line-ending
# fixup, a truncated write onto the virtiofs root.
check_eq $S/sp-sha256 \
ac3e3acb55329b32223d3fe0a0c3e78993f4c37e9476e768f0bdf012d8865a3a \
"$(sha256sum "$SP" | cut -d' ' -f1)"
check_eq $S/dtd-sha256 \
b988667e20281eb76f6fb07dab69899f49e5aad312209104b53048389609ce12 \
"$(sha256sum "$DTD" | cut -d' ' -f1)"
check_eq $S/sp-bytes 363821 "$(wc -c < "$SP")"
check_eq $S/dtd-bytes 6375 "$(wc -c < "$DTD")"
# THE GENERATED FILE, against the native x86-64 xsltproc run of upstream's own
# XSL -- not a re-hash of our own .deb, which would make this tautological. It
# passing is the strongest single statement this port can make: the cross build's
# host transform produced exactly what a native one does, byte for byte. If it
# ever reds while the census below stays green, suspect the libxslt version
# before suspecting the catalogue, and see the header.
check_eq $S/apns-sha256 \
73ac1d5415646c84336ddaa1c28bf1181ab3ac16b699882b5072071a71d2ca79 \
"$(sha256sum "$AP" | cut -d' ' -f1)"
check_eq $S/apns-bytes 151659 "$(wc -c < "$AP")"
# ------------------------------------------- the catalogue, as a database
# ONE xsltproc pass emits every count below as shell assignments. Per-count
# `xmllint --xpath` would be twenty execs each re-parsing 364 kB off the virtiofs
# root, which is where this arm is slowest.
xsltproc "$BST_DATA/mbpi-sp-census.xsl" "$SP" > "$W.sp" 2>/dev/null
. "$W.sp"
# THE SCAN RAN AT ALL. Every count below is compared against a number, and the
# empty string a died-early xsltproc leaves behind would fail each of them
# identically and say nothing. Asserted first so those comparisons rest on
# something.
check_eq $S/sp-scan-ran 'ok' "${MBPI_SCAN:-}"
check_eq $S/sp-format '2.0' "${MBPI_FORMAT:-}"
# The census proper, against the native x86-64 parse.
check_eq $S/sp-countries 154 "${MBPI_COUNTRIES:-}"
check_eq $S/sp-providers 700 "${MBPI_PROVIDERS:-}"
check_eq $S/sp-apns 1281 "${MBPI_APNS:-}"
check_eq $S/sp-gsm 657 "${MBPI_GSM:-}"
check_eq $S/sp-cdma 62 "${MBPI_CDMA:-}"
check_eq $S/sp-network-ids 976 "${MBPI_NETWORK_IDS:-}"
# 700 providers carry 723 elements: a provider localised into more than
# one language has one per lang. Pinned because a reader that took the first name
# and stopped would report 700 and look merely tidy.
check_eq $S/sp-provider-names 723 "${MBPI_PROVIDER_NAMES:-}"
check_eq $S/sp-apn-names 895 "${MBPI_APN_NAMES:-}"
check_eq $S/sp-apn-username 462 "${MBPI_APN_USERNAME:-}"
check_eq $S/sp-apn-password 445 "${MBPI_APN_PASSWORD:-}"
check_eq $S/sp-apn-auth 11 "${MBPI_APN_AUTH:-}"
check_eq $S/sp-apn-mmsc 323 "${MBPI_APN_MMSC:-}"
check_eq $S/sp-apn-mmsproxy 308 "${MBPI_APN_MMSPROXY:-}"
check_eq $S/sp-apn-dns 430 "${MBPI_APN_DNS:-}"
# The usage census -- what the APNs are FOR. A dated snapshot of 20251101 rather
# than an invariant: a later release moves these and moving them is a version
# bump to be re-measured, not a regression.
check_eq $S/sp-usage-internet 906 "${MBPI_USAGE_INTERNET:-}"
check_eq $S/sp-usage-mms 322 "${MBPI_USAGE_MMS:-}"
check_eq $S/sp-usage-wap 19 "${MBPI_USAGE_WAP:-}"
# Seven APNs carry a compound usage type (mms-internet-hipri, and two of those
# with -fota). They are counted apart because a parser that split on '-' would
# fold them into the three above and still total correctly.
check_eq $S/sp-usage-other 7 "${MBPI_USAGE_OTHER:-}"
# Every country has a code and every APN a value -- the two attributes the whole
# database is keyed on. The DTD does not require either, so validity does not
# cover them.
check_eq $S/sp-country-codes 154 "${MBPI_COUNTRY_CODES:-}"
check_eq $S/sp-no-code 0 "${MBPI_COUNTRY_NO_CODE:-}"
check_eq $S/sp-apn-no-value 0 "${MBPI_APN_NO_VALUE:-}"
check_eq $S/sp-provider-tech 0 "${MBPI_PROVIDER_NO_TECH:-}"
# EXACTLY ONE upstream carries no . That is upstream's catalogue as it
# stands and it is pinned rather than tolerated -- as a 0 it would be an
# invariant this database does not have, and as unasserted it would let a
# truncation that emptied a dozen more go by.
check_eq $S/sp-gsm-no-apn 1 "${MBPI_GSM_NO_APN:-}"
# ------------------------------- the generated flat file, as a database
xsltproc "$BST_DATA/mbpi-apns-census.xsl" "$AP" > "$W.ap" 2>/dev/null
. "$W.ap"
check_eq $S/apns-scan-ran 'ok' "${APNS_SCAN:-}"
check_eq $S/apns-root 'apns' "${APNS_ROOT:-}"
check_eq $S/apns-version 8 "${APNS_VERSION:-}"
# THE HEADLINE. One flat per catalogue /, computed from the two
# files as installed. This is what a generator that ran short fails and what
# every count taken from the flat file alone would miss.
check_eq $S/apns-vs-catalogue "${MBPI_APNS:-}" "${APNS_TOTAL:-}"
check_eq $S/apns-total 1281 "${APNS_TOTAL:-}"
# The root element holds apn children and nothing else -- no comment-turned-
# element, no stray node from a half-run transform.
check_eq $S/apns-only-apns 0 "${APNS_NONAPN_CHILDREN:-}"
# The five attributes the XSL emits unconditionally. Any count below 1281 means
# records arrived stripped rather than missing, which the total cannot see.
check_eq $S/apns-carrier 1281 "${APNS_CARRIER:-}"
check_eq $S/apns-mcc 1281 "${APNS_MCC:-}"
check_eq $S/apns-mnc 1281 "${APNS_MNC:-}"
check_eq $S/apns-apn 1281 "${APNS_APN:-}"
check_eq $S/apns-type 1281 "${APNS_TYPE:-}"
# The conditional attributes, present-count and non-empty-count apart. The pairs
# differ (462/453, 445/434, 329/315) because the XSL emits the attribute for a
# source element that exists but is empty; asserting only one of the two would
# let a transform that dropped the empties look correct.
check_eq $S/apns-user 462 "${APNS_USER:-}"
check_eq $S/apns-user-set 453 "${APNS_USER_SET:-}"
check_eq $S/apns-password 445 "${APNS_PASSWORD:-}"
check_eq $S/apns-password-set 434 "${APNS_PASSWORD_SET:-}"
check_eq $S/apns-mmsc 329 "${APNS_MMSC:-}"
check_eq $S/apns-mmsc-set 315 "${APNS_MMSC_SET:-}"
check_eq $S/apns-mmsproxy 329 "${APNS_MMSPROXY:-}"
check_eq $S/apns-mmsport 329 "${APNS_MMSPORT:-}"
# The type census. Every record is 'default'; 'mms' rides with the 329 that carry
# an MMSC, and 'supl' with the 913 the XSL marks for assisted-GPS.
check_eq $S/apns-type-default 1281 "${APNS_TYPE_DEFAULT:-}"
check_eq $S/apns-type-mms 329 "${APNS_TYPE_MMS:-}"
check_eq $S/apns-type-supl 913 "${APNS_TYPE_SUPL:-}"
# EIGHTEEN RECORDS CARRY AN EMPTY mcc/mnc, and that is upstream's catalogue
# rather than a transform defect: 18 blocks have no for the
# XSL to read a code out of, and each holds exactly one APN. Pinned as 18 in
# three independent places -- the empties, the bad-length mcc and the bad-length
# mnc -- so a transform that started losing codes for a real reason moves all
# three together and cannot be mistaken for this.
check_eq $S/apns-empty-mcc 18 "${APNS_NO_MCC:-}"
check_eq $S/apns-mcc-shape 18 "${APNS_MCC_BADLEN:-}"
check_eq $S/apns-mnc-shape 18 "${APNS_MNC_BADLEN:-}"
# Every record names an operator. Unlike the codes there is no upstream exception.
check_eq $S/apns-carrier-set 0 "${APNS_NO_CARRIER:-}"
# --------------------------------------------------------------- the consumer
# THE DATABASE IS INERT ON THIS IMAGE, BY CONSTRUCTION, and everything below is a
# measurement of that rather than an excuse for it. Each case is derived from
# what is installed, so none goes false the day any of it changes -- a consumer
# arriving turns its case into a skip naming what is now possible, not a red.
#
# Two things would have to be true for the catalogue to be read: a connection
# manager, and a modem for it to configure. Neither is. The recipe carries no
# RDEPENDS in either direction -- it is a data package and nothing pulls a
# manager in behind it -- and this platform models no cellular hardware at all.
for c in ModemManager ofonod connmand nmcli; do
if command -v "$c" >/dev/null 2>&1; then
bst_skip "$S/no-consumer-$c" \
"$c is now on PATH -- a connection manager could read the catalogue, and an end-to-end lookup is possible; it is not covered here"
else
bst_ok "$S/no-consumer-$c"
fi
done
# connman-conf IS on this image and is not a consumer: it ships configuration
# fragments for connmand, which is not installed. Asserted so that "connman
# something is present" never reads as a consumer having landed.
if [ -e /usr/sbin/connmand ] || [ -e /usr/bin/connmand ]; then
bst_skip $S/no-connmand "connmand has landed; the fragments in connman-conf are live and the catalogue is reachable"
else
bst_ok $S/no-connmand
fi
# NO MODEM, at either layer the stack would look for one. /sys/class/tty carries
# the platform's consoles and no cdc-acm or option port; /dev/cdc-wdm* is the
# control node a QMI/MBIM modem exposes. A manager finding neither cannot dial
# whatever the catalogue says.
mdm=$(ls -A /sys/class/tty 2>/dev/null | grep -c '^ttyACM\|^ttyUSB\|^ttyWWAN')
check_eq $S/no-modem-tty 0 "$mdm"
wdm=$(ls -A /dev/cdc-wdm0 /dev/cdc-wdm1 2>/dev/null | wc -l)
check_eq $S/no-modem-wdm 0 "$wdm"
# THE DIRECTORY IS THIS PACKAGE'S ALONE. Nothing else in poky, meta-openembedded
# or meta-brintos ships under ${datadir}/mobile-broadband-provider-info, so a
# fourth file appearing there is another recipe claiming the path -- which is the
# one way the digests above could be right while a client reads something else.
own=$(find "$D" -type f 2>/dev/null | wc -l)
check_eq $S/dir-sole-owner 3 "$own"
rm -f "$W.top" "$W.cls" "$W.sp" "$W.ap"