. "$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 the wheel build. # # 99-python3 carries 1800 for three starts and 99-sip-module # # 600 for one; this sits between them because the one start # # here also builds a wheel. 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-flit-core 3.9.0 (poky meta/recipes-devtools/python, # python3-flit-core_3.9.0.bb, `inherit pypi python_flit_core`) -- THE PEP 517 # BUILD BACKEND FOR FLIT-PACKAGED PROJECTS, on the wasm32-hwjs glibc image. # # ============================================================================ # 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. flit_core is pure Python, which is the # entire reason it ports. # # So `module-no-so` is not boilerplate. It is a REGRESSION TRIPWIRE on a version # bump: flit_core grows no extension today, and the day a release adds a # compiled speedup this case goes red at INSTALL time rather than at `import # flit_core` 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 BACKEND IS DRIVEN END TO END, AND THE EXPECTATIONS ARE MEASURED. # ============================================================================ # # data/python3-flit-core-probe.py writes a minimal Flit project into a scratch # tree, calls flit_core.buildapi.build_wheel, and prints one key=value per line # about the wheel it got back. The suite asserts on that output. # # EVERY VALUE ASSERTED BELOW WAS MEASURED, on x86-64, against THIS EXACT PROBE # and flit_core 3.9.0 unpacked from the same flit-3.9.0.tar.gz this image's # recipe builds (recorded 2026-08-31, and kept in the ticket's design doc). They # are not predictions and they are not read off the source. What makes the # control transferable is that all of it is pure-Python and content-determined: # the wheel's entry set, its dist-info layout, the Metadata-Version, the # generator string and the py3-none-any tag depend on flit_core's code and on # the fixture's own pyproject.toml, not on pointer width, byte order or the # platform string. Nothing arch-dependent is asserted -- the wheel SIZE is # deliberately bounded rather than pinned, since zip framing is the one thing # that could legitimately shift. # # version=3.9.0 zip_entry_count=4 metadata_version_field=2.1 # wheel_name=bstfixture-1.2.3-py3-none-any.whl record_entry_count=4 # wheel_tag=py3-none-any wheel_generator=flit 3.9.0 # wheel_is_purelib=true dist_info_dir=bstfixture-1.2.3.dist-info # # THE TAG IS THE CASE WORTH READING TWICE. `py3-none-any` says the backend # decided the fixture needs NO ABI and NO platform tag. A backend that had # somehow concluded otherwise would emit a tag naming an ABI this arm cannot # provide, and the wheel would be unusable everywhere while every other case # here stayed green. The fixture pins `requires-python = ">=3.8"` precisely so # this tag is py3-only and deterministic: without it flit_core emits a universal # py2.py3 wheel and the case would assert two tags for no reason. # # ============================================================================ # WHAT IS NOT COVERED, ON PURPOSE. # ============================================================================ # # - `flit`, the full CLI. It is a separate recipe, it is not on this image, # and this port is the BACKEND only -- flit_core's own README says the # public interface is the PEP 517 API and nothing else. # - build_sdist beyond its presence as a hook. The ticket closes on the wheel # path, which is the one every dependent recipe of this round actually uses; # asserting the sdist too would double the probe's cost for a path nothing # on this image takes. # - installing the built wheel. That is python3-installer's port (BRINTOS-1830) # and belongs to its suite, not this one. # # THIS PORT CLOSES ON THIS SUITE: non-interactive, no manual browser pass, no # follow-on ticket under BRINTOS-780. P=python3-flit-core # THE CANARY, which asserts nothing about python3-flit-core. # # 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 FC=$SITE/flit_core PKGLIST=/var/lib/dpkg/info/python3-flit-core.list D=$BST_DATA PROBE=$D/python3-flit-core-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-flitcore.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------ the payload --- # Where the module has to land. `python3 -c 'import flit_core'` 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 "$FC" check $P/module-init test -f "$FC/__init__.py" # The three source files the backend's public surface is made of. Named # individually rather than counted: a count moves on any upstream refactor, # while these three are the API flit_core documents. check $P/module-buildapi test -f "$FC/buildapi.py" check $P/module-wheel test -f "$FC/wheel.py" check $P/module-config test -f "$FC/config.py" # 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 "$FC" -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. # # TWO THINGS HERE WERE MEASURED OFF THE BUILT PACKAGE, not assumed, and each # would have made this case red on a CORRECT package: # # - a dpkg .list names DIRECTORIES too, and without a trailing slash, so a # name-only filter reports every directory in the package as an unexpected # shape. Each entry is therefore tested for being a regular file first. # - flit_core ships `vendor/README`, a plain text file beside its vendored # tomli. It is the only non-Python, non-metadata regular file in the # package (3.9.0, verified against the built .deb), and it is allowed by # name rather than by a wildcard so that a SECOND such file is still a red. _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 ;; */flit_core/vendor/README) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # --------------------------------------------------------- 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-flit-core 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-flit-core` is a PREFIX of `python3-flit-core-tests`, # and an unanchored match would report one stanza as the other. sed -n '/^Package: python3-flit-core$/,/^$/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" # ${PN}-tests IS NOT INSTALLED, and that is a decision rather than an accident: # it carries flit_core/tests/*, which is the upstream test corpus and not the # backend. Asserted because an IMAGE_INSTALL that pulled it in would add the # corpus to the image for no deliverable, and nothing else here would notice. sed -n '/^Package: python3-flit-core-tests$/,/^$/p' /var/lib/dpkg/status > "$W/stt" 2>/dev/null check $P/pkg-no-tests test ! -s "$W/stt" # ---------------------------------------------------------- the interpreter -- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If flit_core 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 flit_core' check $P/import-buildapi python3 -c 'from flit_core import buildapi' # ------------------------------------------------------------- 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 # 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" 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 make `version` and # `metadata_version_field` distinguishable. 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 backend, 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 rather than from dpkg. 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 flit_core. check_eq $P/version "3.9.0" "$(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 flit_core. check_eq $P/module-file "$FC/__init__.py" "$(pv module_file)" # PEP 517's mandatory wheel hooks are present and callable. check_eq $P/hook-build-wheel "yes" "$(pv hook_build_wheel)" check_eq $P/hook-build-sdist "yes" "$(pv hook_build_sdist)" check_eq $P/hook-get-requires "yes" "$(pv hook_get_requires_for_build_wheel)" # The probe could write its fixture. Separated from the build so a read-only or # full tmpfs is not reported as a backend failure. check_eq $P/fixture "yes" "$(pv fixture_ok)" # ------------------------------------------------- the backend built a wheel -- # THE DELIVERABLE. build_wheel returned, and it returned the BASENAME PEP 517 # requires, naming a file that is actually on disk. check_eq $P/wheel-name "bstfixture-1.2.3-py3-none-any.whl" "$(pv wheel_name)" check_eq $P/wheel-exists "yes" "$(pv wheel_exists)" # The size is BOUNDED, not pinned: zip framing is the one part of this that could # legitimately differ between arms, and a pinned byte count would be asserting # the control's zip rather than this backend's behaviour. The x86-64 control # measured 1043 bytes. What the bound catches is a truncated or empty wheel. _sz=$(pv wheel_size) case "$_sz" in ''|*[!0-9]*) bst_notok $P/wheel-size "build_wheel reported no usable size (got '$_sz')" ;; *) if [ "$_sz" -gt 400 ] && [ "$_sz" -lt 65536 ]; then bst_ok $P/wheel-size else bst_notok $P/wheel-size "wheel is $_sz bytes, outside the 400..65536 sanity range (x86-64 control: 1043)"; fi ;; esac # IT IS A VALID ZIP, and intact rather than merely parseable. zip_intact comes # from zipfile.testzip(), which re-reads and CRCs every member -- so this is the # case that says the deflate stream is sound, where wheel_is_zip only says the # central directory was found. zlib is builtin in this interpreter (99-python3 # asserts that), so a red here is flit_core's or the arm's, not a missing module. check_eq $P/wheel-is-zip "yes" "$(pv wheel_is_zip)" check_eq $P/wheel-intact "yes" "$(pv zip_intact)" # THE WHEEL'S CONTENTS, as a set. Asserted whole rather than by counting: a # missing METADATA and an extra stray file are different defects and a count # reports them identically. check_eq $P/wheel-entries \ "bstfixture-1.2.3.dist-info/METADATA,bstfixture-1.2.3.dist-info/RECORD,bstfixture-1.2.3.dist-info/WHEEL,bstfixture.py" \ "$(pv zip_entries)" check_eq $P/wheel-entry-count "4" "$(pv zip_entry_count)" check_eq $P/wheel-has-module "yes" "$(pv zip_has_module)" check_eq $P/wheel-dist-info "bstfixture-1.2.3.dist-info" "$(pv dist_info_dir)" # ------------------------------------------------------------- the METADATA -- # The ticket's requirement in its own terms: the wheel's METADATA carries the # fixture's name and version. The summary is asserted alongside because it is the # field flit_core reads from a DIFFERENT part of pyproject.toml, so the three # together say the backend parsed the whole [project] table rather than echoing # the filename it was given. check_eq $P/metadata-name "bstfixture" "$(pv metadata_name)" check_eq $P/metadata-version "1.2.3" "$(pv metadata_version)" check_eq $P/metadata-summary "brintOS flit_core self-test fixture" "$(pv metadata_summary)" check_eq $P/metadata-version-field "2.1" "$(pv metadata_version_field)" # --------------------------------------------------------------- the RECORD -- # The ticket's other requirement: the record lists the fixture module. RECORD is # the wheel's own manifest, so an entry missing here makes the wheel uninstallable # by any PEP 427 installer even though the member is present in the zip -- which # is exactly the split wheel-has-module above cannot see. check_eq $P/record-has-module "yes" "$(pv record_has_module)" check_eq $P/record-has-metadata "yes" "$(pv record_has_metadata)" # RECORD lists ITSELF, with an empty hash and size. Required by PEP 427 and easy # for a backend to omit; python3-installer (BRINTOS-1830) will depend on it. check_eq $P/record-has-itself "yes" "$(pv record_has_itself)" check_eq $P/record-entry-count "4" "$(pv record_entry_count)" # ------------------------------------------------------------ the WHEEL file -- # See the header: the tag is the load-bearing one. py3-none-any says the backend # asked for no ABI and no platform -- the only answer this arm could satisfy. check_eq $P/wheel-tag "py3-none-any" "$(pv wheel_tag)" check_eq $P/wheel-purelib "true" "$(pv wheel_is_purelib)" # The generator string carries flit_core's own version, so this is a SECOND, # independent statement of the version -- from the artifact rather than from the # module's __version__ attribute. The two disagreeing would mean a mixed install. check_eq $P/wheel-generator "flit 3.9.0" "$(pv wheel_generator)" # 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"