# BST-TIMEOUT: 300 # PROVISIONAL -- NOT measured. 48 cases over 20 faces, whose # # cost is one find, one sha256sum of 6.2 MB, twenty fc-scans # # and one fc-list. No freetype-probe run (see below), so this # # should sit BELOW its truetype neighbours rather than above: # # 99-ttf-inconsolata measured 30s with one probe run and # # 99-ttf-bitstream-vera 37s with ten. The bound MUST be reset # # from a real `==BST-SUITE elapsed=` line the first time this # # suite runs on target; it never has, because the image could # # not be built at this Epic's meta-brintos pin (BRINTOS-1762). # # Do not treat this number as measured. It is 10x a guess. # # ttf-roboto 2.138 -- Google's Roboto, taken from the upstream roboto-android.zip # release. meta-openembedded meta-oe/recipes-graphics/ttf-fonts/ttf-roboto_2.138.bb. # # WHAT THIS IMAGE ACTUALLY INSTALLS, MEASURED, NOT ASSUMED: TWENTY files in ONE # package, 6,357,524 bytes, all at /usr/share/fonts/truetype/. Twelve Roboto-* # faces and eight RobotoCondensed-* ones. The recipe also produces ttf-roboto-dbg # and ttf-roboto-dev; both are empty and neither is in IMAGE_INSTALL. There is no # -doc package, which is why the Apache-2.0 text does not reach the target -- see # THE LICENCE below. # # EIGHT FAMILIES RIDE ON TWENTY FILES, AND THAT IS THE FACT THIS SUITE IS BUILT # AROUND. The ticket says "fc-list enumerates the family by name", singular. It is # not one. Roboto ships the TYPOGRAPHIC (name ID 16/17) split: the RIBBI four # carry no 16/17 and declare one family each, while Black, Light, Medium, Thin -- # and their italics, and the Condensed equivalents -- each declare their OWN # legacy family in name ID 1 plus the typographic family in 16. fontconfig reads # both, so # # Roboto-Bold.ttf family "Roboto" # Roboto-Black.ttf family "Roboto,Roboto Black" style "Black,Regular" # # and the package presents Roboto, Roboto Black, Roboto Light, Roboto Medium, # Roboto Thin, Roboto Condensed, Roboto Condensed Light and Roboto Condensed # Medium. fc-families asserts all eight from the CONTROL FILE's own family column # rather than from the package name, because the package name is wrong about this. # # WHAT do_install COULD HAVE SWALLOWED, WHICH IS NOT WHAT THE TICKET ASKED ABOUT. # The ticket flags FILES:${PN} = "${datadir}/fonts/truetype/*.ttf" as a wildcard # that might claim a neighbour's file. It cannot: FILES globs this recipe's own # ${D}, so no other package's file is reachable through it. The real exposure was # one level up and this port fixes it -- upstream sets S = "${WORKDIR}" and # ttf.inc do_installs with `find ./ -name '*.tt[cf]'`, so the SCAN ROOT was # ${WORKDIR}: it contained ${D} (hence five `install: ... are the same file` # lines, nondeterministically, on the 2026-08-28 build) and it also contained # recipe-sysroot-native/, temp/ and pseudo/. Any .tt[cf] another package left # there would have been packaged as ours. The bbappend unpacks into a # subdirectory; ttf-count and ttf-no-extras below are what would catch a # regression of it, and the manifest pins the file list EXACTLY for the same # reason. # # FOUR LAYERS, AND A RED IN AN EARLIER ONE LOCALISES A RED IN A LATER ONE: # # file-*, ttf-* the payload arrived intact -- size, bytes, mode, type, # cotenant-* count -- and the OTHER font directory is untouched. No # font machinery involved at all. # pkg-*, postinst-* the one package the image really installs, the fontcache # class's RDEPENDS, and the deferred first-boot scriptlet. # fcq-*, no-mono-* fontconfig's own parse of each FILE, against a native # x86-64 fontconfig 2.15.0 control -- the same release this # image ships. # fc-families, discovery THROUGH THE CACHE, which is a different path # no-false-family again: a face on disk and absent from the cache is green # everywhere above and red here. # # THERE IS NO freetype-probe LAYER HERE, DELIBERATELY. Its three neighbours run # the shared 7.8 MB data/freetype-probe; on this port it would buy nothing twice # over, and the reasoning is in ttf-roboto-verify.awk beside the gap it leaves. # Short version: file-* pins every face by sha256, so the probe cannot disagree # about the FILE, and the driver it would exercise (freetype's TrueType path) is # already covered by 57-freetype and by those three suites. Twenty invocations # would roughly double this suite's elapsed to re-answer a settled question. # # THE MONOSPACE GENERIC IS NOT AT RISK FROM THIS PORT, and no-mono-claim is the # assertion that says so from the file rather than from the ranking. # post.isFixedPitch is 0 in all twenty faces and fontconfig sets FC_SPACING only # for mono/dual/charcell, so every face's spacing field is EMPTY. A port that # started claiming FC_MONO could contend for `fc-match monospace`, which # source-code-pro-fonts owns by explicit in its own # 44-source-code-pro-fonts-fontconfig.conf; this one structurally cannot. # # WHAT THIS SUITE DELIBERATELY DOES NOT ASSERT, AND WHY. Two things, both # measured rather than assumed away: # # THE GENERIC ALIASES. An x86-64 control built from this image's own font # payload and its own 24-fragment conf.d answered `fc-match sans-serif` with # LiberationMono-Regular.ttf without this port and Roboto-Regular.ttf with it. # That control FAILS ITS OWN VALIDATION and so is not evidence: 99-libass # records the target's real baseline in its case name, # `libass/fc-sans-face-LiberationSans-Regular.ttf` -- Liberation SANS, which the # control never reproduced in either arm. So whether this port takes the # sans-serif generic on target is UNKNOWN, and no case here claims either way. # 99-libass's own case is a shape test ("a font this image ships") and stays # green whichever face wins; its companion `fc-sans-face-*` is a name-carrying # nothing-case, so a change of winner renames it rather than reddening it. The # first on-target run of this suite is what settles the question. # # `fc-match` FOR A FAMILY WE DO NOT SHIP. The ticket asks that `fc-match` for # such a family "not resolve here". fc-match cannot express that -- it ALWAYS # returns a best match, so asking it for "Roboto Slab" on any image answers # something, and on the control it answers Roboto-Regular.ttf by fallback. A # case built on it would assert nothing while looking strict. The property the # requirement is reaching for is about what the installed faces DECLARE, and # that is exactly what fc-list reports: no-false-family asserts that no face on # this image claims "Roboto Slab" or "Roboto Mono", both real Google families # and neither in roboto-android.zip. # # THE LICENCE IS NOT ON THE TARGET, and that is upstream's shape, not a defect # introduced here. ttf.inc's do_install copies only *.tt[cf], so the zip's # 11,357-byte LICENSE is never installed and no -doc package exists to hold it. # Roboto is Apache-2.0. This image sets neither COPY_LIC_MANIFEST nor # COPY_LIC_DIRS, so licence text ships in the build's licence manifest rather # than in the rootfs -- the same treatment every other package on this image # gets. Asserted nowhere here because there is nothing on target to assert; it is # recorded so the next reader does not go looking for a doc-* case. # # WRITTEN TO EXEC AS LITTLE AS POSSIBLE. One find, one sha256sum, one fc-cache, # one fc-list, one sed and one gawk, plus the per-face fc-scan loop which IS the # measurement. . "$BST_LIB" S=ttf-roboto # THE CANARY, which asserts nothing about ttf-roboto. # # 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-1149 established that and filed it as linux-in-the-browser#177). # 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; every real # case below is still counted. It sits ahead of the `require` guards # deliberately, because a `require` that skips 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=$BST_DATA REF=$D/ttf-roboto-manifest.txt FACES=$D/ttf-roboto-fcfaces.txt TTFDIR=/usr/share/fonts/truetype COTDIR=/usr/share/fonts/ttf POSTINST=/var/lib/dpkg/info/ttf-roboto.postinst tmp=${TMPDIR:-/tmp} W=$tmp/bst-ttf-roboto.$$ require gawk $S/gawk-present require sha256sum $S/sha256sum-present require find $S/find-present # fc-scan and fc-list run in the payload block, ahead of the consumer block's own # check, so their absence is guarded here. NOT a `require`: fontconfig-utils is a # hard Depends of this package (fontcache.bbclass appends it), so an absent # fc-scan is a defect in the port rather than a legitimately absent tool, and a # skip would report it green. if [ -x /usr/bin/fc-scan ]; then bst_ok $S/fc-scan-present else bst_notok $S/fc-scan-present "fontconfig-utils is a Depends of ttf-roboto but /usr/bin/fc-scan is absent"; fi if [ -x /usr/bin/fc-list ]; then bst_ok $S/fc-list-present else bst_notok $S/fc-list-present "fontconfig-utils is a Depends of ttf-roboto but /usr/bin/fc-list is absent"; fi if [ ! -f "$REF" ] || [ ! -f "$FACES" ]; then bst_notok $S/ref-present "the reference data did not ship: $REF / $FACES" exit 0 fi bst_ok $S/ref-present # ------------------------------------------------------------- payload shape # One find over the payload directory AND over the other font directory, so every # count is a measurement from the same pass. %y catches a symlink where a regular # file is meant and %m the mode -- neither of which `test -f` can see. find "$TTFDIR" "$COTDIR" -maxdepth 1 -printf '%p %y %s %m %l\n' 2>/dev/null \ | LC_ALL=C sort > "$W.inv" # One sha256sum over our twenty faces. Driven from the MANIFEST's own path list # rather than from a glob: a glob would silently digest a co-tenant's file if one # ever lands here, and this directory has four other tenants already. sed -n 's/^[0-9][0-9]* *[0-9a-f]* *\(\/.*\)$/\1/p' "$REF" > "$W.paths" xargs -a "$W.paths" sha256sum > "$W.sums" 2>&1 # One fc-scan per face. fontconfig opens each through FreeType, so this is an # independent parse -- and it reads fields no sfnt table states (the derived # slant, weight and spacing, the format, the PostScript name). : > "$W.scan" while IFS= read -r f; do LC_ALL=C fc-scan --format '%{file|basename}|%{family}|%{style}|%{slant}|%{weight}|%{spacing}|%{postscriptname}|%{fontformat}|%{scalable}|%{outline}\n' \ "$f" 2>/dev/null >> "$W.scan" done < "$W.paths" LC_ALL=C sort -o "$W.scan" "$W.scan" # BRINTOS-WORKAROUND: linux-in-the-browser#165 revisit -- fc-cache must be bounded # and ordered last among the consumers. A file-backed mmap above a length # threshold is refused and the consumer parks beyond SIGTERM and SIGKILL, so an # unbounded `fc-cache -f` can hang the whole suite (measured >1190 s when # ttf-dejavu's 577,708-byte face was installed). THIS PORT ADDS NO NEW EXPOSURE: # the defect is per-FILE, its largest face is 332,504 bytes, and liberation-fonts # 2.1.5 already puts a 415,816-byte face in front of every fc-cache this image # runs. The 6.2 MB this port adds is volume, not a new maximum. Bounded anyway, # matching the four sibling font suites, until #165 is fixed. timeout -k 5 90 fc-cache -f >/dev/null 2>&1 LC_ALL=C fc-list --format '%{family}\n' 2>/dev/null | LC_ALL=C sort -u > "$W.fclist" LC_ALL=C gawk -v REF="$REF" -v SUMS="$W.sums" -v INV="$W.inv" \ -v FACES="$FACES" -v SCAN="$W.scan" -v FCLIST="$W.fclist" \ -f "$D/ttf-roboto-verify.awk" "$REF" "$W.sums" "$W.inv" "$FACES" "$W.scan" "$W.fclist" # ---------------------------------------------------- the packaging on target # THE PACKAGE DATABASE IS THE ONLY PLACE THE PACKAGING IS VISIBLE ON TARGET. The # file-* cases would look identical if the faces had arrived by any other route, # so they cannot say that ttf-roboto is a real package the image installs rather # than an IMAGE_INSTALL line that resolved to nothing. One sed pulls the stanza; # the loop is shell builtins. The stanza is read from a tmpfs copy, never # redirected from the rootfs (brintos/linux#73). sed -n '/^Package: ttf-roboto$/,/^$/p' /var/lib/dpkg/status > "$W.st" 2>/dev/null _known=0; _dep=0; _arch=0 while IFS= read -r l; do case "$l" in "Package: ttf-roboto") _known=1 ;; "Architecture: all") _arch=1 ;; "Depends: "*) case "$l" in *fontconfig-utils*) _dep=1 ;; esac ;; esac done < "$W.st" if [ "$_known" -eq 1 ]; then bst_ok $S/pkg-known else bst_notok $S/pkg-known "dpkg does not know the package ttf-roboto"; fi # `inherit allarch` is what makes the identical-to-upstream claim provable rather # than measured on one arm; this is that inherit, read back on target. if [ "$_arch" -eq 1 ]; then bst_ok $S/pkg-allarch else bst_notok $S/pkg-allarch "ttf-roboto is not Architecture: all -- allarch did not take"; fi # fontcache.bbclass appends fontconfig-utils to RDEPENDS. Without it the deferred # fc-cache below has no tool to run. if [ "$_dep" -eq 1 ]; then bst_ok $S/pkg-depends-fontconfig else bst_notok $S/pkg-depends-fontconfig "ttf-roboto does not Depend on fontconfig-utils"; fi # THE fontcache POSTINST, CHECKED IN THE ONE FORM THAT IS BOOT-MODE INDEPENDENT. # fontcache.bbclass registers an update_font_cache intercept that runs fc-cache # under qemu-user; there is no wasm32 qemu-user, so it is deferred to # run-postinsts at first boot, which /etc/inittab does run (BRINTOS-53). Whether # it HAS run depends on the pid 1 the harness booted -- the local runner goes # straight to a shell -- so asserting the configured state here would report the # runner's choice as a package red. What is asserted instead is the actual risk: # the scriptlet is on the image, it calls fc-cache, and fc-cache is present, so # the deferred run cannot fail for want of the tool. Image-wide postinst success # is 79-run-postinsts' `no-unpacked` case and is not re-litigated here. The # fc-list block above forces `fc-cache -f` itself, so it is independent of all # of this. if [ -f "$POSTINST" ]; then bst_ok $S/postinst-present if grep -q 'fc-cache' "$POSTINST" 2>/dev/null; then bst_ok $S/postinst-calls-fc-cache else bst_notok $S/postinst-calls-fc-cache "$POSTINST does not call fc-cache"; fi else bst_notok $S/postinst-present "$POSTINST is absent -- fontcache.bbclass registered no scriptlet" fi # ------------------------------------------------------------------- fc-match # REQUIREMENT 7, ON TARGET. The design doc met this with a native fontconfig # 2.15.0 control on x86-64. That rig could not reproduce its own baseline for the # GENERICS -- it answered Liberation Mono where 99-libass records Liberation Sans # -- so its generic verdict was reported and not used, and its per-family # verdicts, though direct matches rather than tie-breaks, were asserted nowhere # the image could check them. These cases assert both from the image itself. if [ -x /usr/bin/fc-match ]; then bst_ok $S/fc-match-present else bst_notok $S/fc-match-present "fontconfig-utils is a Depends of ttf-roboto but /usr/bin/fc-match is absent"; fi _out=$(fc-match --format '%{family}\n' "Roboto" 2>&1) case "$_out" in "Roboto") bst_ok $S/fc-match-family ;; *) bst_notok $S/fc-match-family "fc-match Roboto => '$(_bst_1line "$_out")', want 'Roboto'" ;; esac _out=$(fc-match --format '%{family}\n' "Roboto Condensed" 2>&1) case "$_out" in "Roboto Condensed") bst_ok $S/fc-match-family-condensed ;; *) bst_notok $S/fc-match-family-condensed "fc-match 'Roboto Condensed' => '$(_bst_1line "$_out")', want 'Roboto Condensed'" ;; esac # THE GENERICS THIS PORT TAKES, ASSERTED FROM THE TAKING SIDE. Measured on the # first on-target run of this suite: system-ui, fantasy and cursive move from # Liberation Mono to Roboto. None of the three is seeded by any shipped fragment # and this port ships none, so each falls to the highest-ranked family actually # installed and twenty Roboto faces outrank the incumbent. 99-cantarell-fonts and # 99-source-code-pro-fonts are amended by this ticket to compare against the new # value; they keep their -unchanged names, because what they assert is that THEIR # OWN port did not move the generic, which is still true. That is the same repair # BRINTOS-1455 made to four suites for `monospace`, in the same direction. A # disagreement between any of the three suites is visible rather than silent. # RE-MEASURED FOR BRINTOS-1729 ON THE MERGED FONT SET, AND ROBOTO NO LONGER # HOLDS THESE THREE -- ttf-tlwg's Waree does. None of the three is seeded by any # shipped fragment, so each falls to the highest-ranked family actually # installed; that is exactly why it moves again whenever a port adds faces, and # ttf-tlwg adds 58. MEASURED on the merged image (2026-08-31, one suite per # boot), not predicted: all three read `Waree`, where main reads `Roboto` and # this port's own pre-merge image also read `Waree`. # # THE `-taken` CASE NAMES ARE KEPT DELIBERATELY, EVEN THOUGH THIS PORT NO LONGER # TAKES THESE GENERICS. What the case is worth is that the generic resolves to # the family the whole image actually selects, and it still says that. Renaming # a case id breaks every comparison against an earlier run, for the same reason # a test-plan section is never renumbered -- so the id stays and this note # carries the meaning. The value is owned by whichever port ranks first, which # is now ttf-tlwg; 99-ttf-tlwg asserts the same three from the taking side. for _pair in "system-ui:Waree" "fantasy:Waree" "cursive:Waree"; do _gen=${_pair%%:*}; _want=${_pair#*:} _out=$(fc-match --format '%{family}\n' "$_gen" 2>&1) case "$_out" in "$_want") bst_ok "$S/fc-match-$_gen-taken" ;; *) bst_notok "$S/fc-match-$_gen-taken" "fc-match $_gen => '$(_bst_1line "$_out")', want '$_want' -- this port is expected to take this generic" ;; esac done # THE GENERICS IT DOES NOT TAKE. sans-serif is the one the x86-64 control # suspected this port of taking and could not decide; on target it stays with # Liberation Sans. monospace is decided against this port structurally -- see # no-mono-claim above -- and source-code-pro-fonts holds it by explicit . # AMENDED FOR BRINTOS-1729 (ttf-tlwg), WHICH TAKES sans-serif AND serif # IMAGE-WIDE. ttf-tlwg ships ten fragments into /etc/fonts/conf.d # at 64; PREPENDS ahead of the generic while 30-liberation-aliases.conf's # only APPENDS behind it, so Kinnari takes `serif` and Waree takes # `sans-serif` for every suite on this image at once. It is CONFIGURATION-driven, # so it does not depend on how large the font set is. monospace is unaffected -- # source-code-pro-fonts' own loads at 44, ahead of these. THE CASE IS # AMENDED, NOT DELETED: what it asserts is that THIS port does not take a generic, # which is still true, and only the value it is compared against moved. for _pair in "sans-serif:Waree" "serif:Kinnari" "monospace:Source Code Pro"; do _gen=${_pair%%:*}; _want=${_pair#*:} _out=$(fc-match --format '%{family}\n' "$_gen" 2>&1) case "$_out" in "$_want") bst_ok "$S/fc-match-$_gen-untaken" ;; *) bst_notok "$S/fc-match-$_gen-untaken" "fc-match $_gen => '$(_bst_1line "$_out")', want '$_want' -- this port must not take this generic" ;; esac done rm -f "$W".* 2>/dev/null