# BST-TIMEOUT: 900 . "$BST_LIB" # protobuf-c 1.5.0 -- the C implementation of Protocol Buffers: libprotobuf-c, a # pure-C encode/decode runtime, plus protoc-c, the generator that turns a .proto # into C descriptor code (meta-oe recipes-devtools/protobuf). See # protobuf-c_%.bbappend for what the port adds and why. # # --------------------------------------------------------------------------- # TWO HALVES, IN TWO LANGUAGES, AND THEY FAIL FOR DIFFERENT REASONS # # * THE RUNTIME is pure C. libprotobuf-c.a links no libprotobuf, no libprotoc # and no abseil, and it is what an application on this image would actually # use. It is exercised by protobuf-c-probe, cross-linked during the port # against the very libprotobuf-c.a that ships: it serialises a fixed # message, compares the WIRE BYTES byte-for-byte against an x86-64 # reference, parses them back, and puts the parser's refusal paths through # their paces. A probe is the only way to reach this half at all -- the # runtime is a header plus an archive with no CLI of its own, and this image # ships no C compiler. # # * THE GENERATOR is C++ and links -lprotoc -lprotobuf and the whole abseil # closure -- the same link protobuf's own port had to fight (entry 349). It # is exercised by running protoc-c on the shipped schema and comparing the C # it emits byte-for-byte against an x86-64 reference from the same release. # This is the gengetopt pattern (BRINTOS-387): what a target code GENERATOR # can be held to is its output, since nothing here can compile what it # emits. # # So a red in one half says nothing about the other, and that is the point of # the split: the runtime half is the port's real deliverable, and it is # independent of everything that blocks the generator. # # WHY THE BYTE COMPARISONS ARE LEGITIMATE. # # The WIRE is specified. Protocol Buffers fixes field order (ascending field # number), the varint/zigzag/fixed encodings, and proto3's elision of default # values -- so for a fixed message the bytes are a function of the spec, not of # the host. The schema deliberately contains no map<> field, whose # serialisation order is explicitly unspecified. Both arms are little-endian, # so the fixed64 and double fields compare directly. # # The GENERATED CODE carries no timestamp, no host name, no arch string and no # absolute path -- protoc-c records only the .proto's name as it was GIVEN # relative to --proto_path, which is why the run below cd's into the schema's # directory and names it bare, exactly as the reference was produced. Get that # wrong and the comparison measures the path. # # WHERE THE REFERENCES LIVE, AND WHY NOT IN $BST_DATA. protobuf (entry 349) keeps # its references in the suite's data/ dir; this port keeps all three beside the # package, in /usr/libexec/protobuf-c-tests, installed by protobuf-c itself. They # are outputs OF this package at THIS version -- the .pb-c.{c,h} pair is what its # generator emits, the wire is what its runtime encodes -- so a version bump has # to move them in the same change as the recipe. Beside the package that is # unavoidable; in another package's data/ dir it is merely conventional, and a # stale blob there would read as a protobuf-c regression. # # --------------------------------------------------------------------------- # WHAT IS EXPECTED, AND WHAT WOULD BE A REAL REGRESSION # # THE RUNTIME HALF IS ALL GREEN and no non-pass is tolerated in it, with one # stated exception below: the corpus is in-memory encode/decode -- no socket, no # device, no clock, no thread, and no ordering any result depends on. # # THE EXCEPTION is wire/matches-protobuf-cxx, which SKIPS. It would compare # protobuf-c's bytes against Google's C++ implementation for the same schema, and # that reference needs a native C++ link against protobuf's archives, which our # build hosts cannot perform (BRINTOS-1349 -- fra2 is Ubuntu 20.04 against an # sstate built on 24.04). It is a skip and not a red because the assertion never # ran. The moment 1349 lands, the reference can be emitted at build time and this # skip becomes a pass with no edit to this file. # # THE GENERATOR HALF IS EXPECTED TO BE BLOCKED, and is SKIPPED WITH ATTRIBUTION # rather than left to fail case by case. protoc-gen-c is the same link as protoc, # which traps before it reaches its own main on every invocation -- # brintos/hwjs-cc#82: the coroutinize pass's CTOR-DRIVE lowering drives a # coroutinized .init_array constructor EXACTLY ONCE and then executes a bare # `unreachable` if that single drive did not complete the frame. # # It is decided by a LIVE `protoc-c --version` probe rather than pinned to # "expected red", so the day #82 lands this suite runs the full byte-exact # comparison with no edit and the expected shape becomes zero skips in this half. # # THE PROBE IS THE CONTROL THAT MAKES THAT ATTRIBUTION STICK, and here it is a # stronger control than protobuf's own: protobuf-c-probe does not merely link a # different aggregation of the same library, it links NO C++ AT ALL. If the # runtime half is green while the generator half traps, the fault provably lies # in the C++ link and not in protobuf-c. # # A generator half that turns from skips into REDS means protoc-c started # running -- read the new failures as real. inc=/usr/include/protobuf-c PT=/usr/libexec/protobuf-c-tests refwire=$PT/brintos_probe.ref.bin refsrc=$PT/brintos_probe.pb-c.c.ref refhdr=$PT/brintos_probe.pb-c.h.ref work=${TMPDIR:-/tmp}/bst-protobuf-c.$$ rm -rf "$work"; mkdir -p "$work/src" "$work/gen" "$work/idem" # hasfile -- ok iff is a non-empty regular file. Fork-free. hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "missing or empty: $2"; fi } # --------------------------------------------------------------------------- # 0. THE CANARY, which is not an assertion about protobuf-c at all. # # brintos_local_verify.py loses a suite's FIRST BST result line before the host # parser ever sees it -- BRINTOS-1149, a defect in our own host-side tooling and # not in the runner, the guest or any package. The harness reports the deficit # but computes its pass count as total-minus-failures, so THE DROPPED LINE IS # COUNTED AS A PASS. A suite whose first case is a genuine `not ok` therefore # loses the one line that mattered, silently. # # So the first line out of this file asserts something already true. Remove this # case when 1149 is fixed. bst_ok protobuf-c/suite-canary # --------------------------------------------------------------------------- # 1. PAYLOAD -- what the packages put on the image. hasfile protobuf-c/header "$inc/protobuf-c.h" hasfile protobuf-c/staticlib /usr/lib/libprotobuf-c.a hasfile protobuf-c/pkgconfig /usr/lib/pkgconfig/libprotobuf-c.pc hasfile protobuf-c/probe-shipped "$PT/protobuf-c-probe" hasfile protobuf-c/schema-shipped "$PT/brintos_probe.proto" hasfile protobuf-c/reference-wire "$refwire" hasfile protobuf-c/reference-source "$refsrc" hasfile protobuf-c/reference-header "$refhdr" # The generator is installed under BOTH names: upstream's install-exec-hook # links protoc-c -> protoc-gen-c, and the port re-makes that link after # stripping the real binary (llvm-strip would otherwise follow the symlink and # leave the unstripped original shipping whole). Assert both, because a strip # that turned the link into a second copy is exactly what this catches. hasfile protobuf-c/generator-binary /usr/bin/protoc-gen-c check_rc protobuf-c/generator-alias-is-symlink 0 test -L /usr/bin/protoc-c # The .pc has to agree with the release the references were produced from -- the # same precondition as the version check below, one layer down. pcver=$(sed -n 's/^Version: *//p' /usr/lib/pkgconfig/libprotobuf-c.pc 2>/dev/null) check_eq protobuf-c/pc-version "1.5.0" "$pcver" # --------------------------------------------------------------------------- # 2. THE GENERATOR. require protoc-c protobuf-c/protoc-c-present # ONE LIVE PROBE DECIDES THIS WHOLE HALF -- see the header for why it is a probe # and not a pinned expectation. pv=$(protoc-c --version 2>&1); prc=$? if [ "$prc" -ne 0 ]; then # Not bst_execfail: the binary DOES stage and start. It is not a missing tool # and not an ENOMEM staging refusal -- it traps in its own constructors -- so # labelling it UNRUN-* would attribute it to the wrong platform mechanism. why="blocked by brintos/hwjs-cc#82 -- protoc-gen-c traps in its .init_array drive before main (rc=$prc$([ -n "$pv" ] && echo ", said: $(_bst_1line "$pv")")); the CTOR-DRIVE lowering drives a coroutinized ctor once and then executes a bare unreachable. Not a protobuf-c defect: protobuf-c-probe below links NO C++ at all and completes" for c in protoc-c-version protoc-c-libprotoc-version protoc-c-codegen \ emitted-source emitted-header \ codegen-source-matches-x86-64 codegen-header-matches-x86-64 \ protoc-c-idempotent-source protoc-c-idempotent-header \ protoc-c-rejects-malformed protoc-c-rejects-unknown-type \ protoc-c-rejects-duplicate-field-number protoc-c-rejects-missing-input do bst_skip "protobuf-c/$c" "$why" done printf 'BST-DIAG protobuf-c/generator-blocked rc=%s size=%s bytes (hwjs-cc#82)\n' \ "$prc" "$(wc -c < /usr/bin/protoc-gen-c 2>/dev/null)" else # protoc-c prints TWO version lines and they are two different facts: its own # release, and the libprotoc it was linked against. Both are preconditions for # the byte comparison -- the references came from a build of 1.5.0 against # libprotoc 25.8. case "$pv" in *"protobuf-c 1.5.0"*) bst_ok protobuf-c/protoc-c-version ;; *) bst_notok protobuf-c/protoc-c-version "want 'protobuf-c 1.5.0', got: $(_bst_1line "$pv")" ;; esac case "$pv" in *"libprotoc 25.8"*) bst_ok protobuf-c/protoc-c-libprotoc-version ;; *) bst_notok protobuf-c/protoc-c-libprotoc-version "want 'libprotoc 25.8', got: $(_bst_1line "$pv")" ;; esac # THE CODEGEN UNDER TEST. cd + a bare filename, because protoc-c bakes the # .proto name it was GIVEN into the generated code; the reference was produced # by an x86-64 protoc-c invoked exactly this way. cp "$PT/brintos_probe.proto" "$work/src/brintos_probe.proto" ( cd "$work/src" && protoc-c --c_out="$work/gen" brintos_probe.proto ) 2>"$work/pc.err" prc=$? if [ "$prc" -eq 0 ]; then bst_ok protobuf-c/protoc-c-codegen elif bst_execfail "$(cat "$work/pc.err" 2>/dev/null)"; then bst_unrun protobuf-c/protoc-c-codegen "$(cat "$work/pc.err" 2>/dev/null)" else bst_notok protobuf-c/protoc-c-codegen "rc=$prc $(_bst_1line "$(cat "$work/pc.err" 2>/dev/null)")"; fi hasfile protobuf-c/emitted-source "$work/gen/brintos_probe.pb-c.c" hasfile protobuf-c/emitted-header "$work/gen/brintos_probe.pb-c.h" # THE ASSERTION THIS HALF EXISTS FOR: wasm32 protoc-c and x86-64 protoc-c emit # the same bytes for the same schema. check_rc protobuf-c/codegen-source-matches-x86-64 0 \ cmp -s "$work/gen/brintos_probe.pb-c.c" "$refsrc" check_rc protobuf-c/codegen-header-matches-x86-64 0 \ cmp -s "$work/gen/brintos_probe.pb-c.h" "$refhdr" # cmp says only "differ", so give the reader the shape of any difference without # making it a second verdict. if ! cmp -s "$work/gen/brintos_probe.pb-c.c" "$refsrc" 2>/dev/null; then printf 'BST-DIAG protobuf-c/codegen-source-diff %s\n' \ "$(cmp "$work/gen/brintos_probe.pb-c.c" "$refsrc" 2>&1 | head -1)" printf 'BST-DIAG protobuf-c/codegen-source-size on-target=%s reference=%s\n' \ "$(wc -c < "$work/gen/brintos_probe.pb-c.c" 2>/dev/null)" "$(wc -c < "$refsrc" 2>/dev/null)" fi if ! cmp -s "$work/gen/brintos_probe.pb-c.h" "$refhdr" 2>/dev/null; then printf 'BST-DIAG protobuf-c/codegen-header-diff %s\n' \ "$(cmp "$work/gen/brintos_probe.pb-c.h" "$refhdr" 2>&1 | head -1)" printf 'BST-DIAG protobuf-c/codegen-header-size on-target=%s reference=%s\n' \ "$(wc -c < "$work/gen/brintos_probe.pb-c.h" 2>/dev/null)" "$(wc -c < "$refhdr" 2>/dev/null)" fi # A generator that is not deterministic cannot be byte-compared against # anything, so this is what licenses the two assertions above rather than being # a separate nicety. ( cd "$work/src" && protoc-c --c_out="$work/idem" brintos_probe.proto ) 2>/dev/null check_rc protobuf-c/protoc-c-idempotent-source 0 \ cmp -s "$work/idem/brintos_probe.pb-c.c" "$work/gen/brintos_probe.pb-c.c" check_rc protobuf-c/protoc-c-idempotent-header 0 \ cmp -s "$work/idem/brintos_probe.pb-c.h" "$work/gen/brintos_probe.pb-c.h" # THE ERROR CHANNEL. A compiler that accepts everything is indistinguishable # from a working one to any test that only feeds it valid schemas. printf 'syntax = "proto3";\nmessage Broken { string x = 1\n' > "$work/src/broken.proto" check_rc protobuf-c/protoc-c-rejects-malformed 1 \ sh -c "cd '$work/src' && protoc-c --c_out='$work/gen' broken.proto" printf 'syntax = "proto3";\nmessage T { nosuchtype x = 1; }\n' > "$work/src/badtype.proto" check_rc protobuf-c/protoc-c-rejects-unknown-type 1 \ sh -c "cd '$work/src' && protoc-c --c_out='$work/gen' badtype.proto" printf 'syntax = "proto3";\nmessage D { string a = 1; int32 b = 1; }\n' > "$work/src/duptag.proto" check_rc protobuf-c/protoc-c-rejects-duplicate-field-number 1 \ sh -c "cd '$work/src' && protoc-c --c_out='$work/gen' duptag.proto" check_rc protobuf-c/protoc-c-rejects-missing-input 1 \ sh -c "cd '$work/src' && protoc-c --c_out='$work/gen' nosuchfile.proto" fi # --------------------------------------------------------------------------- # 3. THE RUNTIME -- one run of the probe, then fork-free globs over its output. probe=$PT/protobuf-c-probe if [ ! -x "$probe" ]; then bst_notok protobuf-c/probe-runs "not executable: $probe" else pout=$("$probe" "$refwire" 2>&1); prc=$? if bst_execfail "$pout"; then bst_unrun protobuf-c/probe-runs "$pout" else if [ "$prc" -eq 0 ]; then bst_ok protobuf-c/probe-runs else bst_notok protobuf-c/probe-runs "rc=$prc"; fi # One verdict per probe case. The probe prints `PC- OK`, `PC- # FAIL: ` or `PC- SKIP: `; a case that printed NONE of # those is a red too -- silence is how a probe that died half-way would # otherwise pass. for c in build/serialize build/nonempty \ wire/bytesize-agrees wire/deterministic wire/default-elided \ wire/reference-readable wire/matches-x86-64 \ parse/reference-parses wire/matches-protobuf-cxx \ parse/roundtrip \ read/name read/hp read/mana-default read/color read/alive \ read/weight read/depth read/id read/packed-inventory \ read/submessages read/blob-binary \ parse/reserialises-identically \ verify/no-truncation-accepted-as-whole \ verify/truncation-rejected-somewhere \ verify/reject-zero-tag verify/reject-overlong-length \ verify/reject-runaway-varint do case "$pout" in *"PC-$c OK"*) bst_ok "protobuf-c/$c" ;; *"PC-$c SKIP"*) bst_skip "protobuf-c/$c" \ "$(printf '%s\n' "$pout" | sed -n "s|^PC-$c SKIP: ||p" | head -1)" ;; *"PC-$c FAIL"*) bst_notok "protobuf-c/$c" \ "$(printf '%s\n' "$pout" | sed -n "s|^PC-$c FAIL: ||p" | head -1)" ;; *) bst_notok "protobuf-c/$c" "probe printed no verdict for this case" ;; esac done # The probe's own tally, so a case added to the probe but not listed above # cannot fail unnoticed. Skips do not count towards it. case "$pout" in *"PC-SUMMARY failures=0"*) bst_ok protobuf-c/probe-summary ;; *"PC-SUMMARY"*) bst_notok protobuf-c/probe-summary \ "$(printf '%s\n' "$pout" | sed -n 's|^PC-SUMMARY ||p' | head -1)" ;; *) bst_notok protobuf-c/probe-summary "probe printed no summary line" ;; esac printf 'BST-DIAG protobuf-c/truncation %s\n' \ "$(printf '%s\n' "$pout" | sed -n 's|^PC-DIAG truncation ||p' | head -1)" printf 'BST-DIAG protobuf-c/sizes %s\n' \ "$(printf '%s\n' "$pout" | sed -n 's|^PC-DIAG sizes ||p' | head -1)" printf 'BST-DIAG protobuf-c/version %s\n' \ "$(printf '%s\n' "$pout" | sed -n 's|^PC-DIAG version ||p' | head -1)" fi fi rm -rf "$work"