. "$BST_LIB" # BST-TIMEOUT: 300 # One probe run (a 4096-combination sweep plus a handful # of stream builds, milliseconds) and ~60 shell # extractions. Same shape and budget as # 57-xcb-util-keysyms.test. # xcb-util-renderutil 0.3.10 -- the XCB Render utility library (poky # recipes-graphics/xorg-lib). See xcb-util-renderutil_%.bbappend for what the # port adds. # # --------------------------------------------------------------------------- # WHAT IS MEASURED HERE # # libxcb-render-util.a defines exactly 14 global symbols -- measured with # llvm-nm --defined-only on the wasm32 archive, not assumed; the only other # entry is the local `find_display'. All 14 are named by the probe, and they # fall into three groups: # # THREE ARE PURE LOOKUPS over a reply the CALLER supplies: # xcb_render_util_find_standard_format / _find_visual_format / _find_format # These do the library's real work here, against a committed reply. # # SIX BUILD A STREAM IN MEMORY, with no connection until it is sent: # _composite_text_stream / _glyphs_8 / _glyphs_16 / _glyphs_32 / # _change_glyphset / _composite_text_free # Their output is compared byte for byte below. # # FIVE NEED A LIVE CONNECTION and are referenced by the probe but never # called: _query_version / _query_formats / _disconnect / _composite_text / # _composite_text_checked. All three of the first group reach # xcb_get_setup(c) through cache.c's per-connection cache, so unlike the # lookups they carry NO null guard and a call would fault. The link is what # proves the archive still defines them. # # --------------------------------------------------------------------------- # THE TICKET'S PREMISE HOLDS HERE -- WHICH IS WORTH SAYING, BECAUSE THE # SIBLING'S DID NOT # # BRINTOS-2338 (xcb-util-keysyms) asserted that a suite could supply a keyboard # mapping instead of a server, and that was false twice over: the type was # opaque with no injection API, and the function read the connection before # indexing the mapping at all. A reader who met that finding first would # reasonably expect the same correction here. It does not apply. # # xcb_renderutil.h declares all three lookups taking # `const xcb_render_query_pict_formats_reply_t *formats' as their FIRST # PARAMETER, and util.c iterates that parameter with libxcb's own iterators and # touches nothing else -- checked both directions, the header's signatures and # the function bodies, which contain no xcb_connection_t at all. So the # committed reply below is the real input, not a stand-in, and "picture-format # matching is arithmetic over a reply the caller already holds" is exactly # right. # # THE COMMITTED REPLY IS NOT TRUSTED EITHER. It is laid out by hand as the wire # reply, so before any lookup is scored the probe re-reads it through # xcb_render_query_pict_formats_{formats,screens}_iterator and reports the four # counts. construct-ok below is what stands between a malformed buffer and 40 # plausible-looking wrong answers. # # --------------------------------------------------------------------------- # THE MASK SWEEP IS OVER A DELIBERATELY MIXED TEMPLATE # # The ticket asks for _find_format "under each combination of the mask bits". # Twelve documented bits give 4096 combinations and the probe runs all of them. # The template matters more than the count: a template copied from one format # matches formats[0] in every masked field, so all 4096 return that same entry # and the digest is a hash of 4096 identical answers -- a library that ignored # `mask' entirely would produce a byte-identical digest. This template draws # each field from a DIFFERENT committed format, so distinct mask subsets select # distinct formats and contradictory subsets select none: 6 distinct formats # and 3932 nulls. The histogram is committed as well as the digest, because the # digest alone would hide a regression that merely reshuffled the answers. # # Two of the eight committed formats take ZERO hits in the sweep and that is # load-bearing, not padding: 0x24 (A1) is reached only by the standard-format # lookup, and 0x26 -- the deliberate ARGB32 twin -- only by the precedence # cases, which is the whole reason it exists. # # --------------------------------------------------------------------------- # THE TEXT STREAM IS READ THROUGH A MIRRORED PRIVATE STRUCT # # The ticket asks for the serialised bytes against a committed expectation, # "which is the wire contract". They are exactly that -- composite_text passes # this buffer and CURRENT_LEN straight to xcb_render_composite_glyphs_8 -- but # xcb_render_util_composite_text_stream_t is OPAQUE (defined in glyph.c, not in # the header) and no accessor returns the buffer. The probe mirrors the layout # and reads through it, so the bytes scored are the ones the LIBRARY wrote. # # ONLY BYTES THE LIBRARY ACTUALLY WROTE ARE SCORED. glyphs_8/16/32 copy exactly # `count' glyph units and then advance to the next 32-bit boundary WITHOUT # writing the bytes in between, so a full dump would include recycled heap -- # measured, a 5-glyph run showed 0x76 in a pad byte while an earlier run read 00 # from a fresh page. The probe therefore reports a `.defined' prefix and only # that is compared; the padding is asserted through `.len', which is what the # pad arithmetic decides and is deterministic. # # The cost of that is silent layout drift, and it is paid down rather than # accepted: stream-layout below re-derives five fields whose values the PUBLIC # api fixes -- glyph_size 0, both glyphsets equal to the argument, stream_len # 32, current == stream -- and NO byte comparison is scored unless all five # agree. A drifted mirror fails loudly at stream-ok instead of comparing # against garbage. The alternative -- a mock X11 handshake over # xcb_connect_to_fd() -- is named and declined in the bbappend: it would make # the probe rather than the library the subject. # # --------------------------------------------------------------------------- # EVERY EXPECTATION BELOW WAS MEASURED, NOT PREDICTED # # On 2026-09-07, by building this exact probe source as # xcb-util-renderutil-native (739 of 739 tasks, zero errors) and running it on # x86-64. The bbappend carries BBCLASSEXTEND = "native" so that run stays # reproducible from the shipped tree. The stream bytes carry across the two # arms because both are little-endian and the stream is built by memcpy of a # struct whose only multi-byte members are the int16 dx/dy; the widths that # decide the committed reply's layout are pinned below rather than assumed. # # --------------------------------------------------------------------------- # NOTHING IS ASSERTED ABOUT A SHARED OBJECT, IN EITHER DIRECTION # # xcb-util-renderutil is not in brintos.conf's BRINTOS_PACKAGE_SHARED_ALLOW, so # FILES:${PN} (${libdir}/lib*.so.*) matches nothing and the main package is # never emitted. Asserting the .so absent would go red the day it is # allow-listed, and asserting it present is false today. So neither is # asserted, bst_lib_shape carries the arm-independent half, and # libxcb-render-util.a is the artefact this suite is about. Same reading as # BRINTOS-2333/2334/2335/2337/2338. # # THE TICKET ASKS FOR `libxcb-render-util.so' BY NAME -- not shipped, for the # reason above; libxcb-render-util.a is, and is what is asserted. The # pkg-config file is right in number (there is one) and its name is # xcb-renderutil.pc. PROBE=${BST_XCB_RENDERUTIL_PROBE:-/usr/libexec/xcb-util-renderutil-tests/xcb-util-renderutil-probe} ARCHIVE=/usr/lib/libxcb-render-util.a HEADER=/usr/include/xcb/xcb_renderutil.h PC=/usr/lib/pkgconfig/xcb-renderutil.pc tmp=${TMPDIR:-/tmp} out=$tmp/bst-xcb-renderutil-probe.$$.out # The first BST result line of a suite is dropped by the guest console, so the # suite opens with one that costs nothing to lose. bst_ok xcb-util-renderutil/suite-start # --- installed surface ------------------------------------------------------ check xcb-util-renderutil/archive-present test -s "$ARCHIVE" bst_lib_shape xcb-util-renderutil/lib-shape "/usr/lib/libxcb-render-util.so*" check xcb-util-renderutil/header-present test -s "$HEADER" check xcb-util-renderutil/pc-present test -s "$PC" # The .pc must name this library, not merely exist -- an empty or # mis-generated one is exactly what a consumer's link would fail on. check_out xcb-util-renderutil/pc-names-lib "-lxcb-render-util" cat "$PC" # Requires: xcb-render -- this is the dependency that is easy to miss and that # the port's own first link failed on. The Render protocol bindings are a # SEPARATE library from libxcb, and a .pc that stopped saying so would let a # consumer's static link fail with undefined references naming util.c rather # than their own code. check_out xcb-util-renderutil/pc-requires-xcb-render "xcb-render" grep '^Requires' "$PC" check xcb-util-renderutil/probe-present test -x "$PROBE" # --- the probe runs --------------------------------------------------------- if ! "$PROBE" > "$out" 2>&1; then bst_notok xcb-util-renderutil/probe-runs "probe exited $? : $(head -c 200 "$out")" exit 0 fi if ! grep -q '^==XCBRU-PROBE-END==$' "$out"; then bst_notok xcb-util-renderutil/probe-runs "probe produced no end sentinel -- truncated run" exit 0 fi bst_ok xcb-util-renderutil/probe-runs field() { grep "^$1 " "$out" | head -1 | cut -d" " -f2-; } # --- the wire widths the committed reply is laid out against ---------------- # # build_reply() writes the reply byte by byte, so every offset in it depends on # these. A struct that changed size would leave the buffer malformed while the # iterators still walked something. check_eq xcb-util-renderutil/w-reply-header "32" "$(field sizeof.reply_header)" check_eq xcb-util-renderutil/w-pictforminfo "28" "$(field sizeof.pictforminfo)" check_eq xcb-util-renderutil/w-pictscreen "8" "$(field sizeof.pictscreen)" check_eq xcb-util-renderutil/w-pictdepth "8" "$(field sizeof.pictdepth)" check_eq xcb-util-renderutil/w-pictvisual "8" "$(field sizeof.pictvisual)" check_eq xcb-util-renderutil/w-glyphset "4" "$(field sizeof.glyphset)" # --- the committed reply reads back through libxcb's own iterators ---------- check_eq xcb-util-renderutil/construct-formats "8" "$(field construct.formats)" check_eq xcb-util-renderutil/construct-screens "2" "$(field construct.screens)" check_eq xcb-util-renderutil/construct-depths "3" "$(field construct.depths)" check_eq xcb-util-renderutil/construct-visuals "4" "$(field construct.visuals)" check_eq xcb-util-renderutil/construct-ok "1" "$(field construct.ok)" # --- the format tables, compared entry by entry ----------------------------- # # compare_table walks the committed copy on fd 3 against the probe's emission # on fd 4 and reports the FIRST divergence with its index. A whole-table # check_eq would truncate its red at 100 characters, and these lines are ~95, # so a divergence in the last field would show an identical prefix and tell the # reader nothing. # # Both operands are named as "$tmp/..." rather than through the function's own # parameters, so check-suite-redirects.py can resolve them: it follows # `tmp=${TMPDIR:-/tmp}' above and sees a tmpfs source, where an unresolvable # $_exp would be treated as the virtiofs root and refused. Same construction, # and the same reason, as 57-xcb-util.test and 57-xcb-util-keysyms.test. compare_table() { _id=$1; _tag=$2 _n=0; _bad= exec 3< "$tmp/bst-xcbru-$_tag.expected" exec 4< "$tmp/bst-xcbru-$_tag.got" while IFS= read -r _want <&3; do _n=$((_n + 1)) if IFS= read -r _have <&4; then if [ "$_want" != "$_have" ]; then _bad="entry $_n: want '$_want' got '$_have'"; break fi else _bad="ended at entry $((_n - 1)) -- TRUNCATED, the committed table has more" break fi done if [ -z "$_bad" ] && IFS= read -r _have <&4; then _bad="extra entry past the committed end: '$_have'" fi exec 3<&- 4<&- if [ -n "$_bad" ]; then bst_notok "$_id" "$_bad"; else bst_ok "$_id"; fi } # THE FIVE STANDARD FORMATS, every field of each. # # This is the ticket's central clause: "for each of the standard formats -- # ARGB32, RGB24, A8, A4, A1 -- against a committed formats reply, comparing # depth, type, and every red, green, blue and alpha shift and mask field by # field". Each line below is exactly that comparison. The values are util.c's # own standardFormats[] templates resolved against the committed table, so a # change to either side moves a line. # # The two guards after them are documented behaviour of the same function: # find_standard_format range-checks `format' against its table # (`if (format < 0 || format >= sizeof(standardFormats)/sizeof(*standardFormats)) # return 0'), and it inherits find_format's null-reply guard. cat > "$tmp/bst-xcbru-std.expected" <<'EOF' std.argb32 id=0x20 type=1 depth=32 rs=16 rm=0xff gs=8 gm=0xff bs=0 bm=0xff as=24 am=0xff cmap=0x0 std.rgb24 id=0x21 type=1 depth=24 rs=16 rm=0xff gs=8 gm=0xff bs=0 bm=0xff as=0 am=0x0 cmap=0x0 std.a8 id=0x22 type=1 depth=8 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0xff cmap=0x0 std.a4 id=0x23 type=1 depth=4 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0xf cmap=0x0 std.a1 id=0x24 type=1 depth=1 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0x1 cmap=0x0 std.oob5 NULL std.null-reply NULL EOF grep '^std\.' "$out" > "$tmp/bst-xcbru-std.got" compare_table xcb-util-renderutil/standard-formats std # THE VISUAL LOOKUP, including the documented null. # # 0x1a1 is the first visual of the first depth of the first screen; 0x1b2 is # the SECOND visual of a depth, so a lookup that stopped at the first fails # there; 0x1c1 lives on the SECOND SCREEN, so a lookup that never advanced the # screens iterator fails there. 0x999 is absent -- the documented null the # ticket asks for -- and the null-reply case is util.c's `if (!formats)'. cat > "$tmp/bst-xcbru-visual.expected" <<'EOF' visual.0x1a1 found visual.0x1a1.format 0x20 visual.0x1b2 found visual.0x1b2.format 0x21 visual.0x1c1 found visual.0x1c1.format 0x25 visual.absent NULL visual.null-reply NULL EOF grep '^visual\.' "$out" > "$tmp/bst-xcbru-visual.got" compare_table xcb-util-renderutil/visual-formats visual # --- the 4096-combination mask sweep ---------------------------------------- check_eq xcb-util-renderutil/sweep-total "4096" "$(field findfmt.sweep.total)" check_eq xcb-util-renderutil/sweep-null "3932" "$(field findfmt.sweep.null)" # Six of the eight committed formats are selected by some subset of the mask # bits. This is the case that fails first if `mask' stops discriminating: a # library that ignored it would report 1 here, and the digest below would # change with it. check_eq xcb-util-renderutil/sweep-distinct "6" "$(field findfmt.sweep.distinct)" check_eq xcb-util-renderutil/sweep-fnv "fc25dac5" "$(field findfmt.sweep.fnv)" # The histogram, committed in full. 0x20 wins exactly the 2^6 = 64 subsets of # the six rgb-triple bits it matches the template on; 0x21 wins the 64 that add # ALPHA, which it matches and 0x20 does not. The two zeros are load-bearing -- # see the header. cat > "$tmp/bst-xcbru-hist.expected" <<'EOF' findfmt.sweep.hits.0x20 64 findfmt.sweep.hits.0x21 64 findfmt.sweep.hits.0x22 4 findfmt.sweep.hits.0x23 4 findfmt.sweep.hits.0x24 0 findfmt.sweep.hits.0x25 24 findfmt.sweep.hits.0x26 0 findfmt.sweep.hits.0x27 4 EOF grep '^findfmt\.sweep\.hits\.' "$out" > "$tmp/bst-xcbru-hist.got" compare_table xcb-util-renderutil/sweep-histogram hist # NAMED COMBINATIONS, so a sweep failure can be localised to a bit. # # direct-depth8 and indexed-depth8 are the pair that proves the TYPE bit does # something: both name depth 8, and only the type distinguishes 0x22 from 0x25. # nomatch is a depth no format carries; null-reply is the `if (!formats)' guard. cat > "$tmp/bst-xcbru-find.expected" <<'EOF' find.depth24 id=0x21 type=1 depth=24 rs=16 rm=0xff gs=8 gm=0xff bs=0 bm=0xff as=0 am=0x0 cmap=0x0 find.direct-depth8 id=0x22 type=1 depth=8 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0xff cmap=0x0 find.indexed-depth8 id=0x25 type=0 depth=8 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0x0 cmap=0x30 find.byid-a4 id=0x23 type=1 depth=4 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0xf cmap=0x0 find.bycolormap id=0x25 type=0 depth=8 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0x0 cmap=0x30 find.nomatch NULL find.null-reply NULL EOF grep '^find\.' "$out" > "$tmp/bst-xcbru-find.got" compare_table xcb-util-renderutil/find-named find # --- the count parameter: the documented precedence ------------------------- # # The ticket asks for "the documented precedence when more than one format # matches". find_format's `if (count-- == 0) return i.data' walks matches in # table order and returns the count'th, so with mask=0 every format matches and # count indexes the table directly -- including one past the end, which is the # null. The prec.* cases are the real precedence: the ARGB32 mask matches TWO # formats, 0x20 and the deliberate twin 0x26, and nothing else in the table. cat > "$tmp/bst-xcbru-count.expected" <<'EOF' count.nomask.0 id=0x20 type=1 depth=32 rs=16 rm=0xff gs=8 gm=0xff bs=0 bm=0xff as=24 am=0xff cmap=0x0 count.nomask.3 id=0x23 type=1 depth=4 rs=0 rm=0x0 gs=0 gm=0x0 bs=0 bm=0x0 as=0 am=0xf cmap=0x0 count.nomask.7 id=0x27 type=1 depth=16 rs=11 rm=0x1f gs=5 gm=0x3f bs=0 bm=0x1f as=0 am=0x0 cmap=0x0 count.nomask.8 NULL prec.argb32.0 id=0x20 type=1 depth=32 rs=16 rm=0xff gs=8 gm=0xff bs=0 bm=0xff as=24 am=0xff cmap=0x0 prec.argb32.1 id=0x26 type=1 depth=32 rs=16 rm=0xff gs=8 gm=0xff bs=0 bm=0xff as=24 am=0xff cmap=0x0 prec.argb32.2 NULL EOF grep -E '^(count|prec)\.' "$out" > "$tmp/bst-xcbru-count.got" compare_table xcb-util-renderutil/find-precedence count # --- the text stream, byte for byte ----------------------------------------- # # THE LAYOUT GUARD COMES FIRST AND GATES EVERYTHING BELOW IT. The five fields # are all fixed by the public api immediately after # composite_text_stream(gs,0,0): glyph_size is 0 until the first glyphs_* call # picks a width, both glyphsets are the argument, the default allocation is 32 # bytes, and the write pointer starts at the base. If the mirrored struct has # drifted from glyph.c's, at least one of these reads wrong, and the byte # comparisons after it would be scoring garbage. check_eq xcb-util-renderutil/stream-layout "ok" "$(field stream.layout)" check_eq xcb-util-renderutil/stream-layout-size "0" "$(field stream.layout.glyph_size)" check_eq xcb-util-renderutil/stream-layout-init "0xabcd1234" "$(field stream.layout.initial)" check_eq xcb-util-renderutil/stream-layout-cur "0xabcd1234" "$(field stream.layout.current_gs)" check_eq xcb-util-renderutil/stream-layout-len "32" "$(field stream.layout.len0)" check_eq xcb-util-renderutil/stream-layout-empty "1" "$(field stream.layout.empty)" check_eq xcb-util-renderutil/stream-ok "1" "$(field stream.ok)" # THE WIRE CONTRACT. Each header is _glyph_header_t -- one count byte, three # pad, then int16 dx and dy little-endian -- followed by the glyph payload # padded up to the next 32-bit boundary. These are the bytes # xcb_render_util_composite_text hands to xcb_render_composite_glyphs_8. # # g8 04 000000 0300 fbff | 41424344 # count 4, dx 3, dy -5 (0xfffb), then 'A' 'B' 'C' 'D' # +cg ff 000000 0000 0000 | 00002222 # the glyphset-change header is count 255 with dx = dy = 0, then the # new glyphset 0x22220000 little-endian # +g8 03 000000 0000 0000 | 616263 00 # three glyphs padded with one byte to the 32-bit boundary check_eq xcb-util-renderutil/stream-g8-len "12" "$(field stream.g8.len)" check_eq xcb-util-renderutil/stream-g8-defined "12" "$(field stream.g8.defined)" check_eq xcb-util-renderutil/stream-g8-bytes \ "040000000300fbff41424344" "$(field stream.g8.bytes)" check_eq xcb-util-renderutil/stream-g8-size "1" "$(field stream.g8.size)" check_eq xcb-util-renderutil/stream-cg-len "24" "$(field 'stream.g8+cg.len')" check_eq xcb-util-renderutil/stream-cg-bytes \ "040000000300fbff41424344ff0000000000000000002222" "$(field 'stream.g8+cg.bytes')" check_eq xcb-util-renderutil/stream-cg-current \ "0x22220000" "$(field 'stream.g8+cg.current')" # change_glyphset to the glyphset already current is documented as a no-op # (`if (glyphset == stream->current_glyphset) return'), so the length is # unchanged from the line above. check_eq xcb-util-renderutil/stream-cg-same-noop "24" "$(field stream.cg-same.len)" check_eq xcb-util-renderutil/stream-g8b-len "36" "$(field 'stream.g8+cg+g8.len')" check_eq xcb-util-renderutil/stream-g8b-defined "35" "$(field 'stream.g8+cg+g8.defined')" check_eq xcb-util-renderutil/stream-g8b-bytes \ "040000000300fbff41424344ff00000000000000000022220300000000000000616263" \ "$(field 'stream.g8+cg+g8.bytes')" # A RUN WHOSE LENGTH IS 1 MOD 4 -- the case mutation testing forced into # existence. glyphs_8 advances by ((count+3)>>2) words, and at counts 4 and 3 # above that is indistinguishable from the plausible off-by-one ((count+2)>>2): # both give the same answer, so the padding arithmetic was not pinned by any # case here. 1 mod 4 is the only residue that separates them. 5 glyphs => an # 8-byte header + 5 bytes + 3 pad = 16, and it is the LENGTH that carries the # claim, since the 3 pad bytes are never written by the library. check_eq xcb-util-renderutil/stream-g8x5-len "16" "$(field stream.g8x5.len)" check_eq xcb-util-renderutil/stream-g8x5-defined "13" "$(field stream.g8x5.defined)" check_eq xcb-util-renderutil/stream-g8x5-bytes \ "05000000010001005152535455" "$(field stream.g8x5.bytes)" # Once a stream has a glyph width, a call at a different width is refused # outright -- the length is unchanged from the line above. check_eq xcb-util-renderutil/stream-mixed-refused "36" "$(field stream.mixed-refused.len)" # 16- and 32-bit glyphs in streams of their own. glyphs_16 pads to the 32-bit # boundary; glyphs_32 needs no padding, which is why its length is 16 and not # 12 for the same glyph count. check_eq xcb-util-renderutil/stream-g16-len "12" "$(field stream.g16.len)" check_eq xcb-util-renderutil/stream-g16-bytes \ "02000000ffff020034127856" "$(field stream.g16.bytes)" check_eq xcb-util-renderutil/stream-g16-size "2" "$(field stream.g16.size)" check_eq xcb-util-renderutil/stream-g32-len "16" "$(field stream.g32.len)" check_eq xcb-util-renderutil/stream-g32-bytes \ "0200000007000900efbeadde04030201" "$(field stream.g32.bytes)" check_eq xcb-util-renderutil/stream-g32-size "4" "$(field stream.g32.size)" # The documented count ceilings. glyphs_8 refuses above 252 and the wider two # above 254, and a refused call leaves the stream completely untouched -- # glyph_size is still 0, so the stream has not even been committed to a width. check_eq xcb-util-renderutil/stream-ceil8-len "0" "$(field stream.ceil8.len)" check_eq xcb-util-renderutil/stream-ceil8-size "0" "$(field stream.ceil8.size)" check_eq xcb-util-renderutil/stream-ceil16-len "0" "$(field stream.ceil16.len)" # A sized stream allocates (total_glyphs + total_glyphset_changes) * 3 * 4 # bytes up front instead of the 32-byte default: 4*12 + 1*12 = 60. check_eq xcb-util-renderutil/stream-sized "60" "$(field stream.sized.len0)" # --- the five connection-bearing entry points are in the archive ------------ # # They are referenced rather than called -- see the header. The link is the # real proof; these lines assert the probe still holds all five, so a future # edit that dropped one would show here rather than silently shrinking the ABI # the suite covers. The bbappend pins the same floor at 14 from the source # side, and llvm-nm measured the archive at exactly 14 globals. check_eq xcb-util-renderutil/linked-query-version "1" "$(field linked.0)" check_eq xcb-util-renderutil/linked-query-formats "1" "$(field linked.1)" check_eq xcb-util-renderutil/linked-disconnect "1" "$(field linked.2)" check_eq xcb-util-renderutil/linked-composite-text "1" "$(field linked.3)" check_eq xcb-util-renderutil/linked-composite-chk "1" "$(field linked.4)" rm -f "$out" "$tmp"/bst-xcbru-*.expected "$tmp"/bst-xcbru-*.got