. "$BST_LIB" # BST-TIMEOUT: 1200 # ONE python3 start, and that is the whole cost. Every # # assertion below either reads the filesystem/dpkg with # # shell builtins or greps the single probe run's output, so # # the bound is one interpreter load plus a parse and three # # serialisations of an 881-byte document. It matches # # 99-python3-tomli-w's 1200 rather than undercutting it: the # # work after the start is comparable and the start is what # # dominates. NOT MEASURED ON TARGET YET -- this port's first # # on-target run is the batch verify, and the measured elapsed # # replaces this note in the test plan rather than in this # # comment, which would cost an image build to keep the # # shipped suite byte-identical. # # python3-tomlkit 0.12.3 (meta-openembedded meta-python, # python3-tomlkit_0.12.3.bb, `inherit pypi python_poetry_core ptest`) -- THE # STYLE-PRESERVING TOML LIBRARY, on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THIS PORT IS FOR, AND WHY THE ASSERTIONS ARE ON BYTES. # ============================================================================ # # This image ALREADY HAS A TOML PARSER -- `tomllib`, in the interpreter's own # stdlib split -- and it has had python3-tomli (BRINTOS-1838) since the same # Epic. tomlkit is not a fourth parser. What it sells is that a document survives # an EDIT AS BYTES: comments, key order and blank-line grouping come back out # unchanged, with only the edited value's bytes different. # # So a suite that parsed the fixture and compared the result against a dict would # be asserting the one thing tomlkit is NOT here for, and would pass just as well # against a library with no style preservation at all. Every functional case # below therefore compares DOCUMENT TEXT, and the expectation for the edit case # is COMPUTED from the fixture (`src.replace("8080", "9090")`) rather than # written out, so the fixture and the expectation cannot drift apart. # # ============================================================================ # WHY THIS PORT IS POSSIBLE AT ALL, AND WHAT THE .so CASE IS REALLY ASSERTING. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an EMPTY lib-dynload and no # dynamic loader (BRINTOS-1476; 99-python3 asserts both directly). A module that # carried a compiled extension therefore could not be imported here -- not # "would be slow", could not be imported. tomlkit is pure Python, which is the # entire reason it ports: the package installs .py files and nothing else. # # So `module-no-so` is not boilerplate. It is a REGRESSION TRIPWIRE on a version # bump, and a live one for THIS package specifically -- tomlkit's whole cost # centre is a hand-written character-at-a-time parser, which is precisely the # shape of code a maintainer eventually rewrites in C or Rust for speed. The day # a release ships that, this case goes red at INSTALL time rather than at # `import tomlkit` on somebody else's ticket three rounds later. It is anchored # on the package's own dpkg file list as well as on a find, because the two fail # differently -- a find catches a file the package does not declare, the list # catches a declared file that is not a .py. # # ============================================================================ # THE BUILD BACKEND IS poetry-core, AND NOTHING ABOUT THAT WAITS ON A PORT. # ============================================================================ # # The recipe's one DEPENDS is python3-poetry-core-native (poky # meta/recipes-devtools/python, 1.9.0), which builds and runs on the BUILD HOST. # Its RDEPENDS -- python3-datetime, python3-profile, python3-stringold -- are all # stdlib splits of the interpreter recipe itself (BRINTOS-1476), already on this # image. That is what makes this a leaf port: nothing in it waits on another # package in this Epic or outside it. # # ============================================================================ # THE EXPECTATIONS BELOW ARE MEASURED, NOT PREDICTED. # ============================================================================ # # Every value asserted here was measured on x86-64 against THIS EXACT PROBE, THIS # EXACT FIXTURE, and THE BYTES THIS RECIPE BUILT -- python3-tomlkit_0.12.3-r0_ # wasm32.deb unpacked and put on PYTHONPATH, not a pip copy and not the upstream # sdist (recorded 2026-08-31; the ticket's design doc carries the transcript). # They are not predictions and they are not read off the source. # # The packaging facts below were read from that same .deb: `Depends: python3-core, # python3-datetime, python3-profile, python3-stringold` (which is what the four # pkg-depends-* cases split), and three packages produced -- python3-tomlkit, # -dev and -dbg, and NO -ptest, which is what pkg-no-ptest asserts stays true. # # What makes the control transferable to wasm32 is that all of it is pure Python # and content-determined: the document tomlkit emits, its length, and the order # it keeps depend on tomlkit's code and on the fixture, not on pointer width, # byte order or the platform string. There is no float in the fixture and no # hash-ordered iteration in the assertions, which are the two places an arch # could otherwise show through. # # version=0.12.3 fixture_bytes=881 readpath_bytes=881 # readpath_exact=yes edit_exact=yes edit_bytes=881 # edit_readback=9090 edit_blank_lines=3 edit_owner_lines=18,19,20 # add_bytes=895 add_line_delta=1 add_lines_lost= # add_server_keys=host,port,enabled,debug add_debug_line=17 # parse_port_type=Integer parse_owner_keys=zulu,name,alpha # # THE LENGTHS ARE PINNED, NOT BOUNDED. The documents here are plain UTF-8 text # with no framing of any kind, emitted by pure Python from a fixed input, so 881 # and 895 are reproducible by construction and a pinned value turns any # formatting drift in a version bump into a red here rather than into a silent # change nobody sees. 881 appearing three times -- the fixture, the read path and # the edit -- is the point, not a coincidence: the edit is same-width on purpose. # # ============================================================================ # WHAT IS NOT COVERED, ON PURPOSE. # ============================================================================ # # - THE ptest PACKAGE. The recipe inherits ptest, but `ptest` is not in this # distro's DISTRO_FEATURES, so no python3-tomlkit-ptest is produced. # `pkg-no-ptest` below asserts it is not installed anyway, because that is the # case that catches a DISTRO_FEATURES change -- the ptest package RDEPENDS on # python3-poetry-core, python3-pytest, python3-pyyaml and # python3-unittest-automake-output, and would drag all four onto this image as # a side effect of a distro-level edit nobody connected to this port. # - TOML 1.0.0 TYPE COVERAGE. That is python3-tomli's port (BRINTOS-1838) and # belongs to its suite. This fixture carries a string, an integer and a bool # because that is all the STYLE property needs; widening it would duplicate # another ticket's coverage and buy nothing here. # - tomlkit's document-CONSTRUCTION API (`tomlkit.document()`, `table()`, # `comment()`). The ticket scopes this port to the edit-in-place path, which # is what the package is used for. # # THIS PORT CLOSES ON THIS SUITE: non-interactive, no manual browser pass, no # follow-on ticket under BRINTOS-780. P=python3-tomlkit # THE CANARY, which asserts nothing about python3-tomlkit. # # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and NOT a defect in the host tool # (BRINTOS-1149 established that and filed it as linux-in-the-browser#177). The # harness computes its pass count as total-minus-failures, so THE DROPPED LINE # IS COUNTED AS A PASS. A nothing-case therefore stands in that position and the # eaten line costs nothing. It sits ahead of the `require` guards deliberately, # because a `require` that skips would emit its skip as the first line and be # eaten too. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages TK=$SITE/tomlkit DI=$SITE/tomlkit-0.12.3.dist-info PKGLIST=/var/lib/dpkg/info/python3-tomlkit.list D=$BST_DATA PROBE=$D/python3-tomlkit-probe.py FIXTURE=$D/python3-tomlkit-fixture.toml tmp=${TMPDIR:-/tmp} W=$tmp/bst-tomlkit.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------ the payload --- # Where the module has to land. `python3 -c 'import tomlkit'` further down is the # functional statement; these are the ones that say WHICH copy answered, so a # module that imported out of some other path is distinguishable from one that # imported out of the package this image installed. check $P/site-packages-dir test -d "$SITE" check $P/module-dir test -d "$TK" check $P/module-init test -f "$TK/__init__.py" # The load-bearing files, named individually rather than counted: a count moves # on any upstream refactor. __init__ and api are the public surface, parser is # the hand-written scanner, and container/items are where the TRIVIA this port # exists to preserve is actually stored -- a release that dropped any of them is # a different library. check $P/module-api test -f "$TK/api.py" check $P/module-parser test -f "$TK/parser.py" check $P/module-container test -f "$TK/container.py" check $P/module-items test -f "$TK/items.py" # The PEP 561 marker. tomlkit ships it, and it is the one non-.py, non-metadata # regular file in the package -- so it is named here rather than discovered as a # surprise by pkg-only-python below. check $P/module-py-typed test -f "$TK/py.typed" # NO COMPILED EXTENSION, ANYWHERE UNDER THE MODULE. See the header: on this image # a .so could not be loaded at all, so this is the case that fails a version bump # which grew one -- loudly, here, instead of at `import` on a later ticket. check_eq $P/module-no-so "" \ "$(find "$TK" -name '*.so' -o -name '*.so.*' 2>/dev/null)" # ...and the same assertion from the PACKAGE's side rather than the filesystem's. # A find sees what is on disk; the dpkg list sees what the package CLAIMS. A # compiled extension that arrived without being declared shows up in the first, # one that is declared but landed elsewhere shows up in the second. # # The list is copied to tmpfs first and never redirected from the rootfs: a # top-level stdin redirect on the virtiofs root makes dup2(2) return 513 and ash # EXITS, ending the suite silently (brintos/linux#73). cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # EVERY REGULAR FILE THE PACKAGE SHIPS UNDER site-packages IS PYTHON OR ITS # METADATA. The two cases above name the failure that is expected; this one # catches the shape nobody predicted -- a binary, a .pyd, a shared archive -- # without having to enumerate what it might be. # # A dpkg .list names DIRECTORIES too, and without a trailing slash, so a # name-only filter would report every directory in the package as an unexpected # shape; each entry is therefore tested for being a regular file first. py.typed # is allowed by its extension rather than by name, matching 99-python3-tomli-w, # so a second PEP 561 marker would not be a red on its own. _bad="" while IFS= read -r _f; do case "$_f" in "$SITE"/*) ;; *) continue ;; esac [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc|*.typed) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ---------------------------------------------------------- the dist-info ---- # All the metadata files are asserted because they are what makes the installed # module discoverable by importlib.metadata; a package whose module landed but # whose metadata did not would import fine here and be invisible to any # dependency resolver on the image. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check $P/dist-info-record test -f "$DI/RECORD" check $P/dist-info-wheel test -f "$DI/WHEEL" # THE VERSION THE RECIPE PINS, from the metadata rather than from the module. # Together with $P/version below -- which reads tomlkit.__version__ -- this is # two independent statements of the same fact, and the two disagreeing would mean # a mixed install: a dist-info from one version beside a module from another. # The directory name carries it a third time, in $DI itself. check_out $P/dist-info-version "Version: 0.12.3" cat "$DI/METADATA" # --------------------------------------------------------- the package ------ # dpkg KNOWS the package. Every case above would look identical if some other # package had installed the same files, so this is the one that says # python3-tomlkit is a real package this image installed rather than an # IMAGE_INSTALL line that resolved to nothing. # # The stanza is read from a tmpfs copy for the same reason as the file list. The # anchor is exact: `python3-tomlkit` is a PREFIX of `python3-tomlkit-ptest`, and # an unanchored match would report one stanza as the other. sed -n '/^Package: python3-tomlkit$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $P/pkg-known test -s "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" # THE RUNTIME DEPENDENCIES THE RECIPE DECLARES, as dpkg recorded them. ONE CASE # PER DEPENDENCY, against the extracted line, rather than one case asserting the # whole list as a string: the recipe's RDEPENDS order is not dpkg's to preserve # and the separator spacing is dpkg's own, so an exact-string case here would be # asserting a FORMAT this phase cannot measure. Split, each red also names which # dependency went missing instead of printing the whole line back. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" check_out $P/pkg-depends-core "python3-core" cat "$W/dep" check_out $P/pkg-depends-datetime "python3-datetime" cat "$W/dep" check_out $P/pkg-depends-profile "python3-profile" cat "$W/dep" check_out $P/pkg-depends-stringold "python3-stringold" cat "$W/dep" # ${PN}-ptest IS NOT INSTALLED. See the header: it is not built at all today, # because ptest is not in this distro's DISTRO_FEATURES -- so this case is a # tripwire on a DISTRO_FEATURES change rather than on anything this port does. # It matters because that package RDEPENDS on four further python packages and # would pull all of them onto the image with nothing else here noticing. sed -n '/^Package: python3-tomlkit-ptest$/,/^$/p' /var/lib/dpkg/status > "$W/stp" 2>/dev/null check $P/pkg-no-ptest test ! -s "$W/stp" # ---------------------------------------------------------- the interpreter -- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If tomlkit cannot be imported then # every probe case below reds at once and each names a different symptom; this # case is what says the cause is the import. It is also the cheapest possible # statement of the deliverable. check $P/import python3 -c 'import tomlkit' # ------------------------------------------------------------- the probe ----- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present # The fixture is the OTHER half of the payload, and it is asserted separately: # without it every functional case below is unscorable, and a single # `probe-present` would not say which of the two was missing. if [ ! -f "$FIXTURE" ]; then bst_notok $P/fixture-present "$FIXTURE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/fixture-present # ONE interpreter start for everything that follows. bst_probe bounds it and # keeps BOTH streams: a probe that dies early otherwise leaves every case below # reporting got:'' and naming a symptom each, with the actual traceback nowhere. # The bound is generous because a python3 start on this arm is tens of seconds # before any work begins. bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" "$W" "$FIXTURE" OUT="$W/probe" # Read a single key. The probe emits each key at most once, so head -n 1 is # defensive rather than load-bearing; the anchors keep `edit_bytes` and # `edit_blank_lines` distinguishable, and `add_ok` from `add_line_delta`. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # THE EVIDENCE PLANE. If the probe emitted nothing, every case below is reading # an absent file: each would red with got:'' and the set would read exactly like # a broken library, when what actually happened is that no measurement was taken. # The word `unscorable` is PARSED -- brintos_local_verify re-measures a suite that # prints it on a fresh guest rather than trusting a shared boot's numbers # (BRINTOS-1644) -- so it goes in this structural check, not in a comment. # # The cases below still print their reds: withdraw, do not hide. This case is # what says what those reds measure. if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi # THE VERSION THE RECIPE PINS, read from the module. A mismatch here means the # recipe and what actually landed have gone out of step, and every expectation in # this file is then describing a different tomlkit. check_eq $P/version "0.12.3" "$(pv version)" # The module answered out of the package this image installed, not from a stray # copy on sys.path. This is what makes the payload cases above and the behaviour # cases below statements about the SAME tomlkit. check_eq $P/module-file "$TK/__init__.py" "$(pv module_file)" # The entry points this port is asserted on. Not the whole public surface as a # set -- tomlkit exports several dozen names, so pinning the set would turn every # upstream addition into a red here without saying anything about whether the # library still works. check_eq $P/api-parse "yes" "$(pv api_parse)" check_eq $P/api-dumps "yes" "$(pv api_dumps)" check_eq $P/api-loads "yes" "$(pv api_loads)" check_eq $P/api-load "yes" "$(pv api_load)" check_eq $P/api-dump "yes" "$(pv api_dump)" # ------------------------------------------------------------- the fixture --- # The fixture arrived intact, and carries exactly the bytes the edit case's # expectation is computed from. A fixture edited without this suite being # revisited reds HERE, with a clear cause, rather than four cases later as a # mysterious byte mismatch. check_eq $P/fixture-read "yes" "$(pv fixture_read)" check_eq $P/fixture-bytes "881" "$(pv fixture_bytes)" check_eq $P/fixture-old-count "1" "$(pv fixture_old_count)" check_eq $P/fixture-new-count "0" "$(pv fixture_new_count)" # ------------------------------------------------------------- the parse ----- check_eq $P/parse-ok "yes" "$(pv parse_ok)" # THE KEY ORDER AS PARSED, before anything is written back. [owner]'s keys are # deliberately not in sorted order in the fixture, so this is the first place a # library that had rebuilt the document from a plain dict would show it. check_eq $P/parse-toplevel-keys "title,revision,server,owner" "$(pv parse_toplevel_keys)" check_eq $P/parse-server-keys "host,port,enabled" "$(pv parse_server_keys)" check_eq $P/parse-owner-keys "zulu,name,alpha" "$(pv parse_owner_keys)" check_eq $P/parse-port "8080" "$(pv parse_port)" # THE VALUE CAME BACK AS A tomlkit ITEM, not as a bare int. This is the case that # says the parse went through tomlkit's document model -- which is the thing that # makes style preservation possible at all -- rather than through a shortcut that # would round trip the value and lose the trivia attached to it. check_eq $P/parse-port-type "Integer" "$(pv parse_port_type)" # ------------------------------------------------- CASE 2: the read path alone # dumps(parse(x)) == x, with nothing touched. The weaker of the two byte # assertions, and it comes first deliberately: if it reds, the edit case below is # measuring style loss that has nothing to do with the edit, and the two reds # together say so. check_eq $P/readpath-bytes "881" "$(pv readpath_bytes)" check_eq $P/readpath-exact "yes" "$(pv readpath_exact)" # ------------------------------------------------------- CASE 1: the edit path # THE DELIVERABLE, IN ONE CASE: one scalar reassigned, and the document back byte # for byte with only that value's bytes changed. check_eq $P/edit-ok "yes" "$(pv edit_ok)" check_eq $P/edit-bytes "881" "$(pv edit_bytes)" check_eq $P/edit-exact "yes" "$(pv edit_exact)" # THE EDIT ACTUALLY HAPPENED. edit-exact would already catch an edit that was # silently dropped -- the expectation carries 9090 and the output would not -- but # it would report it as "style lost", which is the wrong diagnosis for a library # whose style preservation was working perfectly. These name it. check_eq $P/edit-new-present "yes" "$(pv edit_new_present)" check_eq $P/edit-old-absent "yes" "$(pv edit_old_absent)" check_eq $P/edit-readback "9090" "$(pv edit_readback)" # WHICH LINES MOVED, when edit-exact reds. Empty on a clean edit. A byte # comparison says only that the document changed; this says how much of it did, # which is the difference between "one value formatted differently" and "the # whole document was re-serialised". check_eq $P/edit-diff-lines "" "$(pv edit_diff_lines)" check_eq $P/edit-line-count "22" "$(pv edit_line_count_got)" # The style features named one at a time, so a red says WHICH kind of trivia was # dropped. Each is redundant with edit-exact when everything passes and is the # whole point when it does not: a full-line comment, a trailing comment, the # blank-line grouping, and the deliberately-unsorted key order. check_eq $P/edit-line-comment "yes" "$(pv edit_line_comment)" check_eq $P/edit-trailing-comment "yes" "$(pv edit_trailing_comment)" check_eq $P/edit-blank-lines "3" "$(pv edit_blank_lines)" check_eq $P/edit-owner-lines "18,19,20" "$(pv edit_owner_lines)" check_eq $P/edit-owner-order "yes" "$(pv edit_owner_order)" # ------------------------------------------- CASE 3: a NEW key joins its table # It lands at the end of ITS OWN table rather than reordering the document. check_eq $P/add-ok "yes" "$(pv add_ok)" check_eq $P/add-bytes "895" "$(pv add_bytes)" # The new key is last in [server] and the other tables are untouched. Asserted on # the keys read back from the emitted TEXT, so a mapping that iterated correctly # while emitting the key into the wrong table cannot pass. check_eq $P/add-server-keys "host,port,enabled,debug" "$(pv add_server_keys)" check_eq $P/add-toplevel-keys "title,revision,server,owner" "$(pv add_toplevel_keys)" check_eq $P/add-owner-keys "zulu,name,alpha" "$(pv add_owner_keys)" check_eq $P/add-debug-value "False" "$(pv add_debug_value)" # THE NEW KEY IS INSIDE [server], NOT AFTER [owner]. Position in the TEXT, which # is the only thing that distinguishes "appended to the right table" from # "appended to the document" -- a key emitted after the [owner] header would still # parse back into some table and could still iterate in a plausible order. check_eq $P/add-debug-line "17" "$(pv add_debug_line)" check_eq $P/add-debug-after-server "yes" "$(pv add_debug_after_server)" check_eq $P/add-debug-before-owner "yes" "$(pv add_debug_before_owner)" # THE REST OF THE DOCUMENT IS UNTOUCHED BY THE ADD: exactly one line more than # the edited document, none of its lines gone, and the one gained line is the new # key. An add that also reflowed a comment, dropped a blank line or re-quoted a # neighbouring string reds here even though every ordering fact above passes. check_eq $P/add-line-delta "1" "$(pv add_line_delta)" check_eq $P/add-lines-lost "" "$(pv add_lines_lost)" check_eq $P/add-lines-gained "debug = false" "$(pv add_lines_gained)" # ------------------------------------------------------------ the scratch file # dump()/load() over a real file, which is the path a caller editing a config on # disk actually takes. The in-memory cases above are the assertions; these are the # statement that the file wrappers agree with them. check_eq $P/file-exists "yes" "$(pv file_exists)" check_eq $P/file-matches-dumps "yes" "$(pv file_matches_dumps)" # The probe ran to the end. Every case above greps a key, so a probe that died # halfway would leave the remaining keys absent and each case would red with # got:'' -- this case is the one that says the cause was a truncated run. check_eq $P/probe-complete "yes" "$(pv done)" rm -rf "$W"