. "$BST_LIB" # BST-TIMEOUT: 1200 # ONE python3 start, and that is nearly the whole cost. # # Everything else reads the filesystem and dpkg with shell # # builtins or greps the single probe run's output. The # # probe's own work after the start is 102 sha256 digests # # over a 400 kB payload -- 100 of them the locale # # catalogues, none over 4 kB -- one gzip write of 396 # # bytes, and a dozen calls into pure-Python code. That is # # nothing beside the interpreter load. 1200 matches the # # sibling 99-python3-sphinxcontrib-jquery for the same # # reason: the start 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-sphinxcontrib-devhelp 1.0.6 (poky/meta recipes-devtools/python/ # python3-sphinxcontrib-devhelp_1.0.6.bb, `inherit pypi python_flit_core', NO # meta-brintos bbappend) on the wasm32-hwjs glibc image -- THE SPHINX BUILDER # THAT EMITS A GNOME DEVHELP BOOK. It is one of the five builders split out of # Sphinx core into their own distributions, and its deliverable is a single # gzip'd XML file, `.devhelp.gz', beside the HTML tree Devhelp browses. # # ============================================================================ # THE ONE FACT THAT SHAPES THIS WHOLE SUITE: THE MODULE CANNOT BE IMPORTED HERE. # ============================================================================ # # sphinxcontrib/devhelp/__init__.py pulls EIGHT names out of Sphinx at MODULE # scope -- addnodes, Sphinx, StandaloneHTMLBuilder, IndexEntries, # get_translation, logging, NodeMatcher and make_filename. This image installs # no Sphinx: python3-sphinx exists in poky and is not in # core-image-brintos-emcraft, and no ticket in this Epic ports it. So # # python3 -c 'import sphinxcontrib.devhelp' # # raises ModuleNotFoundError: No module named 'sphinx', and `import-needs-sphinx' # below asserts exactly that rather than working around it. It is the measured # state of this image, not a defect in the port. # # THE OBVIOUS REPAIR IS THE WRONG ONE, and the sibling port BRINTOS-2106 settled # this for the family. `RDEPENDS:${PN} += "python3-sphinx"' in a bbappend would # correct the package and drag the whole unported Sphinx tree into the shared # batch image -- a graph-wide cost paid by every member of the round, to make # importable a module nothing on this image imports. So the disagreement is left # STATED in the image recipe and here, and `pkg-depends-no-sphinx' is the # tripwire: it asserts the Depends line does NOT name python3-sphinx, and goes # red together with `import-needs-sphinx' on the day Sphinx lands, which is the # day the decision is reopened. # # WHAT THIS PACKAGE DECLARES IS NOT WHAT ITS CODE NEEDS, AND THAT IS UPSTREAM'S # DOING RATHER THAN OE's. pyproject.toml says `dependencies = []' and puts # `Sphinx>=5' under the `standalone' EXTRA, so the installed METADATA carries no # unconditional Requires-Dist at all -- `dist-requires-unconditional' below is # the assertion. That is the difference from sibling sphinxcontrib-jquery, whose # METADATA does carry `Requires-Dist: Sphinx>=1.8': there, OE dropped a declared # dependency; here there was never one to drop, and the .deb's `Depends: # python3-core' is a faithful rendering of what upstream declared. # # ============================================================================ # WHAT IS ASSERTED INSTEAD, AND WHY IT IS NOT A WEAKER TEST. # ============================================================================ # # data/python3-sphinxcontrib-devhelp-probe.py installs a stub `sphinx' package # and then measures THIS PACKAGE's code against it. The stub is as thin as the # module allows -- see the probe's header for what each piece stands in for -- # and docutils is NOT stubbed at all: python3-docutils 0.20.1 is on this image, # every node the TOC fixture builds is a real docutils node, and the walk over # it is real docutils traversal through `findall'. # # What that buys is the whole deliverable. build_devhelp() is the only method in # this package that produces a file, and it reads nothing but `self.config', # `self.env' and two collaborators. Both are supplied as fixtures, so THE GZIP'D # XML `xml' COMPARES AGAINST A LITERAL IS PRODUCED BY THE PACKAGE'S OWN CODE, # line for line, on this arm's interpreter. Every formatting decision the # ticket names is in that one string: the nesting of elements from the # toctree, the three arms of the index writer, and the escaping of `&', `<', # `"' and non-ASCII. # # WHAT IS NOT MEASURABLE, STATED PLAINLY RATHER THAN IMPLIED. DevhelpBuilder # extends StandaloneHTMLBuilder, and the HTML tree Devhelp browses is produced # by that base class -- Sphinx's code, not this package's. A stub can say # nothing about it. So the class-shape cases below assert what DevhelpBuilder's # OWN `__dict__' declares and nothing an instance merely inherits. # # THE TICKET NAMES `format' AND IT IS NOT ASSERTED AS A VALUE, deliberately. # DevhelpBuilder does not declare `format'; it inherits it from # StandaloneHTMLBuilder, which is absent here, so any value read for it would be # the stub's own. `builder-format-inherited' pins the INHERITANCE instead, which # is the true statement and stays true against a real Sphinx. That is a # narrowing of the ticket's clause and is recorded as one in the design doc. # # ============================================================================ # NO COMPILED EXTENSION, AND WHY THAT IS A TRIPWIRE RATHER THAN BOILERPLATE. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an EMPTY lib-dynload and no # dynamic loader (BRINTOS-1476), and an empty Py* export surface (BRINTOS-2046). # A module carrying a compiled extension could therefore not be imported here at # all -- not "would be slower", could not be imported. This package is 102 files # of which exactly one is Python source; the other 101 are the locale catalogues # and the wheel metadata. So `pkg-no-so' does not describe today's package, it # fails a version bump that grew an extension, at INSTALL time rather than at # somebody's import three rounds later. # # ============================================================================ # EVERY VALUE BELOW WAS MEASURED, NOT PREDICTED. # ============================================================================ # # The expectations come from the tree unpacked from the .deb THIS recipe built # -- python3-sphinxcontrib-devhelp_1.0.6-r0_wasm32.deb, from the BRINTOS-2110 # recipe-scope build on hz1, 2026-09-02 -- with the probe below run against it # verbatim on CPython 3.12.3 and docutils 0.20.1, the same interpreter minor and # the same docutils this image carries. Nothing asserted is arm-dependent: the # wheel is `py3-none-any', the digests are the wheel's own, the single .py file # is the same bytes on both machines, and the XML is ElementTree's serialisation # of a tree built from constants. The paths are the only substitution and they # are built from $SITE rather than written out. # # The runner's read drops the first line a suite emits, per-suite and # deterministic -- not a defect in the host tool (BRINTOS-1149, filed 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 # stands in that position so the eaten line costs nothing. It sits ahead of the # `require' guard 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. P=python3-sphinxcontrib-devhelp bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages NSDIR=$SITE/sphinxcontrib PKGDIR=$NSDIR/devhelp LOCALES=$PKGDIR/locales DI=$SITE/sphinxcontrib_devhelp-1.0.6.dist-info PKGLIST=/var/lib/dpkg/info/python3-sphinxcontrib-devhelp.list PROBE=$BST_DATA/python3-sphinxcontrib-devhelp-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-scdevhelp.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. The probe's `module-answered' further down is # what says the interpreter agreed; these say what the filesystem holds, so a # package that answered out of some other path on sys.path is distinguishable # from one that answered out of what this image installed. check $P/site-packages-dir test -d "$SITE" # THE NAMESPACE DIRECTORY, AND THE FILE THAT MUST NOT BE IN IT. `sphinxcontrib' # is a PEP 420 implicit namespace package: a directory with NO __init__.py, # which is how the whole sphinxcontrib-* family -- jsmath, serializinghtml, # applehelp, devhelp, htmlhelp, qthelp and jquery, five of them sibling tickets # in this very round -- shares one top-level name without any single # distribution owning it. A release that started shipping sphinxcontrib/ # __init__.py would shadow all of them, and would pass every other case here. check $P/namespace-dir test -d "$NSDIR" check $P/namespace-no-init test ! -f "$NSDIR/__init__.py" check $P/package-dir test -d "$PKGDIR" # THE ONE SOURCE FILE. Everything that executes in this package is in it: the # DevhelpBuilder class and the setup() Sphinx calls to register it. check $P/src-init test -s "$PKGDIR/__init__.py" # The precompiled bytecode. OE byte-compiles the tree with compileall, which # does NOT import -- which matters here more than usual, since importing this # module needs a Sphinx that is not on the image. So the .pyc exists despite the # module being unimportable, and that is correct rather than surprising. check $P/pycache-init test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" # THE LOCALE CATALOGUES. setup() registers locales/ as a message catalog # directory (`catalog-dir-is-package' below), and what makes that registration # mean anything is the compiled .mo files being there: a .po with no .mo beside # it is a translation that never reaches a user. Two languages are named # individually so a red says which half of a pair went missing, and the counts # are asserted in the probe over the whole tree. check $P/locales-dir test -d "$LOCALES" check $P/locales-template test -s "$LOCALES/sphinxcontrib.devhelp.pot" check $P/locales-de-mo test -s "$LOCALES/de/LC_MESSAGES/sphinxcontrib.devhelp.mo" check $P/locales-de-po test -s "$LOCALES/de/LC_MESSAGES/sphinxcontrib.devhelp.po" check $P/locales-ru-mo test -s "$LOCALES/ru/LC_MESSAGES/sphinxcontrib.devhelp.mo" check $P/locales-ru-po test -s "$LOCALES/ru/LC_MESSAGES/sphinxcontrib.devhelp.po" # THE TRANSIFEX CONFIG SHIPS TOO, and it is the one file in this package with a # leading-dot directory and no extension. It is named because `pkg-only-expected' # below has to allow it explicitly: a sieve written from the extensions alone # would report a correct package as carrying an unexpected shape. check $P/locales-tx-config test -s "$LOCALES/.tx/config" # NO COMPILED EXTENSION. See the header -- this is the regression tripwire. # # 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)" # NOT ONE FILE IN THIS PACKAGE IS UNDER /usr/bin. A Sphinx builder installs no # program: it is reached through `sphinx-build -b devhelp', which is Sphinx's # executable and not this package's. The case above names the failure that is # expected; this one catches the shape nobody predicted. check_eq $P/pkg-no-bin "" \ "$(grep -E '^/usr/(bin|sbin)/' "$W/pkgf" 2>/dev/null)" # EVERY REGULAR FILE THE PACKAGE SHIPS IS PYTHON, A GETTEXT CATALOGUE OR ITS # METADATA -- without having to enumerate what an unexpected one might be. dpkg's # .list marks no directory with a trailing slash, so the entries are sieved by # asking the filesystem what each one is; without that `-f' test every directory # in the package reads as an unexpected shape and this case is red on a CORRECT # package (measured on python3-pathspec, BRINTOS-1834). _bad= while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc|*.mo|*.po|*.pot) continue ;; */locales/.tx/config) continue ;; "$DI"/METADATA|"$DI"/RECORD|"$DI"/WHEEL|"$DI"/LICENSE) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-expected "" "$_bad" # ------------------------------------------------------------ the dist-info -- # The installed distribution metadata. The directory name carries the version, # so a mixed install -- new files beside an old dist-info -- shows up as the two # disagreeing rather than as neither being wrong. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -s "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" check $P/dist-info-wheel test -s "$DI/WHEEL" # flit copies the project's LICENSE into the dist-info; a wheel that dropped it # would install a BSD-2-Clause package carrying no licence text on target. check $P/dist-info-license test -s "$DI/LICENSE" # NO entry_points.txt, AND ITS ABSENCE IS THE DESIGN. A Sphinx THEME is # discovered through the `sphinx.html_themes' entry-point group. An EXTENSION is # not discovered at all: the user names it in conf.py's `extensions' list and # Sphinx imports it. So there is nothing to declare, and a file here would mean # the package had changed shape. check $P/no-entry-points test ! -f "$DI/entry_points.txt" # PURE PYTHON, AS THE WHEEL ITSELF DECLARES IT. `Root-Is-Purelib: true' is the # packaging-side statement of the header's claim -- no platform tag, no # extension module, nothing that could need the absent dynamic loader. check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag-py3 "Tag: py3-none-any" cat "$DI/WHEEL" # ...AND ONLY py3. Sibling sphinxcontrib-jquery's wheel is universal and carries # a py2 tag as well; this one requires >=3.9 and tags py3 alone. Asserted as an # absence so the two ports' metadata cannot be confused for each other. check_eq $P/dist-info-no-py2-tag "" \ "$(grep -F 'Tag: py2-none-any' "$DI/WHEEL" 2>/dev/null)" # THE BUILD BACKEND THAT PRODUCED IT. python_flit_core builds the wheel from the # sdist during do_compile, so this line is OE's own build talking, and it agrees # byte-for-byte with the wheel upstream publishes on PyPI (both flit 3.9.0, # checked 2026-09-02). A future poky that swapped the backend would change the # RECORD layout the audit below parses. check_out $P/dist-info-generator "Generator: flit 3.9.0" cat "$DI/WHEEL" # The distribution name is HYPHENATED and the import name is DOTTED -- # `sphinxcontrib-devhelp' installs `sphinxcontrib.devhelp'. flit writes no # top_level.txt, so METADATA's Name is the only packaging-side statement of the # name, and it is not the name anyone imports. check_out $P/dist-info-name "Name: sphinxcontrib-devhelp" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 1.0.6" cat "$DI/METADATA" check_out $P/dist-info-python "Requires-Python: >=3.9" cat "$DI/METADATA" # SPHINX IS DECLARED ONLY UNDER AN EXTRA, which is the packaging fact behind the # header: there is no unconditional dependency here for OE to have dropped. Both # halves are asserted -- the extra exists, and the marker is on the line. check_out $P/dist-info-extra-standalone "Provides-Extra: standalone" cat "$DI/METADATA" check_out $P/dist-info-sphinx-is-extra \ 'Requires-Dist: Sphinx>=5 ; extra == "standalone"' 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-sphinxcontrib-devhelp 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, and # the anchor is exact -- an unanchored match would also take the -dev or -dbg # stanza of the same family. sed -n '/^Package: python3-sphinxcontrib-devhelp$/,/^$/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" check_out $P/pkg-version "Version: 1.0.6-r0" cat "$W/st" check_out $P/pkg-arch "Architecture: wasm32" cat "$W/st" # THE RUNTIME DEPENDENCIES, as dpkg recorded them. python3-core is what # python_flit_core's base contributes and it is the whole of the line -- which # here is a faithful rendering of upstream's own empty `dependencies', not a # dropped declaration. 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" # ...AND THE DEPENDENCY THAT IS NOT ON IT. The tripwire the header describes: the # module imports sphinx at module scope and the .deb declares nothing of the # kind. Asserting the ABSENCE keeps the gap visible and dated instead of leaving # it to a comment, and makes the day python3-sphinx is ported the day this case # goes red and the bbappend decision is reopened. check_eq $P/pkg-depends-no-sphinx "" \ "$(grep -E 'python3-sphinx([ ,]|$)' "$W/dep" 2>/dev/null)" # THE IMAGE TOOK THE RUNTIME PACKAGE ALONE. The recipe BBCLASSEXTENDs to native # and nativesdk and OE splits a -dev and a -dbg from every recipe; none of the # three belongs on a target rootfs. check_eq $P/no-dev-package "" \ "$(sed -n '/^Package: python3-sphinxcontrib-devhelp-dev$/p' /var/lib/dpkg/status 2>/dev/null)" check_eq $P/no-dbg-package "" \ "$(sed -n '/^Package: python3-sphinxcontrib-devhelp-dbg$/p' /var/lib/dpkg/status 2>/dev/null)" # --------------------------------------------------------- the interpreter --- # THE IMPORT FAILS, AND THAT IS THE MEASURED STATE OF THIS IMAGE, NOT A DEFECT. # See the header. Eight names imported out of Sphinx at module scope, no # python3-sphinx in the rootfs, so the interpreter raises ModuleNotFoundError and # exits 1. This case pins the fact; the probe's `bare-import' names the missing # module, so a DIFFERENT import failure -- a SyntaxError, a truncated file -- is # distinguishable from this one rather than hiding behind the same red. check_rc $P/import-needs-sphinx 1 python3 -c 'import sphinxcontrib.devhelp' # ...AND SPHINX ITSELF IS WHAT IS ABSENT, asserted separately so the cause is # stated rather than inferred. A red HERE and a pass above would mean Sphinx had # arrived, which is the event that reopens the bbappend decision. check_rc $P/sphinx-absent 1 python3 -c 'import sphinx' # THE NAMESPACE PACKAGE ITSELF IMPORTS, THOUGH -- it is a directory with no code # in it, so nothing it does can fail. This is what separates "the submodule needs # Sphinx" from "sphinxcontrib is not installed at all", which would look the same # from the case above. check $P/import-namespace python3 -c 'import sphinxcontrib' # AND docutils DOES IMPORT. It is not stubbed in the probe -- every node of the # TOC fixture is a real docutils node and the walk over it is real docutils # traversal -- so this is a precondition of the measurement rather than a # property of this package. A red here explains every probe red below at once. check $P/import-docutils python3 -c 'import docutils' # ---------------------------------------------------------------- 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 # reading an empty file with no traceback to say why. bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" 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 `version' distinguishable # from `version_info' and `dist_version'. 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 package, 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. 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 BARE IMPORT, MEASURED INSIDE THE PROBE TOO, and naming the module that is # missing. `check_rc' above says the import exits 1; this says WHY. check_eq $P/bare-import "ModuleNotFoundError:sphinx" "$(pv bare_import)" # The docutils this measurement ran against, so the evidence names its own # instrument. 0.20.1 is what BRINTOS-1712 put on this image. check_eq $P/docutils-version "0.20.1" "$(pv docutils_version)" # THE VERSION THE RECIPE PINS, three times and independently: the package's own # `__version__', its `__version_info__' tuple, and importlib.metadata over the # installed dist-info. Two of these disagreeing means a mixed install. check_eq $P/version "1.0.6" "$(pv version)" check_eq $P/version-info "1.0.6" "$(pv version_info)" check_eq $P/dist-version "1.0.6" "$(pv dist_version)" check_eq $P/dist-name "sphinxcontrib-devhelp" "$(pv dist_name)" # The package answered out of the files 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 package. check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/package-dir-attr "$PKGDIR" "$(pv package_dir)" # Everything it loaded is .py or .pyc -- the interpreter's own view of the # header's claim, complementing `pkg-no-so', which is the packaging view. check_eq $P/all-pure-python "yes" "$(pv all_pure_python)" # ------------------------------------------------------ the metadata plane --- # NOTHING IS REQUIRED UNCONDITIONALLY. Read through importlib.metadata rather # than grepped, so what is asserted is what the interpreter resolves out of the # installed dist-info. This is the case that makes `pkg-depends-no-sphinx' a # faithful packaging of upstream rather than a dropped declaration. check_eq $P/dist-requires-unconditional "" "$(pv dist_requires_unconditional)" # ...and the full list, every entry marker-guarded, so a release that moved one # out from behind its extra changes this and not only the case above. check_eq $P/dist-requires \ 'flake8\s;\sextra\s==\s"lint",mypy\s;\sextra\s==\s"lint",docutils-stubs\s;\sextra\s==\s"lint",Sphinx>=5\s;\sextra\s==\s"standalone",pytest\s;\sextra\s==\s"test"' \ "$(pv dist_requires)" # AND DECLARES NO ENTRY POINTS. See `no-entry-points' above: an extension is # imported by name from conf.py, never discovered. check_eq $P/ep-count "0" "$(pv ep_count)" # ------------------------------------------------- the namespace package --- # THE PEP 420 FACTS, from the interpreter rather than the filesystem. # `sphinxcontrib' has __file__ None and a _NamespacePath for __path__ -- that is # what an implicit namespace package IS, and it is the mechanism every sibling # sphinxcontrib-* port in this round depends on. `namespace-no-init' above says # no __init__.py is on disk; these say the interpreter agreed and built a # namespace rather than a regular package. check_eq $P/ns-file "None" "$(pv ns_file)" check_eq $P/ns-path-type "_NamespacePath" "$(pv ns_path_type)" check_eq $P/ns-path "$NSDIR" "$(pv ns_path)" # ------------------------------------------------------------------ the API --- # THE PUBLIC SURFACE, sorted and read off the module -- it declares no __all__. # The imported names are in it and that is not a mistake in this expectation: # __init__.py does `from sphinx import addnodes', `import gzip', `import re' and # the rest at module scope, so each is a module attribute like any other. They # are stated rather than filtered out, because a release that changed its import # form would change the surface and this is the case that would say so. # `Sphinx', `StandaloneHTMLBuilder', `IndexEntries', `NodeMatcher', # `get_translation' and `make_filename' being visible here is also the plainest # possible statement of why the bare import fails: six of the eight names are # right there on the module. check_eq $P/api-surface \ "Any,DevhelpBuilder,IndexEntries,NodeMatcher,Sphinx,StandaloneHTMLBuilder,addnodes,annotations,etree,get_translation,gzip,logger,logging,make_filename,nodes,os,package_dir,path,re,setup" \ "$(pv api_surface)" # -------------------------------------------------------- the builder class --- # WHAT DevhelpBuilder DECLARES IN ITS OWN CLASS BODY -- nine names, asserted as a # set. This is read from __dict__ rather than off an instance ON PURPOSE: # anything an instance answers that is not in this set comes from the stub base, # and asserting that would measure the stub instead of the package. check_eq $P/builder-declares \ "add_permalinks,build_devhelp,copysource,embedded,epilog,handle_finish,init,name,supported_image_types" \ "$(pv builder_declares)" # THE NAME IS THE WHOLE OF THE USER INTERFACE: `sphinx-build -b devhelp' is how # anyone reaches this package, and the string is what makes that work. check_eq $P/builder-name "devhelp" "$(pv builder_name)" # `format' IS INHERITED, NOT DECLARED -- see the header. The ticket names it; the # honest statement is this negative plus the base-class identity below, because # StandaloneHTMLBuilder is not on this image and any value read for `format' # would be the stub's. check_eq $P/builder-format-inherited "no" "$(pv builder_format_declared)" check_eq $P/builder-base-is-imported "yes" "$(pv builder_base_is_imported)" # THE THREE OVERRIDES THAT MAKE THIS A DEVHELP BUILD RATHER THAN AN HTML ONE, and # each one is a behaviour: copysource=False keeps the reST out of the book, # add_permalinks=False drops the paragraph anchors Devhelp cannot use, and # embedded=True suppresses the sidebar and relbar a standalone HTML build emits. # A release that flipped any of them would produce a valid HTML tree that is the # wrong artifact, and no file case here would notice. check_eq $P/builder-copysource "False" "$(pv builder_copysource)" check_eq $P/builder-add-permalinks "False" "$(pv builder_add_permalinks)" check_eq $P/builder-embedded "True" "$(pv builder_embedded)" # THE IMAGE TYPES IT SUPPORTS, IN ORDER. Sphinx picks an image candidate by # walking this list, so the ORDER is a preference and not a set: PNG first. The # ticket names this and it is one of the few class facts that is genuinely this # package's own. Devhelp renders no SVG, which is why the list is these three. check_eq $P/supported-image-types "image/png,image/gif,image/jpeg" \ "$(pv supported_image_types)" check_eq $P/supported-image-count "3" "$(pv supported_image_count)" # THE EPILOG is the message `sphinx-build' prints when this builder finishes, and # the only user-facing text the package owns. Its two placeholders are # interpolated by Sphinx AFTER the build, so a release that renamed one would # raise KeyError at the very end of a successful build -- after all the work. check_eq $P/epilog-placeholders "outdir,project" "$(pv epilog_placeholders)" check_eq $P/epilog-lines "4" "$(pv epilog_lines)" check_eq $P/epilog-first 'To\sview\sthe\shelp\sfile:' "$(pv epilog_first)" # init() IS THE ONLY STARTUP STATE THIS CLASS TOUCHES: it calls super().init() # FIRST and then pins both suffixes to .html. The order matters -- the base sets # out_suffix from the config, and doing so after would undo this. All three facts # are in one value so the ordering is asserted rather than only the results. check_eq $P/init-suffixes ".html,.html,super" "$(pv init_result)" # ---------------------------------------------------------------- setup() --- # WHAT SPHINX CALLS AND WHAT IT GETS BACK. setup() is an extension's entry point. # The dict it returns is read by Sphinx itself: `version' is reported in build # output and used to invalidate the cached environment, and the two parallel_* # flags decide whether `sphinx-build -j' may run the extension in parallel. An # extension that returned None would be treated as not parallel-safe and would # silently serialise every parallel build that loaded it -- a performance defect # with no error message, which is exactly the kind this case exists to catch. check_eq $P/setup-returns-dict "dict" "$(pv setup_type)" check_eq $P/setup-keys "parallel_read_safe,parallel_write_safe,version" \ "$(pv setup_keys)" # The version in the dict is the package's own, so a build's extension listing # and the installed dist-info cannot drift apart. check_eq $P/setup-version "1.0.6" "$(pv setup_version)" check_eq $P/setup-parallel-read "True" "$(pv setup_parallel_read)" check_eq $P/setup-parallel-write "True" "$(pv setup_parallel_write)" # THE MINIMUM SPHINX IT DEMANDS. require_sphinx('5.0') is what turns a too-old # Sphinx into one clear error instead of an AttributeError deep in a build. It is # also the only version constraint that reaches a user: METADATA's `Sphinx>=5' is # behind an extra nobody installs. check_eq $P/require-sphinx "5.0" "$(pv require_sphinx)" # THE EXTENSION IT PULLS IN FIRST. setup_extension('sphinx.builders.html') is what # guarantees the base class this builder extends is loaded before add_builder # registers the subclass. check_eq $P/setup-extension "sphinx.builders.html" "$(pv setup_extension)" # THE BUILDER IT REGISTERS -- exactly one, and BY IDENTITY. A release that # registered a different class would still register something called `devhelp', # and only the identity case would see it. check_eq $P/builders-registered "1" "$(pv builders_registered)" check_eq $P/builder-is-devhelp "yes" "$(pv builder_is_devhelp)" # THE MESSAGE CATALOG. The 48 translations under locales/ are reachable only # because of this one call, and the directory it names is asserted to be the one # INSIDE the installed package -- which is what ties the registration to the # files `locales-*' counted. check_eq $P/catalog-count "1" "$(pv catalog_count)" check_eq $P/catalog-name "sphinxcontrib.devhelp" "$(pv catalog_name)" check_eq $P/catalog-dir-is-package "yes" "$(pv catalog_dir_is_package)" # THE CONFIG VALUE IT DECLARES -- exactly one, and every attribute of it. # `devhelp_basename' names the .devhelp.gz a build produces, and its default is a # CALLABLE: Sphinx's idiom for "derive it from the config when it is read", # because the project name is not known at registration time. So what is asserted # is that it is callable and WHAT THE PACKAGE READS OUT OF THE CONFIG -- the # arrow's left half. Asserting the string it returns would be asserting # make_filename, which is Sphinx's function and stubbed here; the right half is # printed only so a red shows both. check_eq $P/cfg-count "1" "$(pv cfg_count)" check_eq $P/cfg-name "devhelp_basename" "$(pv cfg_name)" # `rebuild' tells Sphinx WHICH builder's output is stale when this value changes. # It is the builder's own name, not the usual 'html' or 'env': changing the # basename renames the book and invalidates nothing else. check_eq $P/cfg-rebuild "devhelp" "$(pv cfg_rebuild)" check_eq $P/cfg-default-callable "yes" "$(pv cfg_default_callable)" check_eq $P/cfg-default-reads "project->brintOSGuestDocs" "$(pv cfg_default_reads)" # --------------------------------------------------------- build_devhelp() --- # THE DELIVERABLE. Everything above is registration; this is the only method in # the package that produces a file, and that file IS the port's output. # # The fixtures are in the probe: a real docutils toctree of the shape Sphinx's # resolution produces, and an index covering all three arms of write_index(). # Everything below is what THIS PACKAGE's code made of them, on this arm. check_eq $P/devhelp-file "yes" "$(pv devhelp_file)" # GZIP, NOT PLAIN XML. `gzip.open(..., "w")' is the only write path in the method # and Devhelp reads the compressed form, so a release that dropped the # compression would produce a file no reader could open. Asserted by # decompressing it rather than by the name. check_eq $P/devhelp-gzip "yes" "$(pv devhelp_gzip)" # THE DOCTREE IT ASKED FOR. build_devhelp() resolves the MASTER document with # prune_toctrees=False, which is what makes the whole toctree visible to the walk # rather than only the top level. A release that let it prune would emit a # one-level book and every XML case below would still pass. check_eq $P/env-master-doc "index,prune=False" "$(pv env_call)" # NO XML DECLARATION, and that is ElementTree's documented behaviour rather than # an omission: the encoding is passed positionally as "utf-8" and ET emits a # declaration only for an encoding outside {utf-8, us-ascii, unicode}. Pinned # because a release that started emitting one would move every byte in the file # and `xml' below would red with no explanation. check_eq $P/xml-no-declaration "no" "$(pv xml_has_declaration)" # THE WHOLE DOCUMENT, ON ONE LINE. This is the strongest assertion in the suite: # the literal is what this package's own code produced from the probe's fixtures # on an x86-64 control at CPython 3.12.3 / docutils 0.20.1, and a red here is a # real behaviour change rather than a formatting opinion. Spaces are `\s' and # nothing else is transformed, so a red prints the whole document readably. # # READ IT AS FOUR STATEMENTS AT ONCE: # the attributes come from the config, in the order the Element() call # names them, with `&' and `<' escaped in html_title and the non-ASCII # project name written through as UTF-8; # carries the toctree as NESTED elements -- three at the top # level and two inside the second, which is write_toc() turning list_items # into elements and references into their link and name; # carries all three arms of write_index(): a single-ref entry as # one plain , a subitem emitted with the parent title stripped of # its trailing "(in module devhelp)" by the module's own regexp, an entry # with TWO refs split into "[0] " and "[1] " rows, and an entry with NO refs # emitting nothing at all -- `orphan' is in the fixture and is absent here; # and the escaping is complete: `&' and `<' in a title, `"' inside a link # ("), an em dash and Cyrillic straight through. check_eq $P/xml \ '' \ "$(pv xml)" # ...AND THE SAME DOCUMENT TAKEN APART, so a red names WHICH part moved rather # than only that 793 characters differ. Each of these can fail alone. check_eq $P/xml-length "793" "$(pv xml_len)" check_eq $P/xml-book-attrs \ '' \ "$(pv book_attrs)" # FIVE : three top-level toctree entries and the two nested under the # second. A walk that lost the nesting would emit five at one level and this # count alone would not see it -- which is why the whole document is asserted # above and this is the locator, not the assertion. check_eq $P/xml-sub-count "5" "$(pv sub_count)" # SIX from five index entries: four singles (one of them a subitem) # and one two-ref entry split in two, with the no-ref entry emitting nothing. check_eq $P/xml-function-count "6" "$(pv function_count)" # THE COMPRESSED SIZE. Not an assertion about gzip's tuning -- it is stable for # fixed input at a fixed level -- but the cheapest statement that the file on # disk is the document above rather than a truncated write. check_eq $P/devhelp-bytes "396" "$(pv devhelp_bytes)" # handle_finish() IS WHAT A REAL BUILD CALLS, and all it does is route the # builder's outdir and the config value into build_devhelp. Pinned by recording # the call: a release that passed the source dir, or the basename before the # config default had been resolved, would write the book in the wrong place. check_eq $P/handle-finish "/out/dir,brintosguestdocs" "$(pv handle_finish)" # ------------------------------------------------- the manifest checksums --- # EVERY FILE THE WHEEL SHIPPED, RE-HASHED ON TARGET AND SCORED AGAINST THE # WHEEL'S OWN RECORD -- 102 rows, each a sha256 and a byte count. RECORD's own # row carries no digest (a file cannot hash itself) and is skipped by # construction rather than by name, which is why `record-rows' is 102 and not the # 103 lines in the file. # # This is an end-to-end statement that what upstream published, OE packaged, dpkg # installed and virtiofs served is byte-for-byte the same -- over all 102 files, # 100 of which are the locale catalogues no other case in this suite reads. The # __pycache__ .pyc is correctly absent from RECORD: OE creates it at build time # and the wheel never carried it, which is why the package installs 104 files # while RECORD names 103. check_eq $P/record-rows "102" "$(pv record_rows)" check_eq $P/record-all-found "102" "$(pv record_checked)" check_eq $P/record-none-missing "" "$(pv record_missing)" check_eq $P/record-digests-match "" "$(pv record_bad_digest)" check_eq $P/record-sizes-match "" "$(pv record_bad_size)" # THE CATALOGUES, COUNTED AND PAIRED. 48 compiled .mo and 48 source .po -- one # pair per language. The equality is the point: a .po with no .mo beside it is a # translation nobody ever sees, and a packaging change that dropped one side # would leave every `locales-*' file case above green. check_eq $P/locales-pairs "48,48" "$(pv locales)" # 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" exit 0