. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts by default, and the count is the # # whole of the budget: the import case, the probe, and the # # one hook the probe dispatches. `99-python3` declares 1800 # # for three starts; a start on a loaded guest has been # # MINUTES. The count was SIX until 2026-09-01 -- two import # # starts and a probe taking all three PEP 517 hooks -- and # # the suite never reported a case: the host ends a guest # # that sustains memory growth for 120s and killed it in the # # sweep and again on the isolated re-boot, twice over # # (brintos/linux#21, brintos/hardwarejs#20). The three # # sibling python3 suites in the same captures take three # # starts and report whole; this one now matches them. # # BST_PH_ALL_HOOKS=1 restores the other two hooks, and a # # run that sets it wants the old 2400s bound with it. # # STILL NOT MEASURED ON TARGET: this suite has never # # reported a case, so its elapsed is unknown and the # # measured value belongs in the TEST PLAN, not here. # # python3-pyproject-hooks 1.0.0 (poky meta/recipes-devtools/python, # python3-pyproject-hooks_1.0.0.bb, `inherit pypi python_flit_core`) -- THE # LOW-LEVEL LIBRARY THAT CALLS A PEP 517 BACKEND, on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THIS PACKAGE ACTUALLY IS, AND WHY THAT MAKES IT THE UNUSUAL ONE. # ============================================================================ # # Every other pure-Python port in this round is exercised by importing it and # calling it. pyproject_hooks cannot be: its entire mechanism is a SUBPROCESS. # `_impl._call_hook` writes `input.json` into a scratch directory, runs # ` _in_process.py ` with `PEP517_BUILD_BACKEND` in the # environment, and reads `output.json` back. The backend is never imported into # the calling interpreter -- that indirection IS the deliverable, and it is what # lets a frontend build a project whose backend it does not itself depend on. # # So this suite exercises fork(2) + exec(2) through CPython's `subprocess`, three # times, on an arm where `posix_spawn()` is unwired # ([`brintos/linux#14`](https://brintos.io/brintos/linux/issues/14)). It is # expected to work and the reason is specific rather than hopeful: # `subprocess.Popen` takes its `posix_spawn` fast path only when `close_fds` is # FALSE and `cwd` is None, and pyproject_hooks calls `check_call(cmd, cwd=…)` # with `close_fds` at its default True -- so the call lands on # `_posixsubprocess.fork_exec`, and fork+exec is wired here (`41-luaposix` pins a # fork+wait round trip). THAT IS AN ARGUMENT FROM THE SOURCE, NOT A MEASUREMENT. # If it is wrong, `hook-requires` is the case that reds first and its detail # carries the exception, which is why the probe reports every stage's error text. # # ============================================================================ # 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. pyproject_hooks 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: the day a release adds a compiled speedup this case goes red at INSTALL # time rather than at `import pyproject_hooks` 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 THIS DRIVES IS A SIBLING PORT, AND THAT IS DELIBERATE. # ============================================================================ # # The fixture declares `flit_core.buildapi`, which reaches the image through # BRINTOS-1828 in this same batch. Nothing else on this image is a PEP 517 # backend, so there was no independent choice; and using one is the point, since # a hook caller with no backend to call cannot be tested at all. # # `backend-present` exists to make that dependency LOCALISE. Without it, a batch # in which BRINTOS-1828 was dropped would red every hook case here with a # BackendUnavailable and read as a fault in pyproject_hooks. # # ============================================================================ # EVERY EXPECTATION BELOW WAS MEASURED, TWICE, AND NEITHER RUN WAS ON TARGET. # ============================================================================ # # data/python3-pyproject-hooks-probe.py was run on x86-64 (2026-08-31) against # (a) pyproject_hooks 1.0.0 from the same pyproject_hooks-1.0.0.tar.gz this # recipe fetches, and # (b) the pyproject_hooks extracted from THIS build's own .deb, # both with flit_core 3.9.0 from the flit-3.9.0.tar.gz BRINTOS-1828 builds. The # two runs agree on every key but the module's own path. 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 returned dist-info name, the wheel's entry set, its # METADATA fields, the generator string and the py3-none-any tag depend on the # 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=1.0.0 requires_count=0 metadata_version_field=2.1 # metadata_dir_name=bstfixture-1.2.3.dist-info record_entry_count=4 # metadata_dir_files=METADATA,WHEEL zip_entry_count=4 # wheel_name=bstfixture-1.2.3-py3-none-any.whl wheel_generator=flit 3.9.0 # wheel_tag=py3-none-any wheel_is_purelib=true wheel_size=1055 # # THE CASE WORTH READING TWICE IS `dist-info-agrees`. # prepare_metadata_for_build_wheel and build_wheel are separate code paths, and a # frontend routinely calls the first and then the second. A backend that named # the project's dist-info differently between them would produce two names for # one project and every installer downstream would disagree with itself -- while # each hook, taken alone, looked correct. # # ============================================================================ # WHAT IS NOT COVERED, ON PURPOSE. # ============================================================================ # # - build_sdist, build_editable and their get_requires/prepare_metadata # siblings. Each is one more python3 start for a path nothing on this image # takes; the three hooks asserted here are the ones a wheel build goes # through, and they are what the ticket closes on. # - the ERROR channel end to end -- BackendUnavailable from a backend that is # not importable. It is a real part of the contract and it costs a fifth # start; `reject-absolute` and `reject-escape` cover the caller's own # validation for free instead, and the marshalled-exception path is left to # a later ticket that is not paying four starts already. # - 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-pyproject-hooks # THE CANARY, which asserts nothing about python3-pyproject-hooks. # # 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 PH=$SITE/pyproject_hooks DI=$SITE/pyproject_hooks-1.0.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-pyproject-hooks.list D=$BST_DATA PROBE=$D/python3-pyproject-hooks-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-pyprojecthooks.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------ the payload --- # Where the module has to land. `python3 -c 'import pyproject_hooks'` 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 "$PH" check $P/module-init test -f "$PH/__init__.py" check $P/module-impl test -f "$PH/_impl.py" check $P/module-compat test -f "$PH/_compat.py" # THE SUBPACKAGE THE WHOLE MECHANISM RESOLVES THROUGH, and the script inside it. # `_in_process/__init__.py` is what `_impl` imports; `_in_process/_in_process.py` # is the DATA FILE it hands to importlib.resources and then to the subprocess. A # package that shipped the first without the second imports cleanly and dies at # the first hook call, which is why the two are separate cases. check $P/module-inproc-dir test -d "$PH/_in_process" check $P/module-inproc-init test -f "$PH/_in_process/__init__.py" check $P/module-inproc-script test -f "$PH/_in_process/_in_process.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 "$PH" -name '*.so' -o -name '*.so.*' 2>/dev/null)" # THE INSTALLED DIST-INFO. MEASURED off the built .deb, and it is where this # package differs from its sibling: python3-flit-core ships NO top-level # dist-info at all, while python3-pyproject-hooks ships all four files. Asserting # it is not decoration -- python3-installer (BRINTOS-1830) and any importlib # metadata lookup read exactly this directory, and its absence is invisible to # `import`. 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" check $P/dist-info-license test -f "$DI/LICENSE" # The version in the installed metadata, read as a header rather than off the # directory name. The two disagreeing would mean a mixed install. check_out $P/dist-info-version "Version: 1.0.0" cat "$DI/METADATA" # ...and the same .so 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. The # dist-info files are allowed as a class because all four of them # (LICENSE/METADATA/RECORD/WHEEL) are non-Python by construction. _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 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-pyproject-hooks 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. sed -n '/^Package: python3-pyproject-hooks$/,/^$/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 DECLARED RUNTIME DEPENDENCIES, asserted whole. MEASURED from the built # .deb's control stanza. The recipe adds python3-io and python3-json by hand and # the class adds python3-core; all three are stdlib splits of the interpreter # recipe itself, which is what makes this port wait on no other module port. The # case is here because those three are also what carry `tempfile`, `json`, # `subprocess` and `importlib.resources` -- the four this library cannot run # without -- so a split that stopped being pulled in would break the hook path # while `import pyproject_hooks` still worked. check_out $P/pkg-depends "Depends: python3-core, python3-io, python3-json" cat "$W/st" # THERE IS NO ${PN}-tests SPLIT, and that is a fact about this recipe rather than # a decision: pyproject_hooks keeps its test corpus at the sdist's top level, not # inside the package, so nothing lands in a -tests package and none is produced. # Asserted so that an upstream reshuffle which started shipping the corpus on # target is visible here rather than as an unexplained size change. sed -n '/^Package: python3-pyproject-hooks-tests$/,/^$/p' /var/lib/dpkg/status > "$W/stt" 2>/dev/null check $P/pkg-no-tests test ! -s "$W/stt" # ------------------------------------------------- the backend it will call -- # THE SIBLING PORT. See the header: the fixture names flit_core.buildapi, which # BRINTOS-1828 puts on this image. This case is free -- a directory test, not an # interpreter start -- and it exists so that a batch without flit_core reports # ONE red naming the missing backend instead of five naming BackendUnavailable. check $P/backend-present test -d "$SITE/flit_core" # ---------------------------------------------------------- the interpreter -- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If pyproject_hooks 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. # # BOTH IMPORTS IN ONE START. They used to be two, which cost the suite a python3 # start for a fact the second one adds nothing to -- the `from ... import` form # subsumes the plain import, and if the class were missing while the module # imported, $P/api-exports below reports exactly that, in-process, for free. The # start count is the whole budget on this arm (see the probe's header), so a # start that buys no distinguishable failure is a start this suite cannot afford. check $P/import python3 -c 'from pyproject_hooks import BuildBackendHookCaller' # ------------------------------------------------------------- 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 probe run covers both of the starts the header budgets by default -- the # probe itself and build_wheel. The runner 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. # # THIS CALLS THE SHARED bst_probe AGAIN. It briefly did not: a previous pass # copied bst_probe locally with the `sleep 1' removed from its poll, on the # premise -- taken from brintos-selftest's own header -- that `sleep' is a no-op # on this runtime and the poll was therefore a hot fork+exec loop ballooning the # guest. BRINTOS-1868 measured that premise false on this very image: `sleep 1' # takes 0.96s, the poll runs 1.05 iterations/s over a 120s child, and it costs # nothing measurable against a no-poll control. The local copy also did not stop # the wedge -- the guest was killed again, at the same rate -- so what it left # behind was a divergence from the 858-suite corpus with no benefit. The bound # is the sibling suites' 900s rather than 2100s, because the default run now # takes two starts instead of four. 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 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 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 # pyproject_hooks. check_eq $P/version "1.0.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 pyproject_hooks. check_eq $P/module-file "$PH/__init__.py" "$(pv module_file)" # THE PUBLIC SURFACE, whole. Asserted as a set rather than by probing names one # at a time: an export that disappeared and one that was added are different # events and a count reports them identically. These seven are what the module's # own __all__ declares. check_eq $P/api-exports \ "BackendInvalid,BackendUnavailable,BuildBackendHookCaller,HookMissing,UnsupportedOperation,default_subprocess_runner,quiet_subprocess_runner" \ "$(pv api_exports)" # THE SCRIPT THE SUBPROCESS RUNS, resolved the way the library resolves it -- # through importlib.resources, not by a path this suite constructed. On this # image importlib.resources comes from python3-core, so a red here would say the # resource machinery, not the file, is what is missing. check_eq $P/in-process-script "_in_process.py" "$(pv in_process_script)" check_eq $P/in-process-exists "yes" "$(pv in_process_exists)" # sys.executable is what every hook subprocess is started as. An empty one makes # each hook fail in a way that reads as a backend fault, so it is asserted # non-empty here rather than pinned to a path -- the path is the interpreter's # business and could legitimately be either name it installs under. _pyexe=$(pv python_executable) if [ -n "$_pyexe" ]; then bst_ok $P/python-executable else bst_notok $P/python-executable "sys.executable is empty; every hook subprocess would be started as ''"; fi # THE CALLER'S OWN VALIDATION, checked for free -- norm_and_check is string # arithmetic and starts nothing. backend_path is the escape hatch by which a # project points at a backend inside its own tree, and these two are the guards # that keep it inside: an absolute path is refused, and so is one that climbs out # through `..`. check_eq $P/reject-absolute "yes" "$(pv reject_absolute)" check_eq $P/reject-escape "yes" "$(pv reject_escape)" # The probe could write its fixture. Separated from the hooks so a read-only or # full tmpfs is not reported as a hook failure. check_eq $P/fixture "yes" "$(pv fixture_ok)" # THE INDIRECTION, STATED. The probe read the backend out of the fixture's own # pyproject.toml rather than passing a literal -- which is what a frontend does, # and what this library exists to make possible. check_eq $P/backend-declared "flit_core.buildapi" "$(pv backend_declared)" check_eq $P/caller-ok "yes" "$(pv caller_ok)" # ------------------------------------------- hook 1: get_requires_for_build_wheel # THE FIRST SUBPROCESS. See the header: if fork+exec through CPython's subprocess # does not work on this arm, this is the case that reds first, and its detail # carries the exception rather than a symptom. # # The empty list is a statement about the HOOK, not about the fixture: flit_core # returns the project's runtime dependencies here only when version or # description is dynamic, and the fixture pins both, so [] is the documented # answer for it. # # DEFERRED BY DEFAULT. This hook and the next are the two starts the probe drops # unless BST_PH_ALL_HOOKS=1 is set, so that the suite can finish inside the 120s # window the host's guest-growth watchdog measures over; see the probe's header # and $P/hooks-deferred below. build_wheel goes through the identical # _in_process.py subprocess, so the mechanism is still measured. _ph_deferred=$(pv hooks_deferred) _ph_skip="hook deferred: the probe takes one hook start by default so the suite finishes inside the host's 120s guest-growth window (brintos/linux#21, brintos/hardwarejs#20); set BST_PH_ALL_HOOKS=1 to run it" if [ "$_ph_deferred" = "yes" ]; then bst_skip $P/hook-requires-type "$_ph_skip" bst_skip $P/hook-requires-count "$_ph_skip" bst_skip $P/hook-requires "$_ph_skip" else check_eq $P/hook-requires-type "list" "$(pv requires_type)" check_eq $P/hook-requires-count "0" "$(pv requires_count)" check_eq $P/hook-requires "" "$(pv requires)" fi # --------------------------------- hook 2: prepare_metadata_for_build_wheel --- # THE SECOND SUBPROCESS, and the metadata-only path. flit_core implements this # hook natively, so pyproject_hooks' build_wheel FALLBACK is not taken and what # is measured here really is a metadata call. The hook returns the BASENAME of # the directory it created, and the directory has to be there. if [ "$_ph_deferred" = "yes" ]; then bst_skip $P/hook-metadata-name "$_ph_skip" bst_skip $P/hook-metadata-exists "$_ph_skip" else check_eq $P/hook-metadata-name "bstfixture-1.2.3.dist-info" "$(pv metadata_dir_name)" check_eq $P/hook-metadata-exists "yes" "$(pv metadata_dir_exists)" fi # MEASURED: two files, not four. A prepared dist-info is not a wheel's dist-info # -- there is no RECORD, because nothing has been packed yet, and no LICENSE, # because the fixture declares none. Pinning the set is what would catch a hook # that had quietly fallen back to building a wheel and unpacking it. if [ "$_ph_deferred" = "yes" ]; then bst_skip $P/hook-metadata-files "$_ph_skip" bst_skip $P/hook-metadata-pname "$_ph_skip" bst_skip $P/hook-metadata-pversion "$_ph_skip" bst_skip $P/hook-metadata-psummary "$_ph_skip" else check_eq $P/hook-metadata-files "METADATA,WHEEL" "$(pv metadata_dir_files)" check_eq $P/hook-metadata-pname "bstfixture" "$(pv prepared_name)" check_eq $P/hook-metadata-pversion "1.2.3" "$(pv prepared_version)" check_eq $P/hook-metadata-psummary "brintOS pyproject_hooks self-test fixture" "$(pv prepared_summary)" fi # ------------------------------------------------------- hook 3: build_wheel -- # THE THIRD SUBPROCESS AND THE DELIVERABLE. build_wheel returned, and it returned # the BASENAME PEP 517 requires, naming a file that is actually on disk. check_eq $P/hook-wheel-name "bstfixture-1.2.3-py3-none-any.whl" "$(pv wheel_name)" check_eq $P/wheel-exists "yes" "$(pv wheel_exists)" # THE CALLER DID NOT NEED A chdir. flit_core's own buildapi resolves the project # from the CWD, so calling it directly means chdir'ing into the source tree; # pyproject_hooks passes `cwd=` to the subprocess instead and leaves the caller's # CWD alone. This case says it kept that promise -- a frontend that had to chdir # could not build two projects concurrently. check_eq $P/caller-no-chdir "yes" "$(pv cwd_unchanged)" # 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 1055 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: 1055)"; 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 the backend'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 TWO HOOKS AGREE ON THE PROJECT'S DIST-INFO NAME. See the header: this is # the case that catches a split between the metadata path and the build path, # which nothing else here can see because each hook looks correct alone. if [ "$_ph_deferred" = "yes" ]; then bst_skip $P/dist-info-agrees "$_ph_skip -- there is no prepared dist-info name to compare the wheel's against" else check_eq $P/dist-info-agrees "yes" "$(pv dist_info_agrees)" fi # ------------------------------------------------------------- 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 the backend reads from a DIFFERENT part of pyproject.toml, so the three # together say the whole [project] table was parsed rather than the filename # echoed. 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 pyproject_hooks self-test fixture" "$(pv metadata_summary)" check_eq $P/metadata-version-field "2.1" "$(pv metadata_version_field)" # --------------------------------------------------------------- the RECORD -- # 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 -- # py3-none-any says the backend asked for no ABI and no platform -- the only # answer this arm could satisfy. A wheel tagged otherwise would name an ABI # nothing here can provide and be unusable everywhere, while every other case in # this file stayed green. 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 the BACKEND's version, not this library's, so it # is the case that says which backend actually answered the hook call. A # frontend's whole reason for going through pyproject_hooks is that it does not # import the backend itself and so cannot otherwise tell. 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. # WHICH MODE THIS RUN TOOK, as a case rather than only as a skip reason: a reader # of the capture can tell a one-hook run from a three-hook one without counting # skips, and a run whose probe predates this option emits nothing here and reds. if [ -n "$_ph_deferred" ]; then bst_ok $P/hooks-mode-known else bst_notok $P/hooks-mode-known "the probe emitted no hooks_deferred key, so which hooks ran is unknown -- a probe predating BST_PH_ALL_HOOKS shipped beside this suite" fi check_eq $P/probe-complete "yes" "$(pv done)" rm -rf "$W"