. "$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\n
\n
\s\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_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\n
\s\n
\s\n
\s
\s\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"