. "$BST_LIB" # nanopb 0.4.8 UMBRELLA -- meta-oe's nanopb.bb, the packagegroup over the two # halves of nanopb, from dynamic-layers/meta-python/recipes-devtools/nanopb. # The halves themselves are measured by the two suites beside this one: # 92-nanopb.test (the runtime -- archive, headers, and a cross-linked probe that # encodes and decodes) and 92-nanopb-generator.test (the generator -- imports, # emitted bytes against a committed oracle). This file measures neither of them # again. It measures the JOIN. # # --------------------------------------------------------------------------- # WHAT AN UMBRELLA PACKAGE CAN AND CANNOT BE ASKED TO PROVE # # nanopb.bb installs no file. It is `inherit packagegroup' plus an RDEPENDS, and # the .deb it produces has an empty data.tar -- measured, not assumed: the # package this port builds unpacks to `./' and nothing else. So there is no # behaviour here to exercise, and a suite that went looking for some would only # be re-running the halves' own suites under a third name. # # What there IS to measure is a CLAIM: that installing this one name gets you a # working nanopb, meaning both halves, at the paths their packaging declares, # AT VERSIONS THAT AGREE WITH EACH OTHER. Everything below is that claim taken # apart. # # ONE THING THIS SUITE DELIBERATELY DOES NOT CLAIM. On an image assembled by # do_rootfs from IMAGE_INSTALL, both halves are present because they are named # there too -- so nothing here can show that the UMBRELLA is what pulled them in. # Causation is not observable from the target. What is observable, and what the # cases below assert, is CONSISTENCY: every name the umbrella depends on is a # package this image actually installed, and no name it depends on is one this # image does not produce. That is the property that breaks when a dependency is # dropped or misspelled, which is the whole of what an umbrella can get wrong. # # --------------------------------------------------------------------------- # THE DEFECT THIS PORT REMOVED, WHICH IS WHY depends-set IS AN EQUALITY # # Upstream's nanopb.bb declares # # RDEPENDS:${PN} = "nanopb-generator nanopb-runtime" # # and on this image `nanopb-runtime' does not exist. nanopb-runtime_%.bbappend # turns the shared object off, because wasmld refuses a side-module link that # receives native wasm inputs; with no .so left, ${PN}'s default glob matches # nothing, and an empty package with no ALLOW_EMPTY is never written. The batch's # own deploy/deb/wasm32 carries nanopb-runtime-dev, -staticdev and -dbg, and no # nanopb-runtime. The stock umbrella therefore depends on a name nothing # RPROVIDES -- which builds green at recipe scope (measured: 1745 of 1745 tasks, # zero ERROR lines, `Depends: nanopb-generator, nanopb-runtime' in the .deb) and # fails do_rootfs for the WHOLE IMAGE. The port names the packages the image # actually delivers. # # So the interesting failure is not only a MISSING dependency but a SURPLUS one: # the bug was an extra name, and a substring case ("is nanopb-generator in the # Depends line?") passes just as happily before the fix as after it. The set is # therefore compared as a whole. # # BUT NOT AS A STRING. The sibling ports that assert dependencies one case at a # time do so for a reason worth keeping (see 99-python3-coloredlogs.test): the # recipe's RDEPENDS order is not dpkg's to preserve and the separator spacing is # dpkg's own, so an exact-string comparison asserts a FORMAT and reds on a # reformat that changed no fact. depends-set splits on the comma, trims, and # sorts before comparing -- an equality over a SET, which catches the dropped # name and the extra name both, and cannot be broken by dpkg deciding to write # its separators differently. dep-* below then re-reads that same parsed set and # says, per name, whether the image installed it: those are the cases that # separate "this port is wrong" from "its dependency did not land". # # --------------------------------------------------------------------------- # THE CENTREPIECE IS VERSION AGREEMENT, AND IT NEEDS NEITHER protoc NOR A # COMPILER # # The failure an umbrella over two independently-built halves is uniquely # exposed to is DRIFT: a generator from one nanopb release packaged beside a # runtime from another. Both halves would install, both halves' own suites would # pass, and the pair would be broken -- which is precisely the failure nobody # else is looking for, because each half is correct. # # nanopb makes that measurable without running anything, because the two halves # carry each other's version in their artifacts: # # - the runtime's pb.h defines NANOPB_VERSION "nanopb-0.4.8", and the # generator prints the same string for --version. halves-agree compares the # two EXTRACTED values against each other rather than each against a # literal, so it stays true across an upgrade and reds only on a drift. # # - stronger, and it is the halves' OWN contract rather than one this suite # invented: every file the generator emits opens with # #if PB_PROTO_HEADER_VERSION != 40 # #error Regenerate this file with the current version of nanopb generator. # and pb.h defines PB_PROTO_HEADER_VERSION. header-version-agree asserts the # two numbers are equal -- which is exactly the condition under which # generated code compiles against this runtime at all. A red here is a pair # that cannot be used together, stated before anyone tries. # # --------------------------------------------------------------------------- # WHAT THIS ARM CANNOT DO, SAID PLAINLY RATHER THAN QUIETLY DROPPED # # The ticket asks for a round trip generated AND COMPILED on target. Neither # step is available here, and both are structural rather than defects in this # port: # # - THERE IS NO C COMPILER ON THIS IMAGE. Every library port on this arm has # had to ship a cross-linked consumer built at recipe time to have anything # that runs. nanopb already has one: 92-nanopb.test's data/nanopb-probe is # the generator's output compiled against this runtime's archive, and its 97 # cases against a native x86_64 control are where the round trip, the pinned # wire bytes, the absent unset field, the truncated decode and the oversize # decode are actually measured. This suite cites that evidence rather than # restaging it, and roundtrip-compile records the reason as a skip so the # absence is visible in the transcript instead of being inferred from a # suite that simply does not mention it. # # - protoc TRAPS BEFORE main ON THIS IMAGE (brintos/hwjs-cc#82: the # coroutinize pass's CTOR-DRIVE lowering drives a coroutinized .init_array # constructor and then executes a bare `unreachable'). nanopb_generator.py # has no .proto parser of its own -- it shells out to protoc for every file # it reads -- so the on-target generate step goes with it. The live-* cases # are guarded on a LIVE protoc probe rather than pinned to "expected red": # when #82 lands they start running with no edit to this file. # # With protoc dead, the generator-side half of the version agreement is read # from the COMMITTED ORACLE in $BST_DATA -- the .pb.h/.pb.c that # 92-nanopb-generator.test compares the live generator's output against byte for # byte whenever protoc is alive. That is a weaker source than a live run and it # is named as one: oracle-* says the shipped generator's output agrees with the # shipped runtime, live-* says the generator on THIS image does. # --------------------------------------------------------------------------- # A root prefix, empty on the target and set only when this suite is validated # NATIVELY before it ships. The runner never sets it, so on the image these are # exactly the absolute paths. Same escape hatch, and same reason, as # $BST_NANOPB_ROOT in 92-nanopb.test. R="${BST_NANOPB_UMB_ROOT:-}" # Must equal this suite's own name: the batch runner attributes a red to its # member by comparing the case prefix against the declared suite whole. P=nanopb-umbrella STATUS=$R/var/lib/dpkg/status INFO=$R/var/lib/dpkg/info GEN=$R/usr/bin/nanopb_generator.py INCDIR=$R/usr/include/nanopb ARCHIVE=$R/usr/lib/libprotobuf-nanopb.a SITE=$R/usr/lib/python3.12/site-packages/nanopb W=${TMPDIR:-/tmp}/bst-nanopb-umb.$$ mkdir -p "$W" || { bst_skip $P/present "could not create a work dir under ${TMPDIR:-/tmp}"; exit 0; } # ------------------------------------------------ the umbrella, as dpkg has it --- # The anchor is exact. A package named `nanopb-' -- and there are two # of them on this image -- would otherwise be read as this one, which would make # every case below measure the wrong stanza and still look plausible. sed -n '/^Package: nanopb$/,/^$/p' "$STATUS" > "$W/st" 2>/dev/null if [ ! -s "$W/st" ]; then bst_skip $P/present "nanopb (the umbrella packagegroup) is not installed on this image" rm -rf "$W" exit 0 fi bst_ok $P/present check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" # PV comes from the recipe body (nanopb.bb sets PV = "1.0"), not from a version # in its filename -- which is also why the port's append is nanopb.bbappend with # no version glob. Pinned because a change here means the umbrella was rebuilt # from a different recipe than the one this suite was written against. check_out $P/pkg-version "Version: 1.0-r0" cat "$W/st" # IT SHIPS NOTHING, AND THAT IS THE ASSERTION. A packagegroup that started # installing files would be a different kind of object -- and the most likely way # for that to happen is a stray FILES: line pulling one half's payload into the # umbrella, which would then conflict with the half that owns it. _files=$(awk '{ print $1 }' "$INFO/nanopb.list" 2>/dev/null | while read -r _f; do [ -f "$R$_f" ] && printf '%s ' "$_f" done) if [ -z "$_files" ]; then bst_ok $P/ships-no-files else bst_notok $P/ships-no-files "the umbrella packagegroup ships regular files: $(_bst_1line "$_files")" fi # ------------------------------------------------------- the dependency set --- _dep=$(sed -n 's/^Depends: *//p' "$W/st" | head -1) if [ -n "$_dep" ]; then bst_ok $P/depends-line else bst_notok $P/depends-line "the nanopb stanza carries no Depends: line, so the umbrella depends on nothing and joins nothing" fi # Split on the comma, drop any version relation dpkg may carry in parentheses, # trim, sort. What comes out is a set, compared as one -- see the header for why # this is neither a substring test nor a string compare. printf '%s\n' "$_dep" | tr ',' '\n' | sed 's/(.*)//; s/^ *//; s/ *$//; /^$/d' | sort > "$W/deps" _got=$(tr '\n' ' ' < "$W/deps" | sed 's/ *$//') _want='nanopb-generator nanopb-runtime-dev nanopb-runtime-staticdev' check_eq $P/depends-set "$_want" "$_got" # Named on its own because it is the exact regression this port removed, and # because depends-set's red would otherwise report a whole set where one word is # the story. `nanopb-runtime' is the bare runtime package: it is empty on this # image (the shared object is declined) and therefore never written, so an # umbrella depending on it fails do_rootfs for every package in the image. if grep -qx 'nanopb-runtime' "$W/deps"; then bst_notok $P/depends-no-empty-runtime "the umbrella depends on the bare nanopb-runtime, which this image does not produce -- do_rootfs would refuse the whole image" else bst_ok $P/depends-no-empty-runtime fi # EVERY DEPENDENCY IS A PACKAGE THIS IMAGE INSTALLED. Read back from the parsed # set rather than from a list re-typed here, so this walks whatever the umbrella # actually claims today -- including a name a future revision adds. One case per # name: a red says WHICH half did not land, which is the difference between a # broken port and a missing sibling. for _d in $(cat "$W/deps"); do sed -n "/^Package: $_d\$/,/^\$/p" "$STATUS" > "$W/d.st" 2>/dev/null if [ ! -s "$W/d.st" ]; then bst_notok "$P/dep-$_d" "the umbrella depends on $_d, which dpkg does not know on this image" else check_out "$P/dep-$_d" "Status: install ok installed" cat "$W/d.st" fi done # ------------------------------------------- both halves, at the declared paths --- # Restated here rather than left to the two half-suites on purpose: those run # only when THEIR package is installed, so if one half vanished its suite would # go quiet rather than red, and the umbrella's claim would fail with nothing # reporting it. These are the umbrella asserting its own promise. check $P/half-gen-cli test -f "$GEN" check $P/half-gen-plugin test -f "$R/usr/bin/protoc-gen-nanopb" check $P/half-gen-module test -f "$SITE/generator/nanopb_generator.py" check $P/half-rt-archive test -f "$ARCHIVE" for _h in pb.h pb_common.h pb_encode.h pb_decode.h; do check "$P/half-rt-$_h" test -f "$INCDIR/$_h" done # ------------------------------------------------------- version agreement --- # The runtime's own statement of which nanopb it is. _rtver=$(sed -n 's/^#define NANOPB_VERSION "\(.*\)"/\1/p' "$INCDIR/pb.h" 2>/dev/null | head -1) if [ -n "$_rtver" ]; then bst_ok $P/rt-version else bst_notok $P/rt-version "no NANOPB_VERSION in $INCDIR/pb.h, so the runtime does not say which nanopb it is" fi # The generator's. This is a live run of the generator on this image, but NOT of # the transform: --version is answered before any .proto is read, so it does not # reach protoc and is unaffected by hwjs-cc#82. 92-nanopb-generator.test measures # the same string; here it is one operand of the comparison below rather than the # assertion itself. _genver= if command -v python3 >/dev/null 2>&1 && [ -f "$GEN" ]; then _gv=$(python3 "$GEN" --version 2>&1); _gvrc=$? _genver=$(printf '%s' "$_gv" | tr -d '\r' | head -1) # rc=0 IS NOT ENOUGH, and this was found by running the suite natively before # it shipped: against a stub wrapper the interpreter exits 0 having printed # nothing, gen-version went green, and halves-agree then SKIPPED for want of an # operand -- a pass and a non-result where a red belonged. The version string # is the product; its absence is the failure. if [ "$_gvrc" -eq 0 ] && [ -n "$_genver" ]; then bst_ok $P/gen-version elif [ "$_gvrc" -eq 0 ]; then bst_notok $P/gen-version "the generator exited 0 without printing a version" elif bst_execfail "$_gv"; then bst_unrun $P/gen-version "$_gv" else bst_notok $P/gen-version "rc=$_gvrc $(_bst_1line "$_gv")" fi else bst_skip $P/gen-version "python3 or the generator wrapper is absent, so the generator cannot be asked its version" fi # THE DRIFT CASE. Neither side is compared to a literal: the two halves are # compared to each other, so this stays true across a version bump and reds only # when the pair stops being a pair. if [ -n "$_rtver" ] && [ -n "$_genver" ]; then check_eq $P/halves-agree "$_rtver" "$_genver" else bst_skip $P/halves-agree "one side did not report a version (runtime='$_rtver' generator='$_genver')" fi # ------------------------------------- the halves' own compile-time contract --- # pb.h's number, and the number the generator's emitted files demand of it. The # generator writes `#if PB_PROTO_HEADER_VERSION != N / #error Regenerate this # file...' into every file it produces, so equality here is precisely the # condition under which the generator's output compiles against this runtime. _rthv=$(sed -n 's/^#define PB_PROTO_HEADER_VERSION *\([0-9]*\).*/\1/p' "$INCDIR/pb.h" 2>/dev/null | head -1) if [ -n "$_rthv" ]; then bst_ok $P/proto-header-version else bst_notok $P/proto-header-version "no PB_PROTO_HEADER_VERSION in $INCDIR/pb.h" fi # The generator side, read from the committed oracle -- the .pb.h that # 92-nanopb-generator.test compares a live run against byte for byte whenever # protoc is alive. Named oracle-* rather than gen-*: it is the SHIPPED # generator's output, not a run on this image, and the header says why that # distinction is being kept rather than smoothed over. ORACLE=$BST_DATA/nanopbgen_main.pb.h.expected if [ ! -f "$ORACLE" ]; then bst_skip $P/oracle-present "nanopbgen_main.pb.h.expected absent from $BST_DATA" bst_skip $P/header-version-agree "the generator-side oracle did not ship" bst_skip $P/symbols-resolve "the generator-side oracle did not ship" else bst_ok $P/oracle-present _genhv=$(sed -n 's/^#if PB_PROTO_HEADER_VERSION != *\([0-9]*\).*/\1/p' "$ORACLE" | head -1) if [ -z "$_genhv" ] || [ -z "$_rthv" ]; then bst_skip $P/header-version-agree "one side did not state a header version (runtime='$_rthv' generated='$_genhv')" else check_eq $P/header-version-agree "$_rthv" "$_genhv" fi # EVERY nanopb NAME THE GENERATED CODE USES MUST BE ONE THIS RUNTIME PROVIDES. # Derived from the generated files rather than from a list typed here, so it # walks what the emitter actually emits and grows with it. Types and macros are # looked for in the installed headers; the encode/decode entry points are # looked for in the archive's symbol index as well, because a header that # declares a function the archive does not define is a link error a consumer # meets and this image cannot otherwise see (there is no compiler here to meet # it with). # # grep rather than nm: the ar symbol index holds the names as plain bytes, and # this works with no toolchain present -- the same reason 92-nanopb.test gives. cat "$ORACLE" "$BST_DATA/nanopbgen_main.pb.c.expected" 2>/dev/null \ | grep -oE '\b(PB_[A-Z0-9_]+|pb_[a-z0-9_]+_t|pb_(encode|decode|release|get_encoded_size|istream|ostream)[a-z0-9_]*)\b' \ | grep -vE '_INCLUDED$' \ | sort -u > "$W/syms" # The `grep -v' drops the emitted header's OWN include guard, which nanopb # spells PB__PB_H_INCLUDED -- a PB_ token by shape and not a runtime # identifier by any other measure. Without it this case reds on a correct pair, # which is what the native validation run reported before it shipped. _nsym=$(wc -l < "$W/syms" | tr -d ' ') _miss= for _s in $(cat "$W/syms"); do grep -rq "$_s" "$INCDIR" 2>/dev/null || _miss="$_miss $_s" done if [ "$_nsym" = 0 ]; then bst_notok $P/symbols-resolve "extracted no nanopb identifiers from the generated oracle, so this case checked nothing" elif [ -z "$_miss" ]; then bst_ok $P/symbols-resolve else bst_notok $P/symbols-resolve "the generator's output names $(printf '%s' "$_miss" | wc -w | tr -d ' ') of $_nsym nanopb identifiers this runtime's headers do not define:$(_bst_1line "$_miss")" fi fi # The three entry points a generated consumer links against, in the archive this # umbrella promises alongside the headers. Separate from symbols-resolve because # a header-only pass there would still leave a consumer unlinkable. if [ ! -f "$ARCHIVE" ]; then bst_skip $P/archive-entrypoints "$ARCHIVE absent; nothing to link against" else _amiss= for _s in pb_encode pb_decode pb_get_encoded_size; do grep -q "$_s" "$ARCHIVE" 2>/dev/null || _amiss="$_amiss $_s" done if [ -z "$_amiss" ]; then bst_ok $P/archive-entrypoints else bst_notok $P/archive-entrypoints "the runtime archive does not define:$_amiss" fi fi # ------------------------------------------------- the live transform, if any --- # One live probe decides this half, exactly as 92-nanopb-generator.test does over # the same defect. Deliberately not a pinned "expected red": when hwjs-cc#82 # lands these run with no edit here. PROTOC_OK=1 PROTOC_WHY= if command -v protoc >/dev/null 2>&1; then _pv=$(protoc --version 2>&1); _prc=$? if [ "$_prc" -ne 0 ]; then PROTOC_OK=0 PROTOC_WHY="blocked by brintos/hwjs-cc#82 -- protoc traps in __wasm_call_ctors before main (rc=$_prc$([ -n "$_pv" ] && echo ", said: $(_bst_1line "$_pv")")); nanopb_generator.py reads every .proto THROUGH protoc, so the transform never runs. Not an umbrella defect: gen-version and halves-agree above are the generator loading and answering on this image" fi else PROTOC_OK=0 PROTOC_WHY="protoc is not installed on this image, and nanopb_generator.py has no .proto parser of its own" fi if [ "$PROTOC_OK" = 0 ] || [ ! -f "$GEN" ] || ! command -v python3 >/dev/null 2>&1; then [ "$PROTOC_OK" = 1 ] && PROTOC_WHY="python3 or the generator wrapper is absent" bst_skip $P/live-generate "$PROTOC_WHY" bst_skip $P/live-header-version-agree "$PROTOC_WHY" else # The corpus the generator oracle was made from, already shipped for # 92-nanopb-generator.test. Staged into $W because the generator writes its # output beside its input. cp "$BST_DATA/nanopbgen_common.proto" "$BST_DATA/nanopbgen_main.proto" \ "$BST_DATA/nanopbgen_main.options" "$W/" 2>/dev/null _lout=$(cd "$W" && python3 "$GEN" nanopbgen_common.proto nanopbgen_main.proto 2>&1); _lrc=$? if [ "$_lrc" -eq 0 ] && [ -f "$W/nanopbgen_main.pb.h" ]; then bst_ok $P/live-generate elif bst_execfail "$_lout"; then bst_unrun $P/live-generate "$_lout" elif [ "$_lrc" -eq 0 ]; then # Reported apart from a non-zero exit because it is a different fault and # `rc=0' with an empty detail reads as a suite bug rather than a finding -- # which is exactly how it read in the native validation run. bst_notok $P/live-generate "the generator exited 0 but emitted no nanopbgen_main.pb.h" else bst_notok $P/live-generate "rc=$_lrc $(_bst_1line "$_lout")" fi # The same contract as header-version-agree, but with the generator side # produced ON THIS IMAGE rather than read from the committed oracle. This is # the case the ticket's "the two halves agree on this target" actually asks # for; the oracle case above is what stands in for it while protoc is down. if [ -f "$W/nanopbgen_main.pb.h" ] && [ -n "$_rthv" ]; then _lhv=$(sed -n 's/^#if PB_PROTO_HEADER_VERSION != *\([0-9]*\).*/\1/p' "$W/nanopbgen_main.pb.h" | head -1) check_eq $P/live-header-version-agree "$_rthv" "$_lhv" else bst_skip $P/live-header-version-agree "no header was generated on this image, so there is nothing to compare against pb.h" fi fi # --------------------------------------------------- what this arm cannot run --- # A visible skip rather than a silent omission. There is no C compiler on this # image, so the generate-compile-encode-decode round trip the ticket describes # cannot be performed here by any suite. It IS measured, at recipe time and by # the sibling: data/nanopb-probe is this generator's output cross-linked against # this runtime's archive, and 92-nanopb.test scores its 97 cases against a native # x86_64 control -- the round trip, the wire bytes pinned to a literal, the unset # field absent from the encoding, the truncated decode and the oversize decode # all live there. Recorded here so a reader of THIS suite is told where the # evidence is rather than concluding nobody looked. bst_skip $P/roundtrip-compile "no C compiler on this image; the compiled round trip is measured by 92-nanopb.test's data/nanopb-probe -- generator output cross-linked at recipe time against this runtime's archive, scored against a native x86_64 control" rm -rf "$W" 2>/dev/null