. "$BST_LIB" # BRINTOS-WORKAROUND: brintos/linux-in-the-browser#138 revisit -- the FIRST line a # suite writes after the runner's ==BST-BEGIN== can be swallowed when a kernel # printk lands on the console in between, which cost 79-libevdev a scored case # that vanished from the transcript while the runner's cases= count still # included it. The failure direction is the dangerous one: a harness counting # result lines reads N of N green and is wrong about COVERAGE. This absorbs the # loss with a line nobody scores. Drop it once #138 lands. printf '#diag: libbytesize suite start (this line is a console-loss absorber, not a case)\n' # libbytesize 2.11 (meta-oe recipes-support/libbytesize) -- the small library # for parsing and formatting sizes in bytes. What ships here is # /usr/lib/libbytesize.a, /usr/include/bytesize/bs_size.h, the pkg-config file, # and the probe this suite scores. Once the python3 bindings are cleared the # package installs NO program at all, which is why the probe exists. # # --------------------------------------------------------------------------- # NOTHING HERE NEEDS THE KERNEL, AND THAT IS WHAT MAKES THE PORT SCORABLE IN # FULL RATHER THAN AROUND A GAP. # # libbytesize is arbitrary-precision arithmetic (GMP mpz/mpf, MPFR) behind a # PCRE2 regex. It opens no file, takes no socket and issues no syscall beyond # malloc, stdio and one nl_langinfo. So unlike the deviceless ports in this # Epic there is no group held by a platform defect: every value below is one # this arm must reproduce, and any red is a real defect somewhere. # # * GROUP A -- PARSING. bs_size_new_from_str splices nl_langinfo(RADIXCHAR) # into a PCRE2 pattern, matches, hands the numeric part to GMP's # mpf_set_str and the unit to a strncasecmp walk of the binary and then the # decimal unit table. Three libraries have to be linked and working before # one of these lines can be right. Every binary and every decimal unit is # covered, plus the numeric shapes the pattern must accept. # * GROUP B -- FORMATTING. bs_size_human_readable and bs_size_convert_to are # the MPFR/GMP float path: gmp_asprintf("%.*Ff"), a radix-char # substitution, and a trailing-zero strip that walks the string backwards. # * GROUP C -- ARITHMETIC. add / sub / mul_int / div / mod / cmp / round, # exact through mpz, which is the whole reason the package exists. # * GROUP D -- THE 64-BIT BOUNDARY ON A 32-BIT TARGET. The group that would # otherwise fail silently; read the note above D20 before changing a value # here. # * GROUP E -- THE ERROR CONTRACT. A parser that accepted garbage would be # worse than one that crashed. # * GROUP F -- the word size and radix character the rest rests on. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86-64 RUN OF THE SAME PROBE AT THE SAME VERSION. # # Every value pinned below is what a native x86-64 build of # libbytesize_probe.c against libbytesize 2.11 printed (2026-08-18), built # against GMP 6.3.0 and MPFR 4.2.1 -- the same versions this image carries. # # The values that legitimately DIFFER from that oracle are F01, F02, F05 and # F06 (word size, and the two constants that truncate to it), plus the two # division cases D21 and D22 that follow from them. They are pinned to what # this arm does, and the reason is written beside each rather than left for a # reader to rediscover. PROBE=${BST_LIBBYTESIZE_PROBE:-/usr/libexec/libbytesize-tests/libbytesize-probe} ARCHIVE=/usr/lib/libbytesize.a HDR=/usr/include/bytesize/bs_size.h PC=/usr/lib/pkgconfig/bytesize.pc if [ ! -f "$ARCHIVE" ]; then bst_skip libbytesize/present "libbytesize not installed on this image" exit 0 fi bst_ok libbytesize/present # --- the package is whole ----------------------------------------------------- check libbytesize/staticlib test -s "$ARCHIVE" check libbytesize/header test -s "$HDR" check libbytesize/pkgconfig test -s "$PC" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libbytesize/no-shared "/usr/lib/libbytesize.so" "/usr/lib/libbytesize.so.1" # The python3 bindings are off, and their absence is the reason this image can # carry the package at all: with them the package RDEPENDs on python3-core. if [ -d /usr/lib/python3.12/site-packages/bytesize ] || [ -d /usr/lib/python3/site-packages/bytesize ]; then bst_notok libbytesize/no-python "the python3 bindings are installed -- PACKAGECONFIG was not cleared, and the image now depends on python3-core" else bst_ok libbytesize/no-python fi # The header must carry the API rather than merely exist. check_out libbytesize/hdr-parse "bs_size_new_from_str" cat "$HDR" check_out libbytesize/hdr-human "bs_size_human_readable" cat "$HDR" check_out libbytesize/hdr-convert "bs_size_convert_to" cat "$HDR" check_out libbytesize/hdr-arith "bs_size_mul_int" cat "$HDR" check_out libbytesize/hdr-bunit "BS_BUNIT_KiB" cat "$HDR" check_out libbytesize/hdr-dunit "BS_DUNIT_KB" cat "$HDR" # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes. -a where the grep supports it -- # on this arm the archive members are LLVM IR bitcode, so a grep that suppresses # matches in a file containing NULs reports nothing; GNU grep needs -a, busybox # grep as shipped does not. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in bs_size_new_from_str bs_size_human_readable bs_size_convert_to \ bs_size_add bs_size_mul_int bs_size_div bs_size_get_bytes_str \ bs_size_round_to_nearest bs_size_true_div bs_clear_error; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libbytesize/sym-$sym" else bst_notok "libbytesize/sym-$sym" "$sym absent from $ARCHIVE" fi done # --- the probe ---------------------------------------------------------------- if [ ! -x "$PROBE" ]; then bst_skip libbytesize/probe-runs "$PROBE absent from the image" exit 0 fi pout=${TMPDIR:-/tmp}/bst-libbytesize-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libbytesize/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libbytesize/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" else bst_notok libbytesize/probe-runs "rc=$prc $(head -c 200 "$pout" 2>/dev/null)" fi if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libbytesize/probe-complete else bst_notok libbytesize/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # val -- the probe's value for one case, empty when the probe stopped # first. Matched on the ID in field 1 rather than taken by position, since the # probe's output is captured with 2>&1. val() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$pout"; } # want -- score one probe value against a pinned answer. # A missing value is reported as the probe having stopped early rather than as a # mismatch against '', which is a different fault and localises to a different # line. want() { _got=$(val "$1") if [ -z "$_got" ]; then bst_notok "libbytesize/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libbytesize/$2" else bst_notok "libbytesize/$2" "want:'$3' got:'$_got'" fi } # --- A: parsing --------------------------------------------------------------- # The five the ticket names, first: every unit class and a negative size. want A01 parse-1-KiB 1024 want A02 parse-1-kB 1000 want A03 parse-10-MiB 10485760 want A04 parse-1.5-GB 1500000000 want A05 parse-neg-2-GiB -2147483648 # Every binary unit. A13/A14 exceed a uint64_t and are why the value is carried # as an mpz and read back as a decimal string rather than a number. want A06 parse-unit-B 1 want A07 parse-unit-KiB 1024 want A08 parse-unit-MiB 1048576 want A09 parse-unit-GiB 1073741824 want A10 parse-unit-TiB 1099511627776 want A11 parse-unit-PiB 1125899906842624 want A12 parse-unit-EiB 1152921504606846976 want A13 parse-unit-ZiB 1180591620717411303424 want A14 parse-unit-YiB 1208925819614629174706176 # Every decimal unit. These take a different arm of multiply_size_by_unit -- # mpfr_pow_ui on 1000 rather than mpfr_mul_2exp -- so a green binary column # says nothing about this one. want A15 parse-unit-KB 1000 want A16 parse-unit-MB 1000000 want A17 parse-unit-GB 1000000000 want A18 parse-unit-TB 1000000000000 want A19 parse-unit-PB 1000000000000000 want A20 parse-unit-EB 1000000000000000000 want A21 parse-unit-ZB 1000000000000000000000 want A22 parse-unit-YB 1000000000000000000000000 # The shapes the PCRE2 pattern has to accept. want A23 parse-no-unit 1024 want A24 parse-fraction 1536 want A25 parse-sub-one 524288 want A26 parse-plus-sign 3221225472 want A27 parse-exponent 2000 want A28 parse-exponent-upper 2000 want A29 parse-exponent-neg 1 want A30 parse-whitespace 7340032 want A31 parse-case-insens 1048576 want A32 parse-unit-prefix 1048576 want A33 parse-negative-zero 0 # A34 IS THE CASE THAT SAYS MPFR IS LINKED AND DOING THE UNIT MULTIPLY. # libbytesize parses with GMP and multiplies with MPFR for the reason its own # source gives -- "GMP thinks 0.1*1000 = 99" -- so this one spec separates the # two: 100 bytes with MPFR, 99 without. Both answers were reproduced on x86-64 # before this was pinned, so it is known to discriminate rather than merely to # pass. A35 is the same rounding on the binary side (102.4 truncated to 102). want A34 parse-mpfr-multiply 100 want A35 parse-round-toward-zero 102 # --- B: formatting ------------------------------------------------------------ # bs_size_human_readable. B01 is not a typo: the scaling loop's condition is # strictly greater than 1024, so exactly 1024 bytes stays in B. want B01 hr-1-KiB "1024 B" want B02 hr-1.5-KiB "1.5 KiB" want B03 hr-10-MiB "10 MiB" want B04 hr-1.5-GB "1.4 GiB" want B05 hr-negative "-2 GiB" want B06 hr-1-YiB "1024 ZiB" want B07 hr-min-unit-MiB "1024 MiB" want B08 hr-zero-places "1024 MiB" want B09 hr-1000-B "1000 B" want B10 hr-three-places "1000 B" want B11 hr-five-places "1.39698 GiB" # xlate=true must equal xlate=false in the C locale: the msgid IS the string, # so a difference here is gettext returning something other than the msgid. want B12 hr-xlate-C-locale "1.4 GiB" want B13 hr-xlate-negative "-2 GiB" # bs_size_convert_to -- the same MPFR float path, reported as a bare number. want B20 conv-GiB-to-MiB 1024 want B21 conv-GiB-to-KiB 1048576 want B22 conv-GiB-to-B 1073741824 want B23 conv-GB-to-GB 1.5 want B24 conv-GB-to-MB 1500 want B25 conv-negative -2 want B26 conv-YiB-to-YiB 1 want B27 conv-binary-to-decimal 10.48576 # --- C: arithmetic ------------------------------------------------------------ want C00 arith-setup ok want C01 add 1610612736 want C02 add-negative -1073741824 want C03 sub 536870912 want C04 sub-below-zero -536870912 want C05 add-bytes 1073741825 want C06 sub-bytes 1073741823 # The sum that does not fit in 64 bits -- the point of storing an mpz. want C07 add-beyond-uint64 1208925819614629174706177 want C10 mul-int 3221225472 want C11 mul-int-1024 549755813888 want C12 mul-int-negative -4294967296 want C13 mul-int-zero 0 # C21/C22 use 2**32+1, which is above a 32-bit unsigned long: mul_64bit takes # its accumulate loop here and its mpz_mul_ui fast path on the oracle, one # iteration either way. Same product by different code, which is the point. want C21 mul-int-past-ulong 4294967297 want C22 mul-int-past-ulong-2 4611686019501129728 want C14 div "2 sgn=1" want C15 div-by-one "1073741824 sgn=1" want C16 div-sign "4 sgn=-1" want C17 div-int 357913941 want C18 mod 0 want C19 true-div 2 want C20 true-div-int 357913941.3333333333333333333333333333333333333333333333333333333333333333333333 want C30 cmp-greater 1 want C31 cmp-less -1 want C32 cmp-equal 0 want C33 cmp-negative -1 want C34 cmp-absolute 1 want C35 cmp-bytes 0 want C36 sgn-positive 1 want C37 sgn-negative -1 want C38 sgn-zero 0 want C40 round-up 1073741824 want C41 round-down 1073741824 want C42 round-half-up 1610612736 want C43 mul-float-str 1610612736 want C44 copy-equals-original 0 # --- D: the 64-bit boundary on a 32-bit target -------------------------------- # THE GROUP THAT WOULD FAIL SILENTLY, AND WHY TWO OF ITS VALUES ARE NOT THE # ORACLE'S. # # libbytesize stores every size as an mpz and hands it out as a uint64_t, and # it does that through GMP entry points that take an UNSIGNED LONG -- 8 bytes # on the oracle, 4 here (F01). Two call sites are affected and they do not # behave the same way: # # bs_size_get_bytes guards with mpz_cmp(size, mpz(UINT64_MAX)) -- a real # 64-bit comparison -- and then with # mpz_cmp_ui(size, UINT64_MAX), whose argument truncates # to 2**32-1 here. Above that it does NOT call # mpz_get_ui: it formats a decimal string and strtoull's # it. So the truncated guard sends every value the # 32-bit path could not carry down the path that carries # it exactly, and the answer is right on both arms # (D04-D06, D10). Nothing in the source says so, which is # why it is measured here rather than assumed. # # bs_size_div has the same truncated mpz_cmp_ui guard and NO such # path. It answers BS_ERROR_OVER (1) and returns 0. So a # quotient that fits a uint64_t but not an unsigned long # is returned on the oracle (D21 8589934592, # D22 1152921504606846976) and refused here. # # That divergence is upstream's 32-bit behaviour, not something this port # introduced and not something it works around: shipping a libbytesize whose # bs_size_div silently returned a truncated quotient would be worse than one # that refuses. D20 pins the exact boundary from below -- 2**32-1 is returned # on both arms -- so the pair localises the edge rather than merely recording # that one exists. want D01 bytes-1 "1 sgn=1" want D02 bytes-1-GiB "1073741824 sgn=1" want D03 bytes-ulong-max "4294967295 sgn=1" want D04 bytes-past-ulong "4294967296 sgn=1" want D05 bytes-10-GiB "10737418240 sgn=1" want D06 bytes-1-EiB "1152921504606846976 sgn=1" want D07 bytes-past-uint64 ERR:1 want D08 bytes-negative "2147483648 sgn=-1" want D09 bytes-uint64-max-str 18446744073709551615 want D10 bytes-uint64-max "18446744073709551615 sgn=1" want D11 from-bytes-past-ulong 4294967296 want D12 from-bytes-negative -2 want D13 from-bytes-sign -1 want D20 div-at-ulong-max 4294967295 want D21 div-past-ulong ERR:1 want D22 div-far-past-ulong ERR:1 # --- E: the error contract ---------------------------------------------------- # BS_ERROR_INVALID_SPEC is 0, BS_ERROR_OVER 1, BS_ERROR_ZERO_DIV 2. want E01 err-not-a-size ERR:0 want E02 err-unknown-unit ERR:0 want E03 err-empty-string ERR:0 want E04 err-two-radix-chars ERR:0 want E05 err-unit-only ERR:0 want E10 err-div-by-zero ERR:2 want E11 err-div-int-by-zero ERR:2 want E12 err-mod-by-zero ERR:2 want E13 err-bytes-overflow ERR:1 want E14 err-div-overflow ERR:1 want E20 err-message-present 1 want E21 err-cleared-to-null 1 # The whole error argument is optional; a caller passing NULL must not crash. want E22 err-null-slot-ok 1 # --- F: the environment the rest rests on ------------------------------------- # F01/F02 are the two values that legitimately differ from the oracle -- 8 # there, 4 here. They are pinned to 4 because that is the fact group D depends # on, and F05/F06 are the same fact stated as the constants the library # actually passes: (unsigned long) UINT64_MAX is 2**32-1 on this arm, which IS # the truncated guard, observed rather than argued. want F01 sizeof-long 4 want F02 sizeof-pointer 4 want F03 sizeof-uint64 8 want F04 radix-char . want F05 uint64-max-as-ulong 4294967295 want F06 ulong-max 4294967295 rm -f "$pout"