# BST-TIMEOUT: 900 . "$BST_LIB" # protobuf 4.25.8 -- Google's Protocol Buffers: the schema-driven serialisation # runtime plus the protoc compiler (meta-oe recipes-devtools/protobuf). See # protobuf_%.bbappend for what the port adds and why. # # --------------------------------------------------------------------------- # TWO HALVES, AND THEY ANSWER DIFFERENT QUESTIONS # # * THE RUNTIME is exercised by protobuf-probe, cross-linked during the port # against the very libprotobuf.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. The # probe is the only way to reach the runtime at all here: protobuf's runtime # is headers plus archives with no CLI of its own -- protoc is the COMPILER # -- and this image ships no C++ compiler. # # * THE COMPILER is exercised by running protoc on the shipped schema and # comparing its generated C++ byte-for-byte against a reference emitted by # an x86-64 protoc of the same release. This is the gengetopt pattern # (BRINTOS-387), and it is the right one for the same reason it was right # for flatc: what a target code GENERATOR can be held to is its output, # since nothing here can compile what it emits. # # WHY TWO 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, because map # serialisation order is explicitly unspecified and would make this a coin # flip. 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 records only the .proto's name as given relative to # its --proto_path, which is why the run below cd's into the schema's # directory and names it bare, exactly as the port did when it produced the # reference. Get that wrong and the comparison measures the path. # # The version guards are preconditions for both, not banner checks: a protoc or # a runtime from a different release would legitimately differ, and every byte # assertion after it would be measuring the wrong thing. # # --------------------------------------------------------------------------- # WHAT IS EXPECTED, AND WHAT WOULD BE A REAL REGRESSION # # THE RUNTIME HALF IS ALL GREEN and no non-pass is tolerated in it: the corpus is # in-memory encode/decode -- no socket, no device, no clock, no thread, and no # ordering any result depends on. # # THE COMPILER HALF IS BLOCKED, and is SKIPPED WITH ATTRIBUTION rather than left # to fail case by case. protoc traps before it reaches its own main, on every # invocation including `protoc --version`, and the fault is in compiler-generated # glue rather than in protobuf: 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 -- # brintos/hwjs-cc#82. Measured, not inferred: the trap is at # __hwjs_ctor_drive._GLOBAL__I_000102, reached from __wasm_call_ctors, and the # 54-byte wrapper contains no loop opcode. # # THE PROBE IS THE CONTROL THAT MAKES THAT ATTRIBUTION STICK. It links the same # libprotobuf.a, carries a ctor of the same name, has a BYTE-IDENTICAL wrapper # apart from callee indices, and completes -- because protoc's link aggregates # thirteen generated .pb.cc descriptor registrations at that priority where the # probe's aggregates a handful. So the runtime cases below are not merely # unaffected by the block; they are the evidence that the library is sound and # the drive loop is not. # # THIS SUITE TURNS GREEN BY ITSELF WHEN #82 LANDS -- the compiler cases are # guarded by one live probe, not by a pinned expectation, so a fixed protoc runs # the full byte-exact comparison with no edit here. # # SIZE IS THE OTHER THING AT RISK, and it is handled in the recipe rather than # tolerated here. protoc links libprotoc, libprotobuf and the whole abseil # closure, so unstripped it is far past the 64 MiB both-slots exec reservation; # the port strips it with llvm-strip. If protoc's failure ever changes shape, # MEASURE THE SIZE FIRST: bst_execfail attributes a staging failure to the # platform, so such a red arrives already labelled UNRUN-* rather than as a # protobuf defect, and that label would be wrong. Today it is NOT that: the # binary stages and starts, and dies in its own constructors. # # Style follows 83-flatbuffers and 75-re2: the probe runs ONCE, its output is # read into a shell variable, and every per-case verdict is a fork-free `case` # glob against it. The runner's per-suite bound is a budget in process spawns. inc=/usr/include/google/protobuf PT=/usr/libexec/protobuf-tests refwire=$BST_DATA/protobuf-expected-wire.bin refhdr=$BST_DATA/protobuf-expected-brintos_probe.pb.h refsrc=$BST_DATA/protobuf-expected-brintos_probe.pb.cc work=${TMPDIR:-/tmp}/bst-protobuf.$$ 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 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 # (`declared N case(s), N-1 reached the console`) 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. It costs # one line of output and means the position that gets eaten never carries an # assertion anyone depends on. Remove this case when 1149 is fixed. bst_ok protobuf/suite-canary # --------------------------------------------------------------------------- # 1. PAYLOAD -- what the packages put on the image. hasfile protobuf/hdr-message "$inc/message.h" hasfile protobuf/hdr-message-lite "$inc/message_lite.h" hasfile protobuf/hdr-descriptor "$inc/descriptor.h" hasfile protobuf/hdr-arena "$inc/arena.h" hasfile protobuf/hdr-wire-format "$inc/wire_format_lite.h" hasfile protobuf/hdr-parse-ctx "$inc/parse_context.h" hasfile protobuf/staticlib /usr/lib/libprotobuf.a hasfile protobuf/staticlib-lite /usr/lib/libprotobuf-lite.a hasfile protobuf/staticlib-protoc /usr/lib/libprotoc.a hasfile protobuf/pkgconfig /usr/lib/pkgconfig/protobuf.pc hasfile protobuf/probe-shipped "$PT/protobuf-probe" hasfile protobuf/schema-shipped "$PT/brintos_probe.proto" hasfile protobuf/reference-wire "$refwire" hasfile protobuf/reference-header "$refhdr" hasfile protobuf/reference-source "$refsrc" # The .pc has to agree with the release both references were produced from -- # the same precondition as the protoc version check below, one layer down. # # 25.8.0, NOT the recipe's 4.25.8, and this was measured rather than assumed -- # the first draft of this suite asserted 4.25.8 and went red against a correct # port. protobuf's C++ release line dropped the leading `4.` at 22.x; protobuf.pc # and protoc both follow the C++ line, and only the RECIPE and package name keep # the old numbering. So this image legitimately carries three spellings of one # release: protobuf_4.25.8.bb, `Version: 25.8.0`, and `libprotoc 25.8`. pcver=$(sed -n 's/^Version: *//p' /usr/lib/pkgconfig/protobuf.pc 2>/dev/null) check_eq protobuf/pc-version "25.8.0" "$pcver" # --------------------------------------------------------------------------- # 2. THE COMPILER. require protoc protobuf/protoc-present # ONE LIVE PROBE DECIDES THIS WHOLE HALF. protoc is blocked by # 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`, so protoc dies in __wasm_call_ctors before reaching main and # every invocation returns 132 (SIGILL) having printed nothing. # # Guarded on a live run rather than pinned to "expected red": when #82 lands, # this branch stops being taken and the full byte-exact comparison below runs # with no edit to this file. pv=$(protoc --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 traps in __hwjs_ctor_drive._GLOBAL__I_000102 before main (rc=$prc$([ -n "$pv" ] && echo ", said: $(_bst_1line "$pv")")); the CTOR-DRIVE lowering drives a coroutinized .init_array ctor once and then executes a bare unreachable. Not a protobuf defect: protobuf-probe below links the same libprotobuf.a, has a byte-identical drive wrapper, and completes" for c in protoc-version protoc-codegen \ protoc-emitted-header protoc-emitted-source \ codegen-header-matches-x86-64 codegen-source-matches-x86-64 \ protoc-idempotent-header protoc-idempotent-source \ protoc-descriptor-set \ protoc-rejects-malformed protoc-rejects-unknown-type \ protoc-rejects-duplicate-field-number protoc-rejects-missing-input do bst_skip "protobuf/$c" "$why" done printf 'BST-DIAG protobuf/protoc-blocked rc=%s size=%s bytes (hwjs-cc#82)\n' \ "$prc" "$(wc -c < /usr/bin/protoc 2>/dev/null)" else check_out protobuf/protoc-version "libprotoc 25.8" protoc --version # THE CODEGEN UNDER TEST. cd + a bare filename, because protoc bakes the .proto # name it was GIVEN into the generated code; the reference was produced by an # x86-64 protoc invoked exactly this way. cp "$PT/brintos_probe.proto" "$work/src/brintos_probe.proto" ( cd "$work/src" && protoc --cpp_out="$work/gen" brintos_probe.proto ) 2>"$work/protoc.err" prc=$? if [ "$prc" -eq 0 ]; then bst_ok protobuf/protoc-codegen elif bst_execfail "$(cat "$work/protoc.err" 2>/dev/null)"; then bst_unrun protobuf/protoc-codegen "$(cat "$work/protoc.err" 2>/dev/null)" else bst_notok protobuf/protoc-codegen "rc=$prc $(_bst_1line "$(cat "$work/protoc.err" 2>/dev/null)")"; fi hasfile protobuf/protoc-emitted-header "$work/gen/brintos_probe.pb.h" hasfile protobuf/protoc-emitted-source "$work/gen/brintos_probe.pb.cc" # THE ASSERTION THIS HALF EXISTS FOR: wasm32 protoc and x86-64 protoc emit the # same bytes for the same schema. check_rc protobuf/codegen-header-matches-x86-64 0 \ cmp -s "$work/gen/brintos_probe.pb.h" "$refhdr" check_rc protobuf/codegen-source-matches-x86-64 0 \ cmp -s "$work/gen/brintos_probe.pb.cc" "$refsrc" # 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.h" "$refhdr" 2>/dev/null; then printf 'BST-DIAG protobuf/codegen-header-diff %s\n' \ "$(cmp "$work/gen/brintos_probe.pb.h" "$refhdr" 2>&1 | head -1)" printf 'BST-DIAG protobuf/codegen-header-size on-target=%s reference=%s\n' \ "$(wc -c < "$work/gen/brintos_probe.pb.h" 2>/dev/null)" "$(wc -c < "$refhdr" 2>/dev/null)" fi if ! cmp -s "$work/gen/brintos_probe.pb.cc" "$refsrc" 2>/dev/null; then printf 'BST-DIAG protobuf/codegen-source-diff %s\n' \ "$(cmp "$work/gen/brintos_probe.pb.cc" "$refsrc" 2>&1 | head -1)" printf 'BST-DIAG protobuf/codegen-source-size on-target=%s reference=%s\n' \ "$(wc -c < "$work/gen/brintos_probe.pb.cc" 2>/dev/null)" "$(wc -c < "$refsrc" 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 a # separate nicety. ( cd "$work/src" && protoc --cpp_out="$work/idem" brintos_probe.proto ) 2>/dev/null check_rc protobuf/protoc-idempotent-header 0 \ cmp -s "$work/idem/brintos_probe.pb.h" "$work/gen/brintos_probe.pb.h" check_rc protobuf/protoc-idempotent-source 0 \ cmp -s "$work/idem/brintos_probe.pb.cc" "$work/gen/brintos_probe.pb.cc" # protoc's OTHER outputs. The descriptor set is the compiler's own serialised # FileDescriptorProto -- which makes it a second, independent path from the # parser to the wire format, produced by protoc rather than by libprotobuf. ( cd "$work/src" && protoc --descriptor_set_out="$work/gen/probe.desc" brintos_probe.proto ) 2>/dev/null hasfile protobuf/protoc-descriptor-set "$work/gen/probe.desc" # 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/protoc-rejects-malformed 1 \ sh -c "cd '$work/src' && protoc --cpp_out='$work/gen' broken.proto" printf 'syntax = "proto3";\nmessage T { nosuchtype x = 1; }\n' > "$work/src/badtype.proto" check_rc protobuf/protoc-rejects-unknown-type 1 \ sh -c "cd '$work/src' && protoc --cpp_out='$work/gen' badtype.proto" printf 'syntax = "proto3";\nmessage D { string a = 1; int32 b = 1; }\n' > "$work/src/duptag.proto" check_rc protobuf/protoc-rejects-duplicate-field-number 1 \ sh -c "cd '$work/src' && protoc --cpp_out='$work/gen' duptag.proto" check_rc protobuf/protoc-rejects-missing-input 1 \ sh -c "cd '$work/src' && protoc --cpp_out='$work/gen' nosuchfile.proto" fi # --------------------------------------------------------------------------- # 3. THE RUNTIME -- one run of the probe, then fork-free globs over its output. probe=$PT/protobuf-probe if [ ! -x "$probe" ]; then bst_notok protobuf/probe-runs "not executable: $probe" else pout=$("$probe" "$refwire" 2>&1); prc=$? if bst_execfail "$pout"; then bst_unrun protobuf/probe-runs "$pout" else if [ "$prc" -eq 0 ]; then bst_ok protobuf/probe-runs else bst_notok protobuf/probe-runs "rc=$prc"; fi # One verdict per probe case. The probe prints `PB- OK` or # `PB- FAIL: `; a case that printed NEITHER 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/reference-readable wire/matches-x86-64 \ wire/deterministic wire/default-elided \ 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 parse/reference-parses \ verify/no-truncation-accepted-as-whole \ verify/truncation-rejected-somewhere \ verify/reject-truncated-fixed64 verify/reject-zero-tag \ verify/reject-overlong-length verify/reject-runaway-varint do case "$pout" in *"PB-$c OK"*) bst_ok "protobuf/$c" ;; *"PB-$c FAIL"*) bst_notok "protobuf/$c" \ "$(printf '%s\n' "$pout" | sed -n "s|^PB-$c FAIL: ||p" | head -1)" ;; *) bst_notok "protobuf/$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. case "$pout" in *"PB-SUMMARY failures=0"*) bst_ok protobuf/probe-summary ;; *"PB-SUMMARY"*) bst_notok protobuf/probe-summary \ "$(printf '%s\n' "$pout" | sed -n 's|^PB-SUMMARY ||p' | head -1)" ;; *) bst_notok protobuf/probe-summary "probe printed no summary line" ;; esac printf 'BST-DIAG protobuf/truncation %s\n' \ "$(printf '%s\n' "$pout" | sed -n 's|^PB-DIAG truncation ||p' | head -1)" printf 'BST-DIAG protobuf/sizes %s\n' \ "$(printf '%s\n' "$pout" | sed -n 's|^PB-DIAG sizes ||p' | head -1)" fi fi rm -rf "$work"