. "$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 is 117 sha256 digests over the wheel's # # RECORD -- but 108 of those files are .mo/.po catalogs of # # a few kB each and the whole installed payload is under # # 900 kB -- plus 54 .mo parses, three template renders and # # four small files written into a tmpdir. That is nothing # # beside the interpreter load. It matches # # 99-python3-sphinxcontrib-jquery's 1200 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-htmlhelp 2.0.5 (poky/meta recipes-devtools/python/ # python3-sphinxcontrib-htmlhelp_2.0.5.bb, `inherit pypi python_flit_core', NO # meta-brintos bbappend) on the wasm32-hwjs glibc image -- THE SPHINX BUILDER # THAT EMITS MICROSOFT HTML HELP INPUT. It does not produce a .chm; it produces # the three files hhc.exe compiles into one: the .hhp project, the .hhc table of # contents and the .hhk index, plus the .stp stopword list the full-text search # reads. One of the five builders split out of Sphinx core into sphinxcontrib-*. # # ============================================================================ # THE ONE FACT THAT SHAPES THIS WHOLE SUITE: THE MODULE CANNOT BE IMPORTED HERE. # ============================================================================ # # sphinxcontrib/htmlhelp/__init__.py imports Sphinx at MODULE scope -- fourteen # `from sphinx...' lines -- and then SUBCLASSES sphinx.builders.html. # StandaloneHTMLBuilder, also at module scope. This image does not install # Sphinx: python3-sphinx exists in poky (recipes-devtools/python/ # python3-sphinx_7.2.6.bb) and is in neither core-image-brintos-emcraft nor any # port ticket. So on this rootfs # # python3 -c 'import sphinxcontrib.htmlhelp' # # raises ModuleNotFoundError: No module named 'sphinx', and `import-needs-sphinx' # below asserts exactly that. THAT CASE IS GREEN ON PURPOSE. It is not a defect # being tolerated and it is not a platform gap: it is the measured, expected # state of an image that installs a Sphinx builder without installing Sphinx, # and it is the case that will go RED -- and so demand that this decision be # revisited -- on the day python3-sphinx lands. # # ============================================================================ # WHY THE MISSING DEPENDENCY IS *NOT* THE SAME FINDING AS jquery's. # ============================================================================ # # 99-python3-sphinxcontrib-jquery (BRINTOS-2106) reports a real disagreement # between upstream and OE: its METADATA carries a BARE `Requires-Dist: # Sphinx>=1.8' while its .deb declares only python3-core. THIS PACKAGE IS NOT IN # THAT POSITION, and the difference is worth stating rather than pattern-matching # across the family. Its METADATA carries # # Requires-Dist: Sphinx>=5 ; extra == "standalone" # # and NOTHING unconditional -- `dist-requires-none-unconditional' below asserts # that emptiness directly. The dependency direction upstream intends runs the # other way: Sphinx depends on sphinxcontrib-htmlhelp (it is a vendored builder # that was split out of Sphinx core), so requiring Sphinx back would be a cycle, # and the `standalone' extra exists for the one case where somebody installs the # builder without the application. So `Depends: python3-core' is what upstream's # own metadata asks for, and there is no bbappend to write. # # `pkg-depends-no-sphinx' is still here, and still a tripwire -- but it records # a DIFFERENT fact from jquery's case of the same name: not "OE dropped what # upstream declared", but "this image installs a builder whose application is # absent". It goes red with `import-needs-sphinx' the day python3-sphinx is # ported, which is when this port becomes exercisable end to end and this suite # should grow a real sphinx-build case. # # ============================================================================ # WHY THIS PORT IS POSSIBLE AT ALL. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an EMPTY lib-dynload and no # dynamic loader (BRINTOS-1476; 99-python3 asserts both directly), and its # exported symbol table carries no Py* API at all (BRINTOS-2046). A module # carrying a compiled extension therefore could not be imported here -- not # "would be slow", could not be imported. This package is one 11698-byte .py # file, three Jinja2 templates and 108 message-catalog files, which is the # entire reason it ports, and BRINTOS-2046 does not reach it. The wheel says so # itself: `Root-Is-Purelib: true', tag `py3-none-any', generator `flit 3.9.0'. # # So `pkg-no-so' is not boilerplate. It is a REGRESSION TRIPWIRE on a version # bump, and it goes red at INSTALL time rather than at somebody else's import # three rounds later. # # ============================================================================ # WHAT THE PROBE MEASURES, AND WHERE IT STANDS SOMETHING IN. # ============================================================================ # # The probe installs a stub `sphinx' package and uses the REAL docutils # (python3-docutils 0.20.1 is on this image) and the REAL jinja2 # (python3-jinja2 3.1.6). The line between measured and stood-in-for is drawn in # the probe's own header and is repeated at each case that depends on it. In # short: # # * ENTIRELY THIS PACKAGE'S CODE -- chm_htmlescape(), ToCTreeVisitor over a # real docutils doctree, build_hhx()'s literal .hhk output including its # cp1252 encoding, build_project_file()'s [FILES] sieve, the render context # dicts, init()'s locale lookup, setup()'s registrations. # * STOOD IN FOR -- SphinxRenderer (a plain jinja2 Environment, so the render # cases measure THIS PACKAGE'S TEMPLATES AND CONTEXT and say nothing about # Sphinx's renderer) and make_filename_from_project (a marker, so # `default-basename' asserts only the `+ "doc"' this package contributes). # # ============================================================================ # EVERY VALUE BELOW WAS MEASURED, NOT PREDICTED. # ============================================================================ # # The expectations come from the tree unpacked from the .deb this recipe built # (python3-sphinxcontrib-htmlhelp_2.0.5-r0_wasm32.deb), read on the x86-64 # control with the probe below run against it verbatim, on a sys.path carrying # the docutils, jinja2 and markupsafe .debs THIS IMAGE ships rather than the # host's own. Nothing here is arm-dependent: the wheel is `py3-none-any', the # digests are the wheel's own, and the single .py file is the same bytes on both # machines. 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-htmlhelp bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages NSDIR=$SITE/sphinxcontrib PKGDIR=$NSDIR/htmlhelp TPLDIR=$PKGDIR/templates LOCDIR=$PKGDIR/locales DI=$SITE/sphinxcontrib_htmlhelp-2.0.5.dist-info PKGLIST=/var/lib/dpkg/info/python3-sphinxcontrib-htmlhelp.list PROBE=$BST_DATA/python3-sphinxcontrib-htmlhelp-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-schtmlhelp.$$ 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 imported out of some other path on sys.path is distinguishable # from one that imported 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: it is a directory with NO # __init__.py, which is how the whole sphinxcontrib-* family -- jsmath, # serializinghtml, applehelp, devhelp, jquery and qthelp, every one of them a # sibling ticket in this Epic -- 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: # chm_htmlescape(), ToCTreeVisitor, HTMLHelpBuilder with its four writers, and # setup(), which Sphinx calls to register the builder. check $P/src-init test -s "$PKGDIR/__init__.py" # THE THREE TEMPLATES, each by name. project.hhp and project.hhc are Jinja2 # sources rendered per build; project.stp is a literal file COPIED verbatim -- # it is the stopword list the .chm full-text search excludes from its index. check $P/tpl-hhp test -s "$TPLDIR/project.hhp" check $P/tpl-hhc test -s "$TPLDIR/project.hhc" check $P/tpl-stp test -s "$TPLDIR/project.stp" # ...AND NO project.hhk TEMPLATE, which is the design rather than an omission. # The index is the one output build_hhx() writes byte by byte in Python, with no # template at all, because its structure is data-driven in a way Jinja2 would # not express. `hhk-all' below is the whole of that output. check $P/tpl-no-hhk test ! -f "$TPLDIR/project.hhk" # The message catalogs. 54 languages of console messages, each a compiled .mo # beside the .po it came from. check $P/locales-dir test -d "$LOCDIR" check $P/locale-ja-mo test -s "$LOCDIR/ja/LC_MESSAGES/sphinxcontrib.htmlhelp.mo" check $P/locale-pot test -s "$LOCDIR/sphinxcontrib.htmlhelp.pot" # 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" # NO COMPILED EXTENSION. See the header -- this is the regression tripwire, not # boilerplate. # # 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 EXECUTABLE, AND NONE IS UNDER /usr/bin. A # Sphinx builder installs no program -- `sphinx-build -b htmlhelp' is how it is # invoked, and that program belongs to Sphinx. 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 TEMPLATE, A CATALOG 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. # # `.po' AND `locales/.tx/config' ARE IN THE ALLOW-LIST ON PURPOSE. They are the # gettext SOURCES and a Transifex client configuration -- build-time files that # nothing reads at runtime -- and upstream ships them inside the wheel, so OE # installs them. They are ~48% of the installed file count. This is a packaging # wart, not an OE defect and not something a bbappend should carve out: removing # them would put the installed tree out of agreement with the wheel's RECORD, # which `record-*' below scores in full. _bad= while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc|*.mo|*.po|*.pot) continue ;; */htmlhelp/templates/project.hhp) continue ;; */htmlhelp/templates/project.hhc) continue ;; */htmlhelp/templates/project.stp) continue ;; */htmlhelp/locales/.tx/config) continue ;; */sphinxcontrib_htmlhelp-2.0.5.dist-info/LICENSE) continue ;; */sphinxcontrib_htmlhelp-2.0.5.dist-info/METADATA) continue ;; */sphinxcontrib_htmlhelp-2.0.5.dist-info/RECORD) continue ;; */sphinxcontrib_htmlhelp-2.0.5.dist-info/WHEEL) 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" # THIS ONE SHIPS ITS LICENCE INSIDE THE dist-info, which jquery's does not. It # is a BSD-2-Clause file and the recipe's LIC_FILES_CHKSUM digests the copy in # the sdist; this is the copy that reaches a target, so it is the one a licence # audit of the running image would read. 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 -- python3- # alabaster ships one for exactly that. A BUILDER is not discovered at all: the # user names the package in conf.py's `extensions' list and Sphinx imports it, # then setup() calls app.add_builder(). 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. Unlike # jquery's, this wheel carries the py3 tag ALONE: it requires Python >= 3.9. 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" check_eq $P/dist-info-no-py2 "" \ "$(grep '^Tag: py2-none-any' "$DI/WHEEL" 2>/dev/null)" 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-htmlhelp' installs `sphinxcontrib.htmlhelp'. 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-htmlhelp" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 2.0.5" cat "$DI/METADATA" check_out $P/dist-info-requires-python "Requires-Python: >=3.9" cat "$DI/METADATA" # SPHINX IS DECLARED ONLY AS AN EXTRA. See the header: this is the line that # makes this port's missing-Sphinx situation different from jquery's, and it is # read here as text and again through importlib.metadata in the probe, because # the two fail differently. check_out $P/dist-info-sphinx-extra \ 'Requires-Dist: Sphinx>=5 ; extra == "standalone"' cat "$DI/METADATA" # ...AND NO UNCONDITIONAL Requires-Dist EXISTS AT ALL. Every line carries an # `extra ==' marker, so nothing is required of a plain install. Asserting the # emptiness is what turns "we found no bare requirement" into a measurement. check_eq $P/dist-info-no-bare-requires "" \ "$(grep '^Requires-Dist:' "$DI/METADATA" 2>/dev/null | grep -v 'extra ==')" # ------------------------------------------------------------- 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-htmlhelp 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-htmlhelp$/,/^$/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: 2.0.5-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, # per the header, is what upstream's metadata actually asks for. 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 SPHINX IS NOT ON IT. The tripwire: it goes red together with # `import-needs-sphinx' on the day python3-sphinx is ported, which is when this # builder becomes exercisable end to end and this suite should grow a real # `sphinx-build -b htmlhelp' case. See the header for why it is NOT, today, # evidence of a dropped dependency. 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-htmlhelp-dev$/p' /var/lib/dpkg/status 2>/dev/null)" check_eq $P/no-dbg-package "" \ "$(sed -n '/^Package: python3-sphinxcontrib-htmlhelp-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. Fourteen `from sphinx...' lines at module scope and a # StandaloneHTMLBuilder subclass, no python3-sphinx in the rootfs, so the # interpreter raises ModuleNotFoundError and exits 1. This case is what pins the # fact; the probe's `bare-import' names the missing module, so a DIFFERENT import # failure -- a genuinely broken package -- is distinguishable from this one # rather than hiding behind the same red. check_rc $P/import-needs-sphinx 1 python3 -c 'import sphinxcontrib.htmlhelp' # ...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 everything the header describes. 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' # THE TWO REAL LIBRARIES THE PROBE LEANS ON ARE HERE. docutils is imported by # the module itself at module scope, so it is a genuine runtime dependency of # this package that the .deb also does not declare -- and it happens to be # satisfied, which is why the import failure above names sphinx and not # docutils. jinja2 is what Sphinx's SphinxRenderer is built on; the probe # renders the two templates through a plain jinja2 environment, so a red here # explains the render cases going quiet rather than leaving them unattributed. check $P/docutils-present python3 -c 'import docutils' check $P/jinja2-present python3 -c 'import jinja2' # ---------------------------------------------------------------- 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, and it is the # case that would distinguish a package broken some other way -- a SyntaxError, # a missing file -- from the absent Sphinx this image is expected to have. check_eq $P/bare-import "ModuleNotFoundError:sphinx" "$(pv bare_import)" # The two real libraries, by version. These are the versions the expectations # below were measured against; a bump in either is a reason to re-measure rather # than a defect, and stating them is what makes that visible. check_eq $P/docutils-version "0.20.1" "$(pv docutils_version)" check_eq $P/jinja2-available "yes" "$(pv jinja2_available)" check_eq $P/jinja2-version "3.1.6" "$(pv jinja2_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 "2.0.5" "$(pv version)" check_eq $P/version-info "2.0.5" "$(pv version_info)" check_eq $P/dist-version "2.0.5" "$(pv dist_version)" check_eq $P/dist-name "sphinxcontrib-htmlhelp" "$(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 metadata # 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)" # template_dir is `package_dir/templates', computed at import time, and it is # what every render below reads from. A package that shipped its templates # elsewhere would render nothing and fail with a TemplateNotFound. check_eq $P/template-dir-attr "$TPLDIR" "$(pv template_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 --- # UPSTREAM'S REQUIREMENTS, read through importlib.metadata rather than grepped, # so what is asserted is what the interpreter resolves out of the installed # dist-info -- the other half of the `dist-info-*' cases above. Six entries, all # of them optional extras, sorted. check_eq $P/dist-requires \ 'Sphinx>=5\s;\sextra\s==\s"standalone";docutils-stubs\s;\sextra\s==\s"lint";flake8\s;\sextra\s==\s"lint";html5lib\s;\sextra\s==\s"test";mypy\s;\sextra\s==\s"lint";pytest\s;\sextra\s==\s"test"' \ "$(pv dist_requires)" # AND NOT ONE OF THEM IS UNCONDITIONAL. This is the case the header's whole # argument rests on, and it is why there is no bbappend: a plain install of this # distribution requires nothing but the interpreter. check_eq $P/dist-requires-none-unconditional "none" "$(pv dist_requires_unconditional)" # AND DECLARES NO ENTRY POINTS. See `no-entry-points' above: a builder is # registered by setup() calling app.add_builder(), never discovered. The zero is # asserted because a non-zero would mean the package had changed shape. 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 Epic 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__. # MOST OF THIS LIST IS IMPORTS, and that is not a mistake in the expectation: # the module does `from sphinx.util.nodes import NodeMatcher' and thirteen more # like it at module scope, so every one of those names 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' being visible here is also the plainest possible # statement of why the bare import fails. check_eq $P/api-surface \ "Any,Config,Element,HTMLHelpBuilder,IndexEntries,Node,NodeMatcher,Sphinx,SphinxRenderer,StandaloneHTMLBuilder,ToCTreeVisitor,addnodes,annotations,chm_htmlescape,chm_locales,copy_asset_file,default_htmlhelp_basename,document,get_translation,html,logger,logging,make_filename_from_project,nodes,os,package_dir,path,progress_message,relpath,setup,sphinx,template_dir" \ "$(pv api_surface)" # The callables among them. THREE of these are this package's own -- setup(), # chm_htmlescape() and default_htmlhelp_basename() -- plus the two classes it # defines, HTMLHelpBuilder and ToCTreeVisitor. The rest are imported callables. check_eq $P/api-callables \ "Any,Config,Element,HTMLHelpBuilder,IndexEntries,Node,NodeMatcher,Sphinx,SphinxRenderer,StandaloneHTMLBuilder,ToCTreeVisitor,chm_htmlescape,copy_asset_file,default_htmlhelp_basename,document,get_translation,make_filename_from_project,progress_message,relpath,setup" \ "$(pv api_callables)" # ------------------------------------------------------- the locale table --- # chm_locales maps a Sphinx language code to (LCID, code page). The LCID goes # into the .hhp's `Language=' line as `%#x' and the code page is what all three # output files are written in, so a wrong row is a .chm that renders as mojibake # with no error anywhere in the build. # # TWENTY-NINE LANGUAGES CAN BE COMPILED; FIFTY-FOUR ARE TRANSLATED. That gap is # real and worth knowing: the console messages this package prints are # translated into 54 languages (`mo-langs' below), but only the 29 with a row # here can be BUILT into a .chm, because HTML Help predates Unicode and needs a # code page. A `language = "el"' in conf.py falls back to 0x409/cp1252 and emits # Greek as `&#…' references, which is the `init-unknown' branch below. check_eq $P/locales-count "29" "$(pv locales_count)" check_eq $P/locales-keys \ "ca,cs,da,de,en,es,et,fa,fi,fr,hr,hu,it,ja,ko,lt,lv,nl,no_NB,pl,pt_BR,ru,sk,sl,sv,tr,uk_UA,zh_CN,zh_TW" \ "$(pv locales_keys)" # Ten rows by value, chosen to cover every code page family in the table. # `ru' is the odd one out and is stated for it: its value is the IANA name # `windows-1251' rather than the `cp…' spelling every other row uses, and # upstream's own comment beside it says it is emitted as a . check_eq $P/locale-en "0x409,cp1252" "$(pv locale_en)" check_eq $P/locale-de "0x407,cp1252" "$(pv locale_de)" check_eq $P/locale-ja "0x411,cp932" "$(pv locale_ja)" check_eq $P/locale-ko "0x412,cp949" "$(pv locale_ko)" check_eq $P/locale-ru "0x419,windows-1251" "$(pv locale_ru)" check_eq $P/locale-tr "0x41f,cp1254" "$(pv locale_tr)" check_eq $P/locale-zh-cn "0x804,cp936" "$(pv locale_zh_CN)" check_eq $P/locale-zh-tw "0x404,cp950" "$(pv locale_zh_TW)" check_eq $P/locale-et "0x425,cp1257" "$(pv locale_et)" check_eq $P/locale-cs "0x405,cp1250" "$(pv locale_cs)" # EVERY CODE PAGE THE TABLE NAMES RESOLVES ON THIS IMAGE. OE splits the # interpreter's codecs into a separate python3-codecs package; without it # `cp932', `cp936', `cp949' and `cp950' do not exist and a Japanese or Chinese # .chm build dies at the open() with a LookupError naming a codec rather than a # missing package. This is the one case here that is about the IMAGE rather than # about the package, and it is the reason it is worth the eleven lookups. check_eq $P/codecs-all-present "none" "$(pv codecs_missing)" # ----------------------------------------------------- chm_htmlescape() --- # THE ONE PIECE OF PURE LOGIC THIS PACKAGE OWNS OUTRIGHT, and the one most # likely to break under a differently built interpreter -- it is a thin wrapper # over the stdlib's html.escape() whose entire job is to undo one of that # function's choices. .hhc/.hhk files do not recognise HEX character references, # and html.escape() emits `'' for an apostrophe, so the wrapper rewrites it # to the decimal `''. Every title a help index displays goes through here, so # a regression is silently wrong output rather than a crash. check_eq $P/esc-amp 'a\s&\sb' "$(pv esc_amp)" check_eq $P/esc-lt '<a>' "$(pv esc_lt)" check_eq $P/esc-gt 'x\s>\sy' "$(pv esc_gt)" check_eq $P/esc-quot 'say\s"hi"' "$(pv esc_quot)" # THE APOSTROPHE IS THE WHOLE POINT OF THIS FUNCTION. `'', decimal, not # `''. A .hhk carrying the hex form shows the raw entity text in the index # pane of the compiled help file. check_eq $P/esc-apos 'it's' "$(pv esc_apos)" check_eq $P/esc-mixed '<a\shref='x'>&</a>' "$(pv esc_mixed)" # NON-ASCII IS LEFT ALONE. html.escape() touches only the five markup # characters, so the code-page conversion is left entirely to the open() further # down -- which is where `hhk-arrow-charref' below picks it up. check_eq $P/esc-nonascii 'café\s→\snaïve' "$(pv esc_nonascii)" check_eq $P/esc-empty '' "$(pv esc_empty)" # quote=False leaves BOTH quote characters alone -- including the apostrophe, so # the decimal rewrite has nothing to do. This is the parameter's only observable # effect and it is what the ToCTreeVisitor does NOT use (it passes True). check_eq $P/esc-noquote-apos "it's" "$(pv esc_noquote_apos)" check_eq $P/esc-noquote-quot 'say\s"hi"' "$(pv esc_noquote_quot)" # ...AND NO HEX REFERENCE SURVIVES ANY OF THEM. The rewrite is a single targeted # str.replace, so this is the case that would notice a future html.escape() # emitting some other hex form the wrapper does not know about. check_eq $P/esc-no-hex "yes" "$(pv esc_no_hex)" # ------------------------------------------------------ the builder class --- # HTMLHelpBuilder's class attributes ARE its behaviour: Sphinx reads every one # of them off the class rather than calling a method, so each is a contract with # the application and each has a visible consequence in the compiled .chm. check_eq $P/builder-name "htmlhelp" "$(pv b_name)" # It subclasses the STANDARD HTML BUILDER. That is the whole architecture: the # pages are ordinary Sphinx HTML and this package adds the three index files # beside them. A release that changed the base class would change what a # `-b htmlhelp' build produces for every page, not just for the .hhp. check_eq $P/builder-parent "StandaloneHTMLBuilder" "$(pv b_mro_parent)" # `copysource = False' -- the reST sources are not copied into the output, so # they are not compiled into the .chm. A .chm is shipped to users; the sources # would double its size and publish the doc tree. check_eq $P/builder-copysource "False" "$(pv b_copysource)" # `add_permalinks = False' and `embedded = True' -- no ¶ anchors and no sidebar, # because the HTML Help viewer supplies its own navigation chrome around the # page and the page must not compete with it. check_eq $P/builder-permalinks "False" "$(pv b_add_permalinks)" check_eq $P/builder-embedded "True" "$(pv b_embedded)" # `search = False' -- Sphinx's own JavaScript search index is not generated, # because the .chm has a native full-text search that indexes the compiled # pages. That native search is what project.stp's stopword list feeds. check_eq $P/builder-search "False" "$(pv b_search)" # The class-level defaults init() may override from chm_locales: US English. check_eq $P/builder-lcid "0x409" "$(pv b_lcid)" check_eq $P/builder-encoding "cp1252" "$(pv b_encoding)" # THE THREE IMAGE FORMATS HTML HELP CAN COMPILE, in the builder's own preference # order. SVG is absent and that is correct for the format -- Trident cannot # render it -- so a doc tree with .svg figures silently loses them here. check_eq $P/builder-image-types "image/png,image/gif,image/jpeg" "$(pv b_image_types)" # init() -- the locale lookup, five ways. It sets the two suffixes to `.html', # calls super().init(), and only THEN overrides lcid/encoding from chm_locales. # The order matters: a language row that arrived before super() would be # overwritten by the base builder. # # The last field of each value is the super() call the stub recorded, which is # how these cases also assert that the base builder's init() is reached at all. check_eq $P/init-default ".html,.html,0x409,cp1252,init" "$(pv init_none)" check_eq $P/init-en ".html,.html,0x409,cp1252,init" "$(pv init_en)" check_eq $P/init-ja ".html,.html,0x411,cp932,init" "$(pv init_ja)" check_eq $P/init-ru ".html,.html,0x419,windows-1251,init" "$(pv init_ru)" # AN UNKNOWN LANGUAGE FALLS BACK SILENTLY TO US ENGLISH. `chm_locales.get()' # returns None and the class defaults stand -- no warning, no error. That is the # branch a typo in conf.py takes, and also the branch every one of the 25 # translated-but-uncompilable languages takes; see `locales-count' above. check_eq $P/init-unknown ".html,.html,0x409,cp1252,init" "$(pv init_unknown)" # prepare_writing() forces the HTML4 doctype into the global template context. # HTML Help Workshop's renderer is an old Trident that does not parse an HTML5 # doctype, so this single line is what keeps the compiled pages readable. The # second field is the super() call, which must happen FIRST -- the override is # applied to the context the base builder just built. check_eq $P/prepare-writing "False,prepare_writing:foo,index" "$(pv prepare_writing)" # update_page_context() is how the per-page template learns the code page, so # that the in each page agrees with the bytes it was written in. # Measured with language `ja', so a value of cp1252 here would mean the locale # never reached the pages. check_eq $P/page-context-encoding "cp932" "$(pv page_context_encoding)" # write_doc() marks EXTERNAL links target=_blank and leaves internal ones alone. # A .chm is a local viewer: an external link left in-frame navigates the help # window itself away to the web, with no way back. The test distinguishes three # nodes -- external, internal, and a reference with no refuri at all -- because # the condition is `node.get("internal") is None and "refuri" in node' and each # clause needs its own witness. check_eq $P/write-doc-targets \ "ext=_blank,int=None,noref=None,write_doc:index" "$(pv write_doc_targets)" # ------------------------------------------------------------- setup(app) --- # WHAT SPHINX GETS WHEN IT LOADS THIS EXTENSION. This is the package's entire # registration surface and none of it needs Sphinx installed to observe. # # The returned dict is read by Sphinx itself: `parallel_read_safe' and # `parallel_write_safe' decide whether a `sphinx-build -j' may parallelise, and # an extension that returned None would silently serialise every parallel build # that loaded it -- a performance defect with no error message. check_eq $P/setup-return \ "{'version':\s'2.0.5',\s'parallel_read_safe':\sTrue,\s'parallel_write_safe':\sTrue}" \ "$(pv setup_return)" # THE MINIMUM SPHINX IT WILL LOAD UNDER, declared rather than assumed. Sphinx # raises VersionRequirementError before anything else runs, which is a clear # failure instead of an AttributeError deep in a build. check_eq $P/setup-require-sphinx "5.0" "$(pv setup_require_sphinx)" # IT PULLS IN THE HTML BUILDER FIRST. HTMLHelpBuilder subclasses # StandaloneHTMLBuilder, so the extension that provides it must be set up before # this builder is registered. check_eq $P/setup-extensions "sphinx.builders.html" "$(pv setup_extensions)" # THE BUILDER ITSELF -- one, by class. This is what makes `-b htmlhelp' resolve. check_eq $P/setup-builders "HTMLHelpBuilder" "$(pv setup_builders)" # THE MESSAGE CATALOG, registered under the module's own dotted name and # pointing at the locales directory this package installs. Without it the 54 # translations ship and are never loaded. check_eq $P/setup-catalog-name "sphinxcontrib.htmlhelp" "$(pv setup_catalog_name)" check_eq $P/setup-catalog-dir "$LOCDIR" "$(pv setup_catalog_dir)" # THE THREE CONFIG VALUES a conf.py may set, in registration order. check_eq $P/setup-config-names \ "htmlhelp_basename,htmlhelp_file_suffix,htmlhelp_link_suffix" \ "$(pv setup_config_names)" # THE REBUILD SCOPE OF EACH. `htmlhelp_basename' declares the EMPTY string, # meaning a change to it rebuilds nothing -- it only renames the output files. # The two suffixes declare `html', so changing either invalidates every written # page, which is right: they are baked into every link. check_eq $P/setup-config-rebuild \ "htmlhelp_basename:,htmlhelp_file_suffix:html,htmlhelp_link_suffix:html" \ "$(pv setup_config_rebuild)" # ...AND THEIR TYPE CONSTRAINTS. The two suffixes are constrained to str, so a # conf.py setting one to a non-string is refused by Sphinx with a config error # rather than producing broken links. `htmlhelp_basename' declares none, because # its default is a callable and Sphinx infers the type from the default. check_eq $P/setup-config-types \ "htmlhelp_basename:,htmlhelp_file_suffix:str,htmlhelp_link_suffix:str" \ "$(pv setup_config_types)" check_eq $P/setup-basename-default-callable "yes" "$(pv setup_basename_default_callable)" check_eq $P/setup-suffix-defaults-none "None,None" "$(pv setup_defaults_none)" # default_htmlhelp_basename() is `make_filename_from_project(project) + "doc"'. # THE MARKER IS THE POINT: make_filename_from_project is Sphinx's slugging, not # this package's, so the probe stubs it to return `' and this case # asserts only the `+ "doc"' concatenation -- which is the whole of what this # function contributes, and the reason a project called "Foo" gets "Foodoc". check_eq $P/default-basename "doc" "$(pv default_basename)" check_eq $P/default-basename-arg "Test Project" "$(pv mfp_called_with)" # ------------------------------------------------------ the three writers --- # From here down every case is output the package produced from a fixture the # probe supplied. See the header for where a stand-in is involved. # --- copy_stopword_list(): the .stp --------------------------------------- # The stopword list is COPIED, not rendered -- it is a fixed vocabulary of words # the .chm full-text search must not index, including the four that are # OPERATORS in the HTML Help search language ("and", "or", "not", "near"), which # would otherwise be unsearchable AND unusable. Source, destination and byte # equality in one value: the file must arrive named after htmlhelp_basename, not # after the template. check_eq $P/stopword-copy "project.stp,testdoc.stp,identical" "$(pv stopword_copy)" # Microsoft's own documentation gives this file a 256-512 byte ceiling, which # the package's docstring cites. 165 bytes and 33 words leaves room; a # translation or an addition that pushed it past 512 would be silently truncated # by Workshop, and nothing else would say so. check_eq $P/stopword-bytes "165" "$(pv stp_bytes)" check_eq $P/stopword-words "33" "$(pv stp_words)" # --- build_project_file(): the .hhp --------------------------------------- # THE [FILES] SIEVE READS BACKWARDS UNTIL YOU LOOK AT IT: # # if (in_staticdir and not fn.endswith('.js')) or fn.endswith('.html'): # # Under _static a file is listed when it does NOT end in `.js', so the CSS and # the images go in and the JavaScript is left OUT -- correct for a format whose # viewer will not execute script. Everywhere else only `.html' is listed, so a # stray `.txt' beside the pages is dropped. The fixture carries one file of each # kind so the whole decision is visible in one value, in os.walk order with # `dirs.sort()' making it deterministic. # # EVERY PATH CARRIES A BACKSLASH. HTML Help Workshop is a Windows tool reading a # Windows path list; forward slashes compile to a .chm with no pages in it. This # is the assertion that would catch an os.sep-based rewrite done on the wrong # platform. check_eq $P/hhp-files \ 'index.html|_static\app.css|_static\logo.png|sub\page.html' \ "$(pv hhp_files)" check_eq $P/hhp-template-name "project.hhp" "$(pv hhp_template_name)" check_eq $P/hhp-template-from-package "yes" "$(pv hhp_template_dir_is_package)" # THE CONTEXT, field by field. `master_doc' is the one that is NOT simply copied # from config: it is `config.master_doc + out_suffix', so the .hhp's `Default # topic=' names a FILE and not a docname -- getting that wrong opens the .chm on # nothing. check_eq $P/hhp-ctx-outname "testdoc" "$(pv hhp_ctx_outname)" check_eq $P/hhp-ctx-title 'Test\sProject\sDocumentation' "$(pv hhp_ctx_title)" check_eq $P/hhp-ctx-version "1.2" "$(pv hhp_ctx_version)" check_eq $P/hhp-ctx-project 'Test\sProject' "$(pv hhp_ctx_project)" check_eq $P/hhp-ctx-lcid "0x409" "$(pv hhp_ctx_lcid)" check_eq $P/hhp-ctx-master-doc "index.html" "$(pv hhp_ctx_master_doc)" check_eq $P/hhp-written "yes" "$(pv hhp_written)" check_eq $P/hhp-bytes "521" "$(pv hhp_bytes)" # THE RENDERED PROJECT FILE. These read the file the package actually wrote -- # through a plain jinja2 environment standing in for Sphinx's SphinxRenderer, so # what they measure is THIS PACKAGE'S TEMPLATE AND CONTEXT and nothing about # Sphinx's renderer. Each line is one thing hhc.exe requires: without them it # refuses the project or compiles a .chm with no contents, no index or no search. check_eq $P/hhp-line-compiled "yes" "$(pv hhp_has_compiled_file)" check_eq $P/hhp-line-contents "yes" "$(pv hhp_has_contents_file)" check_eq $P/hhp-line-index "yes" "$(pv hhp_has_index_file)" check_eq $P/hhp-line-topic "yes" "$(pv hhp_has_default_topic)" check_eq $P/hhp-line-stoplist "yes" "$(pv hhp_has_full_text_search_stop_list_file)" # THE LCID IS RENDERED AS `%#x' -- lower-case hex WITH the 0x prefix, which is # the form Workshop parses. A decimal here compiles to a .chm in the wrong # language with no diagnostic. check_eq $P/hhp-line-language "yes" "$(pv hhp_has_language)" check_eq $P/hhp-line-title "yes" "$(pv hhp_has_title)" # ...and the [FILES] section of the WRITTEN file agrees with the context, which # is what ties the sieve above to what hhc.exe would actually read. check_eq $P/hhp-files-section \ 'index.html|_static\app.css|_static\logo.png|sub\page.html' \ "$(pv hhp_files_section)" # --- build_toc_file(): ToCTreeVisitor and the .hhc ------------------------ # ToCTreeVisitor IS this package, walking a REAL docutils doctree. The depth # bookkeeping is the whole of it: the OUTERMOST bullet_list emits no
    # (project.hhc supplies it), every nested one does, and visit_reference emits # the two lines then raises SkipNode so the reference's children are # never visited -- which is what stops the link text appearing twice. # # The fixture is two top-level entries, the second carrying a nested list, with # `<', `&' and an apostrophe in the titles so the chm_htmlescape() call inside # the visitor is exercised on real node text rather than on a string literal. check_eq $P/toc-body \ '
  • \s\n\s\s\s\s\n\s\s\s\s\n\n
  • \s\n\s\s\s\s\n\s\s\s\s\n\n
      \n
    • \s\n\s\s\s\s\n\s\s\s\s\n\n
    ' \ "$(pv hhc_body)" check_eq $P/hhc-template-name "project.hhc" "$(pv hhc_template_name)" # THE CONTEXT. `suffix' is out_suffix, and it is appended to master_doc BY THE # TEMPLATE here rather than by the code -- which is why hhp's master_doc already # carries `.html' and hhc's does not. The two disagreeing is correct and is the # kind of thing a reader would otherwise "fix". check_eq $P/hhc-ctx-suffix ".html" "$(pv hhc_ctx_suffix)" check_eq $P/hhc-ctx-short-title "Test" "$(pv hhc_ctx_short_title)" check_eq $P/hhc-ctx-master-doc "index" "$(pv hhc_ctx_master_doc)" check_eq $P/hhc-ctx-domain-indices "none" "$(pv hhc_ctx_domain_indices)" check_eq $P/hhc-written "yes" "$(pv hhc_written)" # THE RENDERED CONTENTS FILE. Same stand-in caveat as the .hhp. The DOCTYPE and # the GENERATOR comment are not decoration -- Workshop sniffs both, and a # sitemap file without them is rejected outright. check_eq $P/hhc-doctype "yes" "$(pv hhc_has_doctype)" check_eq $P/hhc-generator "yes" "$(pv hhc_has_generator)" # The root entry the template adds ahead of the visitor's body, pointing at # master_doc + suffix -- the entry that makes the .chm open on the front page. check_eq $P/hhc-root-sitemap "yes" "$(pv hhc_root_sitemap)" # ...and the visitor's body reached the file intact. check_eq $P/hhc-body-in-output "yes" "$(pv hhc_body_in_output)" # --- build_hhx(): the .hhk index ----------------------------------------- # NO TEMPLATE IS INVOLVED IN THIS ONE. build_hhx() writes the file itself, line # by line, so every byte is this package's own code over the index the probe # supplied -- which makes it the strongest assertion in this suite, and the only # one that would survive Sphinx changing its renderer entirely. # # The fixture carries one entry per branch of write_index(): # 0 refs -> a `See Also' pointing back at the term # 1 ref -> a single `Local' # 2 refs -> a numbered `Name'/`Local' pair per reference # subitems -> a nested
      of one-ref entries check_eq $P/hhk-written "yes" "$(pv hhk_written)" check_eq $P/hhk-bytes "842" "$(pv hhk_bytes)" check_eq $P/hhk-sha256 \ "5bd50b3911532c8599100304ee5a28f2babb37810f9c14497cd19766e3fed4e9" \ "$(pv hhk_sha256)" # =========================================================================== # THE CODE PAGE, ASSERTED ON THE RAW BYTES -- the part of this builder most # likely to break under a differently built interpreter. # =========================================================================== # The file is opened `encoding=self.encoding, errors="xmlcharrefreplace"'. So: # # * `é' (U+00E9) IS in cp1252 and must come out as the SINGLE BYTE 0xE9. A # 0xC3 0xA9 pair instead would mean the file was written as UTF-8 -- which # hhc.exe would compile into an index full of mojibake, silently. # * `→' (U+2192) is NOT in cp1252 and must come out as the numeric reference # `→'. That is what errors="xmlcharrefreplace" buys, and it is the # difference between an index entry and a UnicodeEncodeError that aborts the # build. # # Both are asserted against the bytes on disk, not against a decoded string, # because a decode would hide exactly the failure being looked for. check_eq $P/hhk-cp1252-single-byte "yes" "$(pv hhk_has_cp1252_eacute)" check_eq $P/hhk-not-utf8 "yes" "$(pv hhk_no_utf8_eacute)" check_eq $P/hhk-xmlcharrefreplace "yes" "$(pv hhk_arrow_charref)" # The frame. The trailing `
' is the subitem list's close followed by # the outer one -- write_index() writes `
    ' for subitems without a newline, # which is why the two closes end up adjacent. check_eq $P/hhk-first-line "
      " "$(pv hhk_first_line)" check_eq $P/hhk-last-line "
" "$(pv hhk_last_line)" check_eq $P/hhk-ul-open "2" "$(pv hhk_ul_open)" check_eq $P/hhk-ul-close "2" "$(pv hhk_ul_close)" check_eq $P/hhk-objects "5" "$(pv hhk_object_count)" # The four branches, read back out of the written file. Every keyword has gone # through chm_htmlescape(quote=True) -- `&', `<', `'' -- and the # non-ASCII pair shows the code-page split in one value: the arrow as a numeric # reference, the é as a cp1252 byte that decoded back to itself. check_eq $P/hhk-keywords \ 'alpha & beta|gamma <see>|delta's|epsilon → café|sub été' \ "$(pv hhk_keywords)" # ZERO REFERENCES BECOMES A `See Also' POINTING AT THE TERM ITSELF. That is what # an index entry with subentries but no page of its own compiles to, and it is # the branch nobody would guess from the output. check_eq $P/hhk-see-also 'gamma <see>' "$(pv hhk_see_also)" check_eq $P/hhk-locals \ 'a.html#alpha|d1.html|d2.html|e.html|s.html' "$(pv hhk_locals)" # TWO OR MORE REFERENCES GET NUMBERED `Name' PARAMS -- and the name is the URI, # not a title, which upstream's own `# XXX: better title?' comment marks as # known-poor. Asserting it pins today's behaviour so a future improvement is a # visible change rather than a silent one. check_eq $P/hhk-numbered-names '[0] d1.html|[1] d2.html' "$(pv hhk_names)" # AND THE WHOLE FILE, escaped onto one line. A red here prints the entire output, # which is what makes a formatting regression readable instead of a digest that # only says "different". check_eq $P/hhk-all \ '
    \n
  • \s\n\s\s\s\s\n\s\s\s\s\n\n
  • \s\n\s\s\s\s\n\s\s\s\s\n\n
  • \s\n\s\s\s\s\n\s\s\s\s\n\s\s\s\s\n\s\s\s\s\n\s\s\s\s\n\n
  • \s\n\s\s\s\s\n\s\s\s\s\n\n
      \s
    • \s\n\s\s\s\s\n\s\s\s\s\n\n
\n' \ "$(pv hhk_all)" # --- handle_finish(): the order, and the four files ----------------------- # THE ORDER IS LOAD-BEARING. copy_stopword_list() must precede # build_project_file(), because the .hhp names the .stp in its `Full text search # stop list file=' line and Workshop refuses a project naming a file that is not # there. build_hhx() runs last and is the only one called with explicit # arguments. check_eq $P/handle-finish \ "copy_stopword_list,build_project_file,build_toc_file,build_hhx;testdoc.hhc,testdoc.hhk,testdoc.hhp,testdoc.stp" \ "$(pv handle_finish)" # ------------------------------------------------------- the message catalogs -- # 54 LANGUAGES OF CONSOLE MESSAGES, each a compiled .mo beside its .po source. # The package hands the directory to Sphinx's add_message_catalog and never # opens one itself, so this is the ONLY place the catalogs are exercised at all # -- parsed with the STDLIB gettext, which is what proves they survived # packaging as something a reader could load rather than as 54 files of the # right size. check_eq $P/mo-langs "54" "$(pv mo_langs)" check_eq $P/mo-count "54" "$(pv mo_count)" check_eq $P/mo-all-parse "none" "$(pv mo_unparseable)" # ONE TRANSLATION BY VALUE, so the case says the catalogs carry content and not # just a valid header. This is the Japanese rendering of the progress message # build_project_file() is decorated with. check_eq $P/mo-ja-sample "プロジェクトファイルを書き出し" "$(pv mo_ja_sample)" # THE .po SOURCES AND THE TRANSIFEX CONFIG ARE INSTALLED TOO. Upstream ships # them inside the wheel, so OE installs them; nothing reads a .po at runtime. # They are asserted rather than filtered because they are 54 of the 115 files # under the package directory, and a reader counting files should find the # number already accounted for. See `pkg-only-expected' above for why they are # not carved out with a bbappend. check_eq $P/po-count "54" "$(pv po_count)" check_eq $P/tx-config "yes" "$(pv tx_config)" # ------------------------------------------------------- the templates, hashed -- # The three template files by sha256. They are the package's DATA rather than # its code, and the .hhp/.hhc cases above render two of them -- so a digest # change here with those cases still green would mean the render is not reading # what this image installed. check_eq $P/tpl-hhp-digest \ "00ef1565063833b510fa903d51c494369a5bf8ef2de26637e93a5eeb15f07887" "$(pv tpl_hhp)" check_eq $P/tpl-hhc-digest \ "221cbb26bce49f31c8cc91a0386d571b7e09c55014c7649de7fcc8d845f36efa" "$(pv tpl_hhc)" check_eq $P/tpl-stp-digest \ "bf9d0b8ee6de8464c0609731c9335997865eb58aecded38c90b69093ec1d4f05" "$(pv tpl_stp)" # ------------------------------------------------- the manifest checksums --- # EVERY PAYLOAD FILE RE-HASHED ON TARGET AND SCORED AGAINST THE WHEEL'S OWN # RECORD -- 117 rows, each a sha256 and a byte count. This is the end-to-end # statement that what upstream signed, OE packaged, dpkg installed and virtiofs # served is byte for byte the same thing, and it covers all 115 files under the # package directory plus the LICENSE, METADATA and WHEEL. # # 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 117 and the # file has 118 lines. The __pycache__ .pyc is correctly absent from RECORD -- OE # creates it at build time with compileall, the wheel never carried it -- which # is why 117 rows cover an install of 119 files. check_eq $P/record-rows "117" "$(pv record_rows)" check_eq $P/record-all-found "117" "$(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)" rm -rf "$W"