. "$BST_LIB" # GNU libunistring 1.2 -- Unicode string manipulation for UTF-8/16/32. The # distro is static-only, so the payload is 21 headers (-dev) plus libunistring.a # (-staticdev), and nothing on this image links it yet. # # THIS SUITE IS NOT PAYLOAD-ONLY, for the reason 76-onig.test established: a # library nobody links has a perfect payload whether or not it works. Every # static assertion below is equally true of an archive whose Unicode tables were # never consulted, so the evidence that matters is the two probes -- # cross-linked with wasmcc-glibc against THIS libunistring.a and driven below, # with every expected value taken from a native x86_64 build of the identical # 1.2 source. # # THERE ARE TWO PROBES, AND THE SPLIT IS A FINDING RATHER THAN A TIDY-UP: # # data/unistring-probe 24 cases -- the table-driven halves (decoding, # classification, case, normalisation, width, the # name database, the three break algorithms). All # green on target; its tally is UNI-SUMMARY 24/24. # data/unistring-printf-probe 8 cases -- the unistdio (u*_asnprintf) half, # which is where the platform defect lives. It # ABORTS on target partway through, so only its # first four cases are observable; see the block # at the end of this file. # # They are separate binaries because u16_asnprintf("%s") is miscompiled on # wasm32-hwjs (brintos/hwjs-cc#27): it returns an EMPTY result for short inputs # and aborts with `free(): invalid size` for longer ones. While the unistdio # cases lived in the main probe that abort destroyed the summary line and every # case after it, which is exactly how a real defect in one corner can be made to # look like a broken port everywhere. Split, the blast radius is the unistdio # cases alone and the other 24 still report. # # Four things here would be got wrong by copying a neighbouring suite, all # measured rather than assumed: # # 1. THERE IS NO .pc FILE AND NO onig-config-STYLE STUB. libunistring ships # neither, so the pkg-config assertions that 76-onig.test and 76-cmark.test # spend a dozen cases on have nothing to assert here. Their ABSENCE is # asserted instead, so that a future recipe change adding one is noticed # rather than silently unasserted. # 2. THE MAIN libunistring PACKAGE IS EMPTY AND IS NOT IN THE IMAGE. Only # -dev and -staticdev are installed (two manifest rows, the libmxml/onig # shape), so there is no /usr/lib/libunistring.so and no binary to require. # `require` is therefore driven off the ARCHIVE, not off a tool. # 3. THE c32 PATCH IS A BUILD-TIME FIX AND HAS NO RUNTIME TRIPWIRE. Unlike # onig's mixed-* cases, nothing this suite can run distinguishes a patched # from an unpatched libunistring: unpatched, do_compile fails and no image # exists to test. That is stated rather than faked with a case that looks # like a guard and is not. # 4. FULL CASE MAPPING CHANGES LENGTH. u8_toupper("straße") is "STRASSE" -- # seven characters from six, because U+00DF maps to two. A suite asserting # a length-preserving result would fail against a CORRECT port, and one # asserting only rc=0 would pass against a library that returned the input # untouched. root=${BST_ROOT:-} inc=$root/usr/include lib=$root/usr/lib/libunistring.a # ------------------------------------------------------------------ payload -- # The archive is the package: with no tool and no .so, its absence is what a # `require` must key on. # bst.sh's `require` keys on a TOOL in $PATH and this package installs none, so # the guard is spelled out here against the archive instead. It ends the file # with `exit 0`, exactly as `require` does -- the runner gives each test its own # shell, so `return` would not end anything. if [ -f "$lib" ]; then bst_ok libunistring/archive-present else bst_skip libunistring/archive-present "no $lib — libunistring not installed" exit 0 fi check libunistring/archive-nonempty test -s "$lib" # The thirteen top-level headers, one case each: a partial -dev install is a # real and quiet failure mode, and "some headers are there" would not catch it. for h in unicase uniconv unictype unigbrk unilbrk unimetadata uniname \ uninorm unistdio unistr unitypes uniwbrk uniwidth; do check libunistring/hdr-$h test -f "$inc/$h.h" done # ...and the eight private ones under unistring/, which unistr.h includes and a # consumer never names directly -- so they are the ones most easily dropped. for h in cdefs iconveh inline localcharset stdbool stdint version woe32dll; do check libunistring/hdr-unistring-$h test -f "$inc/unistring/$h.h" done # The version macro, by value. This is the one static fact the probe also # reports at RUNTIME (UNI-version below compares the linked-in variable against # this macro), so the pair catches a header/archive version skew. check_eq libunistring/version-macro 0x010200 \ "$(awk '/define[ \t]+_LIBUNISTRING_VERSION/{print $3}' "$inc/unistring/version.h")" check_out libunistring/hdr-guard "#define _UNISTRING_VERSION_H" cat "$inc/unistring/version.h" # Note 1: neither exists, and asserting so is what makes a future addition # visible rather than silently uncovered. check libunistring/no-pkgconfig test ! -f "$root/usr/lib/pkgconfig/libunistring.pc" check libunistring/no-shared test ! -f "$root/usr/lib/libunistring.so" # ------------------------------------------------------------------ runtime -- # data/unistring-probe is cross-linked against the shipped libunistring.a. Its # link is itself evidence: the coroutinize pass reported `refused=0` over 425 # coroutinized functions, including the seven vasnprintf instantiations whose # dynamic alloca was expected to be refused and was not (see the bbappend). # # Every expectation below is a literal line of native output. They are NOT # relaxed to match target behaviour -- a divergence here is a platform finding # and must go red, which is the whole reason the control was built first. probe=${TMPDIR:-/tmp}/bst-unistring-probe.$$ if cp "$BST_DATA/unistring-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then out=$("$probe" 2>&1) # The linked-in variable agrees with the header the -dev package shipped. check_out libunistring/run-version \ "UNI-version OK [runtime=0x010200 header=0x010200]" printf '%s\n' "$out" # unistr: validation both ways, then decode. The stride is asserted with the # code point because a decoder that returns the right character and the wrong # length desynchronises the caller on the NEXT character, not this one. check_out libunistring/run-check-good \ "UNI-u8-check-good OK [r=null]" printf '%s\n' "$out" check_out libunistring/run-check-bad \ "UNI-u8-check-bad OK [off=1]" printf '%s\n' "$out" check_out libunistring/run-mbtouc \ "UNI-u8-mbtouc OK [U+0041/1 U+00E9/2 U+65E5/3 U+1F600/4]" printf '%s\n' "$out" # Units vs characters -- the distinction the library exists for. check_out libunistring/run-len \ "UNI-u8-len OK [units=10 chars=4]" printf '%s\n' "$out" # The astral character is what separates the three encodings: 4 units in # UTF-8, 1 in UTF-32, a surrogate PAIR in UTF-16. n16=5 is the assertion a # fixed-width UTF-16 converter fails. check_out libunistring/run-roundtrip \ "UNI-u8-u32-u16-roundtrip OK [n32=4 n16=5 back=10]" printf '%s\n' "$out" # unictype: classification, the general-category NAME, a binary property and a # script lookup -- four different compiled tables. check_out libunistring/run-class \ "UNI-uc-class OK [alpha(e9)=1 alpha(3)=0 digit(3)=1]" printf '%s\n' "$out" check_out libunistring/run-category \ "UNI-uc-category OK [e9=Ll 41=Lu 20=Zs]" printf '%s\n' "$out" check_out libunistring/run-property \ "UNI-uc-property OK [ws=1 idstart=1 idstart(sp)=0]" printf '%s\n' "$out" check_out libunistring/run-script \ "UNI-uc-script OK [script(03B1)=Greek]" printf '%s\n' "$out" # unicase: the simple per-character mapping, then Note 4 -- the full mapping # that CHANGES LENGTH, which is the half a per-character table gets wrong. check_out libunistring/run-case-simple \ "UNI-uc-case-simple OK [toupper(E9)=U+00C9 tolower(C9)=U+00E9]" printf '%s\n' "$out" check_out libunistring/run-toupper-full \ "UNI-u8-toupper-full OK [n=7 hex=53 54 52 41 53 53 45]" printf '%s\n' "$out" check_out libunistring/run-casecmp \ "UNI-u8-casecmp OK [rc=0 cmp=0]" printf '%s\n' "$out" # uninorm: both directions, because a normaliser that is the identity function # passes either one alone; then canonical equivalence, which is the operation # that makes normalisation worth shipping (raw_differs=1 proves the two inputs # really are distinct byte strings); then a compatibility fold. check_out libunistring/run-nfd "UNI-u8-nfd OK [n=3 hex=65 CC 81]" printf '%s\n' "$out" check_out libunistring/run-nfc "UNI-u8-nfc OK [n=2 hex=C3 A9]" printf '%s\n' "$out" check_out libunistring/run-normcmp \ "UNI-u8-normcmp OK [rc=0 cmp=0 raw_differs=1]" printf '%s\n' "$out" check_out libunistring/run-nfkc "UNI-u8-nfkc OK [n=2 hex=66 69]" printf '%s\n' "$out" # uniwidth: wide and zero-width, not just ASCII. A width function returning 1 # for everything is right about 'A' and wrong about its reason to exist. check_out libunistring/run-width \ "UNI-uc-width OK [A=1 CJK=2 comb=0]" printf '%s\n' "$out" check_out libunistring/run-strwidth "UNI-u8-strwidth OK [w=4]" printf '%s\n' "$out" # uniname: the largest table in the library, both directions. check_out libunistring/run-uniname-fwd \ "UNI-uniname-forward OK [name=LATIN CAPITAL LETTER A]" printf '%s\n' "$out" check_out libunistring/run-uniname-rev \ "UNI-uniname-reverse OK [cp=U+03B1]" printf '%s\n' "$out" # The three break algorithms -- separate tables and separate state machines. check_out libunistring/run-linebreaks \ "UNI-u8-linebreaks OK [lb=11121]" printf '%s\n' "$out" check_out libunistring/run-wordbreaks \ "UNI-u8-wordbreaks OK [wb=00110]" printf '%s\n' "$out" # base + combining mark is ONE cluster of 3 bytes: the case a byte- or even # character-oriented cursor gets wrong. check_out libunistring/run-grapheme \ "UNI-u8-grapheme OK [adv=3]" printf '%s\n' "$out" # The probe's own tally and exit status. 24/24 is what the identical source # scores against a native x86_64 build of the identical release. The unistdio # cases are NOT here -- they are a separate binary, driven below. check_out libunistring/run-summary "UNI-SUMMARY 24/24" printf '%s\n' "$out" check_rc libunistring/run-exit0 0 "$probe" rm -f "$probe" else bst_notok libunistring/run-stage "cannot stage $BST_DATA/unistring-probe" fi # ----------------------------------------------------------------- unistdio -- # data/unistring-printf-probe drives the u*_asnprintf half against the same # shipped archive. It runs cheapest-risk first -- the narrow width, then the # integer directives, then %s at each output width, which is where the defect # is -- so the most is learned before the abort. # # ON TARGET IT ABORTS at the first %s-at-u16 case with `free(): invalid size` # (brintos/hwjs-cc#27), so exactly four of its eight cases are observable. Those # four are asserted BY VALUE below; the abort itself is asserted as its own # case, and is left FAILING rather than skipped so it flips green on its own # when the platform fix lands -- which is the regression signal this suite is # buying. The three cases the abort masks (u16-s-long, u8-s, u32-s) are named in # that failure rather than asserted separately: they are one defect, and # spending three more reds on it would overstate a single platform bug. # # Measured against a native x86_64 build of the identical source, where all # eight pass and the tally reads UNIP-SUMMARY 8/8. pprobe=${TMPDIR:-/tmp}/bst-unistring-printf.$$ if cp "$BST_DATA/unistring-printf-probe" "$pprobe" 2>/dev/null && chmod +x "$pprobe" 2>/dev/null; then pout=$("$pprobe" 2>&1); prc=$? # %U is libunistring's own directive for a UTF-8 argument. Asserted by VALUE, # so an accepted-but-miscompiled lowering is caught as well as a refused one. check_out libunistring/run-p-u8-asnprintf \ "UNIP-u8-asnprintf OK [n=16 hex=78 3D 34 32 20 73 3D C3 A9 E6 97 A5 20 65 6E 64]" \ printf '%s\n' "$pout" # A width/precision directive: the vasnprintf buffer size is computed from # max_width_length and max_precision_length, so a constant-size case would not # exercise that path at all. check_out libunistring/run-p-u8-asnprintf-width \ "UNIP-u8-asnprintf-width OK [n=14 out=[ 3.1416]]" printf '%s\n' "$pout" # %d at u16 and u32 -- the controls that make the failure below specific to # the %s directive rather than to the u16 instantiation as a whole. check_out libunistring/run-p-u16-asnprintf-int \ "UNIP-u16-asnprintf-int OK [n=3]" printf '%s\n' "$pout" check_out libunistring/run-p-u32-asnprintf-int \ "UNIP-u32-asnprintf-int OK [n=3]" printf '%s\n' "$pout" # THE PLATFORM DEFECT. Left red on purpose -- see the block comment above. case "$pout" in *"UNIP-u16-asnprintf-s OK"*) bst_ok libunistring/run-p-u16-s ;; *) bst_notok libunistring/run-p-u16-s "u16_asnprintf with a %s directive is miscompiled on wasm32-hwjs (brintos/hwjs-cc#27): empty result (n=0) for short inputs, 'free(): invalid size' abort for longer ones -- probe rc=$prc. The same directive at u8 and u32 width, and %d at u16 width, are correct. The abort also masks u16-s-long, u8-s and u32-s." ;; esac # The probe's own tally, which the abort prevents it from ever printing. case "$pout" in *"UNIP-SUMMARY 8/8"*) bst_ok libunistring/run-p-summary ;; *) bst_notok libunistring/run-p-summary "no 'UNIP-SUMMARY 8/8' -- the probe aborts before its tally (brintos/hwjs-cc#27)" ;; esac rm -f "$pprobe" else bst_notok libunistring/run-p-stage "cannot stage $BST_DATA/unistring-printf-probe" fi