# BST-TIMEOUT: 1200 # 148 cases, 92s measured locally; the nightly's browser run still exceeds 300s . "$BST_LIB" # # gdbm 1.23 — GNU dbm: an on-disk hashed key/value store, its three tools # (gdbmtool, gdbm_dump, gdbm_load), the libgdbm.a / libgdbm_compat.a archives # and the gdbm/ndbm/dbm headers. # # THREE SHAPE FACTS decided how this file is written. All three were measured on # the real payload first, because each would otherwise have produced reds on a # perfectly good port -- or, worse, false greens. # # 1. EVERY gdbmtool INVOCATION EXITS 0, INCLUDING A FAILED DATABASE OPEN. # `gdbmtool DB count` on a database it cannot open prints # "cannot open database ...: Malloc error" and still exits 0. A plain # `check` (exit-0) assertion would therefore pass on a completely broken # gdbm. Every gdbmtool case below asserts on OUTPUT, never on exit status # alone; the rc contract is asserted separately, and only where it is real # (unknown command -> 1, gdbm_dump on a missing file -> 1). # # 2. READ-WRITE mmap OF A FILE FAILS ON THIS ARM; READ-ONLY mmap WORKS. # gdbm maps a database with mmap(MAP_SHARED) and adds PROT_WRITE when the # database is opened for writing. That write mapping returns MAP_FAILED # here, which gdbm reports as "Malloc error" (its label for any failed # map, not a real ENOMEM). So gdbmtool -- which defaults to a read-write, # mmap-on open -- cannot open ANY existing database, while gdbm_dump # (GDBM_READER, PROT_READ) and gdbm_load (which defaults to # GDBM_NEWDB|GDBM_NOMMAP upstream) both work untouched. --no-mmap is the # working path for gdbmtool and is what the functional layers below use. # The gap is asserted in its own layer, written to assert the BEHAVIOUR WE # WANT so those cases stand red until the platform fix lands and then flip # green by themselves. See brintos/linux#30. # # 3. ARCHIVE MEMBER NAMES ARE NOT IN THE SYMBOL-INDEX WINDOW. As for gmp, # only names longer than 15 characters live in the string table; short ones # (falloc.o, hash.o) sit inline in each member header, scattered through the # whole archive. This file flattens the first 4 KB -- which covers both # indexes (libgdbm.a's is 1778 bytes, libgdbm_compat.a's is 280) -- and # asserts only EXPORTED SYMBOLS, never member names. # # Symbol assertions match " " (name plus its trailing separator) rather # than a bare substring, so gdbm_load cannot be satisfied by gdbm_load_from_file # and gdbm_dump cannot be satisfied by gdbm_dump_to_file. That substring trap is # the one the gmp control caught on pc-libdir. # require gdbmtool gdbm/present T=${TMPDIR:-/tmp}/bst-gdbm.$$ mkdir -p "$T" # check_out with an EMPTY needle is vacuous -- every string contains "" -- so it # degrades to "exited 0", which shape fact 1 says is true even when gdbmtool # could not open the database at all. A first draft of this file used it for the # silent-success cases and they passed against a gdbm that had done nothing. # This is the assertion those cases need: exit 0 AND no gdbm diagnostic. gdbm_quiet() { _qid=$1; shift _qout=$("$@" 2>&1); _qrc=$? if [ "$_qrc" -eq 0 ] && ! printf '%s\n' "$_qout" \ | grep -qE 'Malloc error|cannot open|[Ff]ailed|Error|error'; then bst_ok "$_qid" else bst_notok "$_qid" "rc=$_qrc $(_bst_1line "$_qout")" fi } # A tool that signals by exit status cannot be asserted with check_out (which # also requires rc=0), so run it through a wrapper that keeps the output and # swallows the status; the status itself is asserted separately by check_rc. out_of() { "$@" 2>&1 || true; } # ---------------------------------------------------------------- 1. the tools check_out gdbm/tool-version "gdbmtool (gdbm) 1.23" gdbmtool --version check_out gdbm/dump-version "gdbm_dump (gdbm) 1.23" gdbm_dump --version check_out gdbm/load-version "gdbm_load (gdbm) 1.23" gdbm_load --version # ------------------------------------------------- 2. core database operations # --no-mmap throughout: shape fact 2. Everything here is the real engine -- # hashing, bucket split, directory, avail list -- not a wrapper. G="gdbmtool --norc --quiet --no-mmap" printf 'store alpha 111\nstore beta 222\nstore gamma 333\n' > "$T/mk" gdbm_quiet gdbm/store-script $G -f "$T/mk" "$T/db" check_out gdbm/count-3 "There are 3 items in the database." $G "$T/db" count check_out gdbm/fetch "222" $G "$T/db" fetch beta check_out gdbm/fetch-2 "333" $G "$T/db" fetch gamma check_out gdbm/list-alpha "alpha" $G "$T/db" list check_out gdbm/list-beta "beta" $G "$T/db" list gdbm_quiet gdbm/store-one $G "$T/db" store delta 444 check_out gdbm/count-4 "There are 4 items in the database." $G "$T/db" count check_out gdbm/fetch-new "444" $G "$T/db" fetch delta gdbm_quiet gdbm/delete $G "$T/db" delete alpha check_out gdbm/count-3b "There are 3 items in the database." $G "$T/db" count check_out gdbm/gone "No such item found" $G "$T/db" fetch alpha # A replace must not grow the database -- it is the GDBM_REPLACE path, not insert. gdbm_quiet gdbm/replace $G "$T/db" store beta 999 check_out gdbm/replaced "999" $G "$T/db" fetch beta check_out gdbm/count-3c "There are 3 items in the database." $G "$T/db" count # Singular/plural wording is gdbm's own, and a one-item database is the case # where an "items" assertion would silently pass on a broken count. check_out gdbm/count-one "There is 1 item in the database." \ sh -c "$G '$T/one' store solo 1 >/dev/null 2>&1; $G '$T/one' count" # 3. the on-disk format is a real GDBM file, not an empty shell check_out gdbm/header-magic "13579acf" $G "$T/db" header check_out gdbm/header-block "block size" $G "$T/db" header check_out gdbm/header-type "GDBM" $G "$T/db" header check_out gdbm/hash "hash value" $G "$T/db" hash beta check_out gdbm/lib-version "GDBM version 1.23." $G "$T/db" version check gdbm/file-exists test -s "$T/db" # The magic word read straight off the file, independently of the tool that # wrote it. header_magic is the first field of gdbm_file_header (an int at # offset 0), so bytes 0..3 are 0x13579acf little-endian = cf 9a 57 13. There is # no ASCII "GDBM" magic on disk -- asserting one is how a correct database gets # reported broken, which is what a first draft of this file did. head -c 4 "$T/db" > "$T/magic.got" printf '\317\232\127\023' > "$T/magic.want" check gdbm/file-magic-raw cmp -s "$T/magic.got" "$T/magic.want" # 0x13579acf is GDBM_MAGIC64, not GDBM_MAGIC32 (0x13579acd): this arm is 32-bit # but has a 64-bit off_t, so gdbm writes the 64-bit file format. That makes this # an arm witness -- it changes if off_t ever does. check_out gdbm/magic-is-64 "13579acf" $G "$T/db" header # --------------------------------------------------- 4. dump / load round trip # gdbm_dump reads (PROT_READ mmap) and gdbm_load writes (NOMMAP by default), so # both halves work on this arm and the round trip is a genuine end-to-end path. check gdbm/dump gdbm_dump "$T/db" "$T/db.dump" check_out gdbm/dump-header "GDBM dump file" cat "$T/db.dump" check_out gdbm/dump-version2 "version 1.23" cat "$T/db.dump" check_out gdbm/dump-format "#:format=standard" cat "$T/db.dump" check_out gdbm/dump-count "#:count=3" cat "$T/db.dump" check_out gdbm/dump-end "# End of data" cat "$T/db.dump" # Records are base64 in the ascii format: "beta" is YmV0YQ==, "999" is OTk5. check_out gdbm/dump-b64-key "YmV0YQ==" cat "$T/db.dump" check_out gdbm/dump-b64-val "OTk5" cat "$T/db.dump" check gdbm/load gdbm_load "$T/db.dump" "$T/rt.db" check_out gdbm/load-count "There are 3 items in the database." $G "$T/rt.db" count check_out gdbm/load-fetch "999" $G "$T/rt.db" fetch beta check_out gdbm/load-fetch2 "333" $G "$T/rt.db" fetch gamma check gdbm/dump-binary gdbm_dump --format=binary "$T/db" "$T/db.bin" check gdbm/load-binary gdbm_load "$T/db.bin" "$T/bin.db" check_out gdbm/binary-count "There are 3 items in the database." $G "$T/bin.db" count check_out gdbm/binary-fetch "999" $G "$T/bin.db" fetch beta # The binary dump is NOT the ascii dump -- a format flag that silently did # nothing would pass every assertion above. check gdbm/binary-differs sh -c "! cmp -s '$T/db.dump' '$T/db.bin'" # ------------------------------------------------------- 5. the error contract # Where gdbm DOES signal by exit status, assert it; where it does not (shape # fact 1), assert the message instead. Both directions are the contract. check_rc gdbm/rc-nofile 1 gdbm_dump "$T/nosuch.db" "$T/x.dump" check_rc gdbm/rc-malformed 1 gdbm_load "$T/mk" "$T/y.db" check_rc gdbm/rc-badcmd 1 $G "$T/db" nosuchcommand check_out gdbm/err-badcmd "Unknown command" out_of $G "$T/db" nosuchcommand check_out gdbm/err-nofile "gdbm_open failed" out_of gdbm_dump "$T/nosuch.db" "$T/x.dump" check_out gdbm/err-malformed "Malformed data" out_of gdbm_load "$T/mk" "$T/y.db" # The documented false-green: a missing key is rc=0 with a message. check_rc gdbm/rc-missing-is-0 0 $G "$T/db" fetch nokey # ----------------------------------- 6. the read-write mmap gap: brintos/linux#30 # Written to assert what SHOULD happen, so these reds flip green on their own # when the platform fix lands. The read-only case is green today and is what # makes the pair an A/B rather than a bare failure: same file, same tool, only # the PROT_WRITE bit differs. check_out gdbm/ro-mmap-open "There are 3 items in the database." \ gdbmtool --norc --quiet --read-only "$T/db" count check_out gdbm/ro-mmap-fetch "999" \ gdbmtool --norc --quiet --read-only "$T/db" fetch beta check_out gdbm/rw-mmap-open "There are 3 items in the database." \ gdbmtool --norc --quiet "$T/db" count gdbm_quiet gdbm/rw-mmap-store gdbmtool --norc --quiet "$T/db" store epsilon 5 # Independent witness, in a different program: gdbm_load defaults to NOMMAP and # succeeds; -M asks for the same write mapping gdbmtool takes and fails with it. # That this reproduces outside gdbmtool is what makes it a platform fact rather # than one tool's quirk. check gdbm/load-mmap sh -c "gdbm_load --mmap '$T/db.dump' '$T/mm.db'" # reorganize rebuilds through a temp database that gdbm opens WITHOUT # propagating NOMMAP (recover.c passes only GDBM_WRCREAT|CLOEXEC|NUMSYNC| # CLOERROR), so it fails even under --no-mmap and there is no user-side # workaround. Same root cause; it flips green with the rest. # # On its OWN database, deliberately: the two rw-mmap cases above mutate $T/db # only on a platform where the write mapping works, so a reorganize assertion # sharing that file would depend on the very gap it sits next to -- green here # and red once the gap closes. The native control caught exactly that (it stored # epsilon, so the count was 4 rather than 3). This database is built and read # entirely through the working --no-mmap path. printf 'store r1 a\nstore r2 b\nstore r3 c\n' > "$T/rmk" $G -f "$T/rmk" "$T/reorg.db" >/dev/null 2>&1 gdbm_quiet gdbm/reorganize $G "$T/reorg.db" reorganize # The failure must at least be non-destructive: the database still reads back. check_out gdbm/count-after-reorg "There are 3 items in the database." $G "$T/reorg.db" count check_out gdbm/fetch-after-reorg "b" $G "$T/reorg.db" fetch r2 # ...and it should not litter. mkstemp has already created .XXXXXX by the # time the temp-database open fails, and that path returns without unlinking it. check gdbm/reorg-no-litter sh -c "! ls '$T'/reorg.db.?????? >/dev/null 2>&1" # --------------------------------------------------------- 7. the headers ship # /usr/include/gdbm.h IS the API header here -- unlike gmp, this recipe runs no # oe_multilib_header, so there is no wrapper to read around. Asserted rather than # assumed: the version macros and the declarations are in that file itself. H=/usr/include/gdbm.h check gdbm/hdr-present test -s "$H" check_out gdbm/hdr-major "define GDBM_VERSION_MAJOR 1" cat "$H" check_out gdbm/hdr-minor "define GDBM_VERSION_MINOR 23" cat "$H" check_out gdbm/hdr-patch "define GDBM_VERSION_PATCH 0" cat "$H" check_out gdbm/hdr-open "gdbm_open" cat "$H" check_out gdbm/hdr-store "gdbm_store" cat "$H" check_out gdbm/hdr-fetch "gdbm_fetch" cat "$H" check_out gdbm/hdr-firstkey "gdbm_firstkey" cat "$H" check_out gdbm/hdr-count "gdbm_count" cat "$H" check_out gdbm/hdr-strerror "gdbm_strerror" cat "$H" check_out gdbm/hdr-datum "datum" cat "$H" check_out gdbm/hdr-gdbmfile "GDBM_FILE" cat "$H" # The open-mode and store-mode constants callers actually compile against. check_out gdbm/hdr-reader "GDBM_READER" cat "$H" check_out gdbm/hdr-wrcreat "GDBM_WRCREAT" cat "$H" check_out gdbm/hdr-newdb "GDBM_NEWDB" cat "$H" check_out gdbm/hdr-nommap "GDBM_NOMMAP" cat "$H" check_out gdbm/hdr-replace "GDBM_REPLACE" cat "$H" # ndbm and dbm are the compat interfaces --enable-libgdbm-compat builds. They # are the reason libgdbm_compat.a exists, so the headers and the archive must # agree; asserting only one of the two would miss a half-built compat layer. check gdbm/ndbm-present test -s /usr/include/ndbm.h check_out gdbm/ndbm-open "dbm_open" cat /usr/include/ndbm.h check_out gdbm/ndbm-fetch "dbm_fetch" cat /usr/include/ndbm.h check_out gdbm/ndbm-store "dbm_store" cat /usr/include/ndbm.h check_out gdbm/ndbm-type "} DBM;" cat /usr/include/ndbm.h check gdbm/dbm-present test -s /usr/include/dbm.h check_out gdbm/dbm-init "dbminit" cat /usr/include/dbm.h check_out gdbm/dbm-includes "include " cat /usr/include/dbm.h # The recipe's do_install:append creates /usr/include/gdbm/{gdbm,ndbm}.h as # relative symlinks so packages that #include resolve. A symlink # that exists but dangles is the failure this pair is for -- test -f follows it, # and reading through it proves the target is the real header. check gdbm/link-gdbm-is-link test -L /usr/include/gdbm/gdbm.h check gdbm/link-ndbm-is-link test -L /usr/include/gdbm/ndbm.h check gdbm/link-gdbm-resolves test -f /usr/include/gdbm/gdbm.h check gdbm/link-ndbm-resolves test -f /usr/include/gdbm/ndbm.h check_out gdbm/link-gdbm-content "GDBM_VERSION_MAJOR" cat /usr/include/gdbm/gdbm.h check_out gdbm/link-ndbm-content "dbm_open" cat /usr/include/gdbm/ndbm.h # --------------------------------------------------------- 8. the archives ship L=/usr/lib/libgdbm.a C=/usr/lib/libgdbm_compat.a check gdbm/lib-present test -s "$L" check gdbm/compat-present test -s "$C" check_out gdbm/lib-archive "!" head -c 8 "$L" check_out gdbm/compat-archive "!" head -c 8 "$C" # Size floors separate a real archive from a stub. Measured: 616422 / 172564. check gdbm/lib-size sh -c "[ \$(wc -c < $L) -gt 400000 ]" check gdbm/compat-size sh -c "[ \$(wc -c < $C) -gt 100000 ]" # Flatten only the index window (shape fact 3), once, for all symbol assertions. IDX="$T/idx"; head -c 4096 "$L" | tr -c '\40-\176\n' ' ' | tr '\n' ' ' > "$IDX" CIDX="$T/cidx"; head -c 4096 "$C" | tr -c '\40-\176\n' ' ' | tr '\n' ' ' > "$CIDX" # The public API, one compiled entry point per layer: open/close, the four # record operations, iteration, count, options, sync, errors, and the dump/load # and reorganize engines the three tools are built on. for s in gdbm_open gdbm_fd_open gdbm_close gdbm_store gdbm_fetch gdbm_delete \ gdbm_exists gdbm_firstkey gdbm_nextkey gdbm_count gdbm_setopt \ gdbm_sync gdbm_reorganize gdbm_strerror gdbm_errno_location \ gdbm_fdesc gdbm_dump gdbm_load gdbm_export gdbm_import; do check_out "gdbm/sym-$s" "$s " cat "$IDX" done # Internals that prove the engine itself compiled, not merely its public faces: # the hash, the bucket layer, the directory allocator and the entry reader. for s in _gdbm_hash _gdbm_get_bucket _gdbm_split_bucket _gdbm_write_bucket \ _gdbm_alloc _gdbm_free _gdbm_read_entry _gdbm_findkey \ _gdbm_full_read _gdbm_full_write _gdbm_cache_init; do check_out "gdbm/sym-$s" "$s " cat "$IDX" done # The compat archive carries BOTH interfaces: ndbm's dbm_* and the older dbm # dbminit/dbmclose. The generic legacy names (fetch/store/delete) are deliberately # NOT asserted -- each is a substring of a dbm_* name, so they would pass on an # archive that shipped only the ndbm half. for s in dbm_open dbm_close dbm_fetch dbm_store dbm_delete dbm_firstkey \ dbm_nextkey dbm_error dbm_clearerr dbm_dirfno dbm_pagfno dbm_rdonly \ dbminit dbmclose; do check_out "gdbm/csym-$s" "$s " cat "$CIDX" done # ------------------------------------------------------------ 9. shipped policy # Static-only: --disable-shared means the plain gdbm and gdbm-compat packages # resolve to shared libraries that are never produced and emit no .deb at all. # If a .so ever appears here the packaging shape changed and -staticdev is no # longer the deliverable. check gdbm/no-shared sh -c "! ls /usr/lib/libgdbm.so* >/dev/null 2>&1" check gdbm/no-compat-so sh -c "! ls /usr/lib/libgdbm_compat.so* >/dev/null 2>&1" # gdbm ships no pkg-config file upstream (unlike gmp). Asserted so that a future # version adding one is noticed rather than silently ignored by consumers. check gdbm/no-pkgconfig sh -c "! ls /usr/lib/pkgconfig/gdbm*.pc >/dev/null 2>&1" # The three tools are the whole of gdbm-bin; gdbm_dump/gdbm_load have no busybox # applet, so a bare invocation is this port and nothing else. check gdbm/bin-gdbmtool test -x /usr/bin/gdbmtool check gdbm/bin-dump test -x /usr/bin/gdbm_dump check gdbm/bin-load test -x /usr/bin/gdbm_load rm -rf "$T"