. "$BST_LIB" # libcomps is the C library behind dnf's handling of comps.xml -- the RPM # metadata that declares package GROUPS, CATEGORIES and ENVIRONMENTS. It parses # that XML with expat, keeps it in a refcounted object tree, and writes it back # out through libxml2's xmlTextWriter. # # The distro is static-only and libcomps has NO CLI of its own, so the whole # installed payload is 25 headers plus libcomps.pc (-dev) and the archive # libcomps.a (-staticdev). The main package is EMPTY by construction. # # --------------------------------------------------------------------------- # WHY THIS SUITE RUNS A PROBE INSTEAD OF STOPPING AT PAYLOAD SHAPE # # BRINTOS-217: oniguruma COMPILED and PACKAGED perfectly on this arm and then # returned silently wrong match offsets to every caller, so a payload-only suite # would have gone green on the exact defect the port existed to find. libcomps # is the same shape of risk: a document parser whose whole job is to be RIGHT, # built out of expat callbacks that reassemble character data across fragments # and a radix-tree object store walked by pointer arithmetic. # # So data/libcomps-probe is cross-linked with wasmcc-glibc against THE VERY # ARCHIVE THAT SHIPS (/usr/lib/libcomps.a), staged to $TMPDIR and run here. Its # source is src/libcomps_probe.c. The cross-link reported # `refused=0 skipped=0 ... invalid_functions=0 module_broken=0`, and the pass # coroutinized comps_parse_file, comps_parse_start_elem_handler, # comps_parse_char_data_handler, comps2xml_str, comps_doc_xml, # comps_docgroup_xml and comps_parse_validate_dtd, plus libxml2's xmlReadFile # and xmlTextWriterStartElement -- so the parser, the emitter and the validator # are ALL genuinely rewritten on this arm, and the groups below measure that. # # EVERY EXPECTED VALUE IS THE VALUE A NATIVE x86_64 BUILD OF THE IDENTICAL # RELEASE PRODUCES. The probe compares each value internally and prints its own # verdict, so the cases below assert the verdict while the console dump carries # the value for diagnosis. The native control scored 41/41 and it corrected # three assertions of mine before it did -- the sample environment declares 3 # grouplist entries and 2 optionlist entries, not 2 and 1, and libcomps does # NOT strip the surrounding whitespace from ' option1 '. All three are now # asserted as the library really behaves. # # THE BYTE-EXACT CASE IS THE ONE THAT ONLY HAS CONTENT HERE. data/ # libcomps-sample-emitted.xml was minted BY the x86-64 build, so on x86-64 the # comparison is a tautology and passes trivially; on wasm32-hwjs it is the # whole assertion -- 8199 bytes of emitter output, every element, attribute and # ordering decision, against what the identical release produces natively. # # --------------------------------------------------------------------------- # WHY BUILTIN MATCHING INSTEAD OF check_out # # The probe runs ONCE and its output is read ONCE into a shell variable; every # assertion is a `case` glob against it, so there is no fork per case. The # cjson suite next door was killed by the runner's per-suite bound # (BST_SUITE_TIMEOUT) at 156 cases written as check_out+cat: on the wasm32-hwjs # runtime the forking IS the cost, not the work. # --------------------------------------------------------------------------- incd=/usr/include/libcomps lib=/usr/lib/libcomps.a pc=/usr/lib/pkgconfig/libcomps.pc nl=' ' # hasfile -- ok iff is a non-empty regular file. # has -- ok iff contains the literal . # hasline -- ok iff carries as a WHOLE line. # Substring matching would let comps_doc_groups # satisfy a lookup for comps_doc, and # comps_parse_file one for comps_parse. hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not in archive: $2" ;; esac; } # --- the shipped payload ----------------------------------------------------- hasfile libcomps/archive "$lib" hasfile libcomps/pkgconfig "$pc" hasfile libcomps/header-doc "$incd/comps_doc.h" hasfile libcomps/header-parse "$incd/comps_parse.h" hasfile libcomps/header-obj "$incd/comps_obj.h" # comps_types.h is #included by comps_log.h, comps_doc.h AND comps_parse.h, and # upstream's libcomps_HEADERS does not install it -- so a stock -dev package # cannot compile a single consumer. This port patches it into the install list; # the case is here so the fix cannot regress silently. hasfile libcomps/header-types-installed "$incd/comps_types.h" pctext=$(cat "$pc" 2>/dev/null) has libcomps/pc-name "Name: libcomps" "$pctext" has libcomps/pc-version "Version: 0.1.20" "$pctext" has libcomps/pc-libs "-lcomps" "$pctext" # --- what must NOT have shipped ---------------------------------------------- # The python bindings are configured off (-DENABLE_PYTHON=OFF, via a patch that # adds the option upstream lacks) because this image installs no python3, and # the shared library is off because static-only is distro policy. Both are # asserted rather than assumed: an -DENABLE_PYTHON that silently did nothing is # exactly the failure the patch exists to prevent. bst_lib_shape libcomps/no-shared-object "/usr/lib/libcomps.so" "/usr/lib/libcomps.so.0" if [ -n "$(find /usr/lib -name '_libpycomps*' -print 2>/dev/null | head -1)" ]; then bst_notok libcomps/no-python-bindings "ENABLE_PYTHON=OFF was set, yet the pycomps module shipped" else bst_ok libcomps/no-python-bindings fi # --- symbol completeness ----------------------------------------------------- # Most of libcomps' public API is MACRO-GENERATED: comps_doccategory_get_id is # spelled `HEAD_COMPS_PROP_GETTER(category, COMPS_DocCategory, id)` in the # header, so the function name appears nowhere in the installed text and a # header-derived symbol list -- the shape 81-libconfig.test uses -- would see # almost none of it. The list below is therefore curated rather than derived, # one name from each area the probe drives, plus a floor on the total so a # half-built archive cannot pass by carrying only the spot-checks. arcsyms=$(grep -ao 'comps_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) nsym=$(printf '%s\n' "$arcsyms" | grep -c . 2>/dev/null) # The two XML entry points need their OWN scan and do not belong in $arcsyms. # They are spelled comps2xml_str / comps2xml_f -- `comps2`, not `comps_` -- so # the pattern above cannot match them and a lookup here would be red forever # against a perfectly good archive. That is exactly what the first on-target run # reported, and it is the same trap 81-libconfig.test documents for # libconfig_yyparse. xmlsyms=$(grep -ao 'comps2xml_[a-z0-9_]*' "$lib" 2>/dev/null | sort -u) hasline libcomps/sym-parse-file comps_parse_file "$arcsyms" hasline libcomps/sym-parse-str comps_parse_str "$arcsyms" hasline libcomps/sym-validate-dtd comps_parse_validate_dtd "$arcsyms" hasline libcomps/sym-xml-str comps2xml_str "$xmlsyms" hasline libcomps/sym-xml-file comps2xml_f "$xmlsyms" hasline libcomps/sym-doc-groups comps_doc_groups "$arcsyms" hasline libcomps/sym-doc-categories comps_doc_categories "$arcsyms" hasline libcomps/sym-doc-environments comps_doc_environments "$arcsyms" hasline libcomps/sym-doc-langpacks comps_doc_langpacks "$arcsyms" hasline libcomps/sym-doc-union comps_doc_union "$arcsyms" hasline libcomps/sym-doc-intersect comps_doc_intersect "$arcsyms" hasline libcomps/sym-arch-filter comps_doc_arch_filter "$arcsyms" hasline libcomps/sym-group-packages comps_docgroup_get_packages "$arcsyms" hasline libcomps/sym-objlist-get comps_objlist_get "$arcsyms" hasline libcomps/sym-objradix comps_objrtree_get "$arcsyms" if [ "${nsym:-0}" -ge 200 ]; then bst_ok libcomps/symbols-floor else bst_notok libcomps/symbols-floor "only ${nsym:-0} distinct comps_* symbols in $lib, want >= 200" fi # --- the library RUNNING, via the cross-linked probe -------------------------- # The probe only READS -- its fixtures and the x86-64 reference both live in # $BST_DATA -- so unlike libconfig's it needs no scratch directory. It does need # to be copied out and made executable: data/ installs 0644 and the deployed # rootfs is read-only at browser runtime. # # Never `test -x "$BST_DATA/libcomps-probe"`: that passes against a payload # placed by hand and fails against every image that ever built the package. hasfile libcomps/probe-shipped "$BST_DATA/libcomps-probe" hasfile libcomps/fixture-sample "$BST_DATA/libcomps-sample.xml" hasfile libcomps/fixture-emitted "$BST_DATA/libcomps-sample-emitted.xml" hasfile libcomps/fixture-dtd "$BST_DATA/libcomps.dtd" hasfile libcomps/fixture-gz "$BST_DATA/libcomps-dtd.xml.gz" hasfile libcomps/reference "$BST_DATA/libcomps-native.txt" probe=${TMPDIR:-/tmp}/bst-libcomps-probe.$$ if cp "$BST_DATA/libcomps-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then out=$("$probe" "$BST_DATA" 2>&1) if bst_execfail "$out"; then bst_unrun libcomps/run-probe "$out" else # the parse itself: expat drove to completion with nothing logged has libcomps/run-parse-clean "PASS sample.parse.log-entries =" "$out" has libcomps/run-parse-nofatal "PASS sample.parse.fatal =" "$out" # the three top-level collections the format declares has libcomps/run-groups-len "PASS sample.groups.len =" "$out" has libcomps/run-categories-len "PASS sample.categories.len =" "$out" has libcomps/run-environments-len "PASS sample.environments.len =" "$out" has libcomps/run-langpacks-len "PASS sample.langpacks.len =" "$out" # groups walked by id, name, and the two package-type filters -- the # per-entry offset arithmetic BRINTOS-217 got wrong for onig has libcomps/run-group0-id "PASS sample.group.0.id =" "$out" has libcomps/run-group0-name "PASS sample.group.0.name =" "$out" has libcomps/run-group0-default "PASS sample.group.0.pkgs.default =" "$out" has libcomps/run-group0-optional "PASS sample.group.0.pkgs.optional =" "$out" has libcomps/run-group1-id "PASS sample.group.1.id =" "$out" has libcomps/run-group1-name "PASS sample.group.1.name =" "$out" has libcomps/run-group1-default "PASS sample.group.1.pkgs.default =" "$out" has libcomps/run-group1-optional "PASS sample.group.1.pkgs.optional =" "$out" has libcomps/run-group2-id "PASS sample.group.2.id =" "$out" has libcomps/run-group2-name "PASS sample.group.2.name =" "$out" has libcomps/run-group2-default "PASS sample.group.2.pkgs.default =" "$out" has libcomps/run-group2-optional "PASS sample.group.2.pkgs.optional =" "$out" # categories, including the groupid list each one carries has libcomps/run-cat0-id "PASS sample.category.0.id =" "$out" has libcomps/run-cat0-name "PASS sample.category.0.name =" "$out" has libcomps/run-cat0-groupids "PASS sample.category.0.groupids =" "$out" has libcomps/run-cat1-id "PASS sample.category.1.id =" "$out" has libcomps/run-cat1-name "PASS sample.category.1.name =" "$out" has libcomps/run-cat1-groupids "PASS sample.category.1.groupids =" "$out" # the environment, and both of its group lists BY MEMBER NAME -- a length # alone would not notice a list that came back in the wrong order. The # optionlist names are asserted with their surrounding whitespace intact, # which is what libcomps really does with that character data. has libcomps/run-env-id "PASS sample.env.0.id =" "$out" has libcomps/run-env-name "PASS sample.env.0.name =" "$out" has libcomps/run-env-grouplist "PASS sample.env.0.grouplist =" "$out" has libcomps/run-env-group0 "PASS sample.env.0.grouplist.0 =" "$out" has libcomps/run-env-group1 "PASS sample.env.0.grouplist.1 =" "$out" has libcomps/run-env-group2 "PASS sample.env.0.grouplist.2 =" "$out" has libcomps/run-env-optionlist "PASS sample.env.0.optionlist =" "$out" has libcomps/run-env-option0 "PASS sample.env.0.optionlist.0 =" "$out" has libcomps/run-env-option1 "PASS sample.env.0.optionlist.1 =" "$out" # THE EMITTER, byte-exact against the x86-64 reference. This is the case the # ticket is really about: 8199 bytes of xmlTextWriter output compared to what # the identical release produces natively. has libcomps/run-emit-byteexact "PASS sample.emit.byteexact =" "$out" # ...and re-parsing our own output, so an emitter that drops an element is # caught even where the byte comparison is the thing under suspicion has libcomps/run-roundtrip-groups "PASS sample.roundtrip.groups =" "$out" has libcomps/run-roundtrip-categories "PASS sample.roundtrip.categories =" "$out" has libcomps/run-roundtrip-environments "PASS sample.roundtrip.environments =" "$out" # THE ZLIB ARM. comps_parse_validate_dtd is the only entry point in libcomps' # C API that calls libxml2's xmlReadFile, and xmlReadFile inflates a gzipped # document transparently -- so the .gz case is what actually exercises zlib, # and the plain file beside it is the control that says a failure is zlib's. has libcomps/run-dtd-plain "PASS dtd.plain.valid =" "$out" has libcomps/run-dtd-gzip "PASS dtd.gzip.valid =" "$out" # The probe's own tally, so a truncated run cannot pass by printing fewer # lines than it has assertions. has libcomps/run-summary "libcomps-probe failures 0" "$out" # ...and the whole transcript compared to the x86-64 reference. This subsumes # every case above and adds the one thing they cannot state: that no line is # MISSING. A probe killed halfway through satisfies each `has` it reached. ref=$(cat "$BST_DATA/libcomps-native.txt" 2>/dev/null) if [ "$out" = "$ref" ]; then bst_ok libcomps/run-transcript-byteexact else bst_notok libcomps/run-transcript-byteexact \ "probe transcript differs from the x86-64 reference (see the console dump for the FAIL lines)" fi fi rm -f "$probe" 2>/dev/null else bst_notok libcomps/probe-stage "cannot stage $BST_DATA/libcomps-probe" fi