# Probe for 99-python3-sphinxcontrib-devhelp.test -- ONE interpreter start for # the whole suite. # # Every value is emitted as `key=value' on its own line. The .test compares with # shell `test x = y', so any value that could carry a newline or a run of spaces # is escaped reversibly here: a newline becomes the two characters \n and a # space becomes \s. Nothing else is transformed, so a red prints the whole value # on one readable line. # # =========================================================================== # THIS PROBE INSTALLS A STUB `sphinx' BEFORE IT IMPORTS ANYTHING, AND THAT IS # THE CENTRAL FACT ABOUT THIS PORT. # =========================================================================== # # sphinxcontrib/devhelp/__init__.py imports NINE names out of Sphinx at MODULE # scope -- addnodes, Sphinx, StandaloneHTMLBuilder, IndexEntries, # get_translation, logging, NodeMatcher, make_filename -- and this image # installs no Sphinx: no python3-sphinx is in core-image-brintos-emcraft and # none is being ported. So `import sphinxcontrib.devhelp' on this rootfs raises # # ModuleNotFoundError: No module named 'sphinx' # # and no measurement of this package is possible without standing something in # for its consumer. The .test asserts that failure directly, as a case, so the # fact is recorded rather than worked around silently; this probe then supplies # the smallest stub the module actually reads and measures the package's # contract with Sphinx against it. # # That is not a weaker test than running Sphinx would be. What ships here is one # builder class plus the setup() that registers it, and the builder's whole # deliverable -- the `.devhelp.gz' index -- is produced by build_devhelp(), a # method that reads only `self.config', `self.env' and two collaborators. Both # collaborators are supplied below as fixtures, so THE XML THIS SUITE COMPARES # AGAINST A LITERAL IS PRODUCED BY THE PACKAGE'S OWN CODE, line for line, on # this arm's interpreter. Nothing about that measurement is weakened by Sphinx # being absent. # # WHAT IS NOT MEASURABLE, STATED PLAINLY. The parts of the builder that live in # StandaloneHTMLBuilder -- the HTML tree Devhelp browses -- are Sphinx's code, # not this package's, and a stub cannot say anything about them. So the suite # pins what DevhelpBuilder DECLARES (its own class body) and what # build_devhelp() PRODUCES, and says so rather than implying more. # # THE STUB IS AS THIN AS THE MODULE ALLOWS AND NO THINNER, and where it must # carry behaviour it reproduces upstream's rather than inventing: # # NodeMatcher -- a faithful re-implementation of sphinx.util.nodes'. # build_devhelp() drives it through docutils' own findall(), # so the TOC walk below is REAL docutils traversal over a # real doctree; only the predicate is ours. # compact_paragraph -- a subclass of docutils' nodes.Element, which is what # sphinx.addnodes' is. write_toc() dispatches on isinstance # against it and against three REAL docutils classes. # IndexEntries -- replaced entirely, because its output shape IS the # fixture: create_index() returns the index and # build_devhelp() only formats it. # StandaloneHTMLBuilder -- an empty base. Deliberately empty: anything put on # it would be measured as though it were devhelp's, which is # why the suite asserts what DevhelpBuilder's OWN __dict__ # declares rather than what an instance happens to answer. # # docutils is NOT stubbed. python3-docutils 0.20.1 is on this image (BRINTOS-1712) # and every node this probe builds is a real one, so the traversal measured here # is the traversal a real Sphinx would perform. import gzip import os import re import sys import tempfile import types OUT = [] def emit(k, v): OUT.append("%s=%s" % (k, v)) def esc(s): return str(s).replace("\\", "\\\\").replace("\n", "\\n").replace(" ", "\\s") def guard(key, fn): """Run fn and emit its value, or emit the exception type. A probe that dies at the first surprise leaves every later case unscorable; one that records the surprise and continues leaves exactly one red beside the evidence.""" try: emit(key, fn()) except Exception as exc: # noqa: BLE001 -- the type IS the measurement emit(key, "EXC:%s" % type(exc).__name__) emit(key + "_detail", esc(str(exc))[:200]) # ------------------------------------------------ the import, twice over --- # FIRST, WITHOUT THE STUB, so the probe measures the same failure the .test # asserts from the shell rather than taking it on trust. This must be done # before the stub is installed and before any import of the package, because a # successful import is cached in sys.modules and cannot be undone cleanly. def _bare_import(): try: import sphinxcontrib.devhelp # noqa: F401 return "imported" except ModuleNotFoundError as exc: return "ModuleNotFoundError:%s" % exc.name except Exception as exc: # noqa: BLE001 return "EXC:%s" % type(exc).__name__ emit("bare_import", _bare_import()) # Whatever the line above left behind. A failed `import sphinxcontrib.devhelp' # still binds the NAMESPACE package `sphinxcontrib' -- the failure is in the # submodule -- so the submodule, and only the submodule, must be gone for the # stubbed import below to actually run the module body. for _n in [n for n in sys.modules if n.startswith("sphinxcontrib.")]: del sys.modules[_n] # ------------------------------------------------------------- the stub ----- import docutils # REAL -- python3-docutils is on this image from docutils import nodes _calls = {"require_sphinx": [], "setup_extension": [], "builders": [], "catalogs": [], "config_values": []} class _CompactParagraph(nodes.Element): """sphinx.addnodes.compact_paragraph is a docutils Element subclass, and write_toc() dispatches on isinstance against it. Subclassing the real nodes.Element is what makes the traversal below real.""" class _NodeMatcher(object): """A faithful re-implementation of sphinx.util.nodes.NodeMatcher: match a node against a set of classes and a set of attribute constraints, where the sentinel `typing.Any' means "carries this attribute, with any value". build_devhelp() passes it to docutils' own findall(), which calls it.""" def __init__(self, *node_classes, **attrs): self.classes = node_classes self.attrs = attrs def match(self, node): try: if self.classes and not isinstance(node, self.classes): return False if self.attrs: if not isinstance(node, nodes.Element): return False for key, value in self.attrs.items(): if key not in node: return False if value is _ANY: continue if node.get(key) != value: return False return True except Exception: # noqa: BLE001 return False def __call__(self, node): return self.match(node) from typing import Any as _ANY # noqa: E402 -- devhelp passes typing.Any itself class _StubBuilder(object): """Stands in for StandaloneHTMLBuilder. DELIBERATELY EMPTY -- see header.""" def __init__(self, *a, **kw): self._super_init_called = False def init(self): self._super_init_called = True def _make_filename(string): """sphinx.util.osutil.make_filename, reproduced. Reached only through the default that setup() hands add_config_value, and the suite asserts what the package PASSES rather than what this function returns -- see cfg_* below.""" import re return re.sub(r"[^a-zA-Z0-9_-]", "", string) or "sphinx" class _StubIndexEntries(object): """Replaced entirely: its output shape IS the fixture. Set by the probe to return INDEX below, so what build_devhelp() formats is known exactly.""" def __init__(self, env): self.env = env def create_index(self, builder): return INDEX def _install_stub(): sphinx = types.ModuleType("sphinx") sphinx.version_info = (7, 2, 6, "final", 0) addnodes = types.ModuleType("sphinx.addnodes") addnodes.compact_paragraph = _CompactParagraph application = types.ModuleType("sphinx.application") application.Sphinx = type("Sphinx", (object,), {}) builders = types.ModuleType("sphinx.builders") builders_html = types.ModuleType("sphinx.builders.html") builders_html.StandaloneHTMLBuilder = _StubBuilder environment = types.ModuleType("sphinx.environment") env_adapters = types.ModuleType("sphinx.environment.adapters") indexentries = types.ModuleType("sphinx.environment.adapters.indexentries") indexentries.IndexEntries = _StubIndexEntries locale = types.ModuleType("sphinx.locale") locale.get_translation = lambda catalog, namespace="general": (lambda s: s) util = types.ModuleType("sphinx.util") util_logging = types.ModuleType("sphinx.util.logging") util_logging.getLogger = lambda name: types.SimpleNamespace( info=lambda *a, **kw: None) util.logging = util_logging util_nodes = types.ModuleType("sphinx.util.nodes") util_nodes.NodeMatcher = _NodeMatcher util_osutil = types.ModuleType("sphinx.util.osutil") util_osutil.make_filename = _make_filename for name, mod in ( ("sphinx", sphinx), ("sphinx.addnodes", addnodes), ("sphinx.application", application), ("sphinx.builders", builders), ("sphinx.builders.html", builders_html), ("sphinx.environment", environment), ("sphinx.environment.adapters", env_adapters), ("sphinx.environment.adapters.indexentries", indexentries), ("sphinx.locale", locale), ("sphinx.util", util), ("sphinx.util.logging", util_logging), ("sphinx.util.nodes", util_nodes), ("sphinx.util.osutil", util_osutil)): sys.modules[name] = mod return sphinx _stub_sphinx = _install_stub() import sphinxcontrib # noqa: E402 import sphinxcontrib.devhelp as D # noqa: E402 # --------------------------------------------------------------- identity --- # THE docutils THIS MEASUREMENT RAN AGAINST. Every node the fixture below builds # is a real docutils node and the TOC walk is real docutils traversal, so the # version is part of the evidence rather than background. emit("docutils_version", docutils.__version__) emit("version", D.__version__) emit("version_info", ".".join(str(n) for n in D.__version_info__)) emit("module_file", D.__file__) emit("package_dir", D.package_dir) try: from importlib.metadata import distribution as _dist except ImportError: # pragma: no cover -- 3.12 always has it _dist = None guard("dist_version", lambda: _dist("sphinxcontrib-devhelp").version) guard("dist_name", lambda: _dist("sphinxcontrib-devhelp").metadata["Name"]) # UPSTREAM DECLARES NO RUNTIME DEPENDENCY AT ALL, and that is the packaging fact # that makes the module-scope `import sphinx' a disagreement rather than a # dropped Depends: pyproject.toml's `dependencies = []' is upstream's own # statement, and Sphinx appears only under the `standalone' extra. So unlike its # sibling sphinxcontrib-jquery -- whose METADATA carries `Requires-Dist: # Sphinx>=1.8' -- there is nothing here for OE to have dropped. guard("dist_requires", lambda: esc(",".join(_dist("sphinxcontrib-devhelp").requires or []))) # AND DECLARES NO ENTRY POINTS. A Sphinx THEME is discovered through the # `sphinx.html_themes' group; an EXTENSION is named by the user in conf.py's # `extensions' list and found by ordinary import. An entry point here would mean # the package had changed shape. guard("ep_count", lambda: len(list(_dist("sphinxcontrib-devhelp").entry_points))) # ...and the same fact stated crisply: NOTHING is required unconditionally. Every # Requires-Dist this package carries is guarded by an `extra ==' marker, so the # installed distribution asks for no runtime package at all -- which is exactly # what the .deb's `Depends: python3-core' says from the packaging side. guard("dist_requires_unconditional", lambda: ",".join( r for r in (_dist("sphinxcontrib-devhelp").requires or []) if "extra ==" not in r)) # ------------------------------------------------- the namespace package --- # `sphinxcontrib' IS A PEP 420 IMPLICIT NAMESPACE PACKAGE -- it ships NO # __init__.py, and its module object therefore has __file__ None and a # _NamespacePath for __path__. That is how the whole sphinxcontrib-* family # shares one top-level name without any one distribution owning it, and it is # why the five sibling ports in this round install alongside this one. emit("ns_file", getattr(sphinxcontrib, "__file__", None)) emit("ns_path_type", type(sphinxcontrib.__path__).__name__) emit("ns_path", ",".join(sphinxcontrib.__path__)) # The public surface, read off the module -- it declares no __all__. The imported # names are module attributes like any other and are stated rather than filtered # out: a release that moved to a different import form would change the surface, # and this is the case that would say so. emit("api_surface", ",".join(sorted(n for n in dir(D) if not n.startswith("_")))) # ------------------------------------------------------- the builder class --- # WHAT DevhelpBuilder DECLARES IN ITS OWN CLASS BODY. This is asserted from # __dict__ rather than from an instance ON PURPOSE: everything an instance # answers that is not in this set comes from the stub base, and asserting those # would measure the stub. `format' is the case that matters -- the ticket names # it, and it is NOT declared here: DevhelpBuilder inherits it from # StandaloneHTMLBuilder, which is Sphinx's class and not on this image. So the # suite pins the inheritance rather than a value the stub would have supplied. B = D.DevhelpBuilder emit("builder_declares", ",".join(sorted( k for k in vars(B) if not k.startswith("__")))) emit("builder_name", B.name) emit("builder_format_declared", "yes" if "format" in vars(B) else "no") # The base class is the one the module imported, asserted by identity. Naming # it would name the stub; this says DevhelpBuilder extends whatever # `from sphinx.builders.html import StandaloneHTMLBuilder' resolved to, which is # the real statement and is true of a real Sphinx too. emit("builder_base_is_imported", "yes" if B.__bases__ == (D.StandaloneHTMLBuilder,) else "no") emit("builder_copysource", B.copysource) emit("builder_add_permalinks", B.add_permalinks) emit("builder_embedded", B.embedded) emit("supported_image_types", ",".join(B.supported_image_types)) emit("supported_image_count", len(B.supported_image_types)) # The epilog is the message `sphinx-build' prints when the builder finishes; it # is the only user-facing text this package owns. Its %(outdir)s and %(project)s # placeholders are interpolated by Sphinx, so a release that renamed one would # raise KeyError at the end of a successful build -- after all the work. emit("epilog_placeholders", ",".join(sorted( set(re.findall(r"%\((\w+)\)s", B.epilog))))) emit("epilog_lines", len(B.epilog.splitlines())) emit("epilog_first", esc(B.epilog.splitlines()[0])) # init() is the one method that touches instance state before a build, and it # calls super().init() first -- so the ordering, and the two suffixes it then # sets, are the whole of what this class does at startup. def _init(): b = B() b.init() return "%s,%s,%s" % (b.out_suffix, b.link_suffix, "super" if b._super_init_called else "no-super") guard("init_result", _init) # ------------------------------------------------------------- setup() ------ # WHAT SPHINX CALLS AND WHAT IT GETS BACK. setup() is the entry point for an # extension: Sphinx imports the module named in conf.py's `extensions' and calls # setup(app). The dict it returns is read by Sphinx itself -- `version' is # reported in build output and used for env invalidation, and the two parallel_* # flags decide whether the extension may run under `sphinx-build -j'. An # extension that returned None is treated as not-parallel-safe and silently # serialises every parallel build that loads it. class _App(object): """The smallest object setup() actually uses. Every call is RECORDED rather than executed, so what is measured is the package's REQUEST of Sphinx -- which is the whole of an extension's registration contract.""" def require_sphinx(self, version): _calls["require_sphinx"].append(version) def setup_extension(self, name): _calls["setup_extension"].append(name) def add_builder(self, builder): _calls["builders"].append(builder) def add_message_catalog(self, catalog, locale_dir): _calls["catalogs"].append((catalog, locale_dir)) def add_config_value(self, name, default, rebuild, types=()): _calls["config_values"].append((name, default, rebuild, types)) _setup = None try: _setup = D.setup(_App()) emit("setup_type", type(_setup).__name__) emit("setup_keys", ",".join(sorted(_setup))) emit("setup_version", _setup.get("version")) emit("setup_parallel_read", _setup.get("parallel_read_safe")) emit("setup_parallel_write", _setup.get("parallel_write_safe")) except Exception as exc: # noqa: BLE001 for _k in ("setup_type", "setup_keys", "setup_version", "setup_parallel_read", "setup_parallel_write"): emit(_k, "EXC:%s" % type(exc).__name__) emit("setup_detail", esc(str(exc))[:200]) # THE MINIMUM SPHINX IT DEMANDS, and the extension it pulls in. require_sphinx # is what makes a too-old Sphinx a clear error rather than an AttributeError # deep in the build; setup_extension('sphinx.builders.html') is what guarantees # the base class this builder extends is loaded before it is registered. guard("require_sphinx", lambda: ",".join(_calls["require_sphinx"])) guard("setup_extension", lambda: ",".join(_calls["setup_extension"])) # THE BUILDER IT REGISTERS -- by identity, not by name. A release that # registered a different class would still register something called `devhelp'. guard("builders_registered", lambda: str(len(_calls["builders"]))) guard("builder_is_devhelp", lambda: "yes" if _calls["builders"] == [D.DevhelpBuilder] else "no") # THE MESSAGE CATALOG. The package ships 60-odd translations of its epilog under # locales/, and this call is the only thing that makes them reachable. The # directory is asserted to be the one INSIDE the installed package, which is # what ties the catalog registration to the files the payload cases counted. guard("catalog_count", lambda: str(len(_calls["catalogs"]))) guard("catalog_name", lambda: _calls["catalogs"][0][0]) guard("catalog_dir_is_package", lambda: "yes" if _calls["catalogs"][0][1] == os.path.join( D.package_dir, "locales") else "no") # THE CONFIG VALUE IT DECLARES -- exactly one, and every attribute of it. # `devhelp_basename' names the .devhelp.gz file a build produces. Its default is # a CALLABLE, which is Sphinx's own idiom for "derive it from the config when it # is read": the value cannot be computed at registration time because the # project name is not known yet. So the suite asserts (a) that it is callable, # and (b) WHAT THE PACKAGE PASSES IT -- config.project -- by handing it a config # that records the attribute read. Asserting the returned string instead would # be asserting make_filename, which is Sphinx's function and stubbed here. guard("cfg_count", lambda: str(len(_calls["config_values"]))) guard("cfg_name", lambda: _calls["config_values"][0][0]) guard("cfg_rebuild", lambda: _calls["config_values"][0][2]) guard("cfg_default_callable", lambda: "yes" if callable(_calls["config_values"][0][1]) else "no") class _RecordingConfig(object): def __init__(self): self.read = [] def __getattr__(self, name): self.read.append(name) return "brintOS Guest Docs" def _cfg_default_reads(): rec = _RecordingConfig() value = _calls["config_values"][0][1](rec) return "%s->%s" % (",".join(rec.read), value) guard("cfg_default_reads", _cfg_default_reads) # ------------------------------------------------------- build_devhelp() ---- # THE DELIVERABLE. Everything above is registration; this is the only method in # the package that produces a file, and the file IS the port's output. The # fixtures below are handed to the package's own code and the gzip'd XML it # writes is compared against a literal by the suite. # # THE TOC FIXTURE is a real docutils doctree of the shape Sphinx's toctree # resolution produces: a compact_paragraph carrying a `toctree' attribute (which # is what the NodeMatcher selects on), wrapping a bullet_list of list_items, # each holding a compact_paragraph with a reference, and one of them nesting a # second level. write_toc() turns each list_item into a and each reference # into that 's link and name -- so the nesting, the ordering and the # attribute placement are all this package's own doing. def _toc_entry(refuri, text, children=()): item = nodes.list_item() para = _CompactParagraph() ref = nodes.reference("", "", nodes.Text(text), refuri=refuri) para += ref item += para if children: sub = nodes.bullet_list() for child in children: sub += child item += sub return item def _build_toc(): doc = nodes.document(None, None) top = _CompactParagraph() top["toctree"] = True blist = nodes.bullet_list() blist += _toc_entry("intro.html", "Introduction") blist += _toc_entry("guide.html", "Guide & Reference", children=[ _toc_entry("guide.html#install", "Installing"), _toc_entry("guide.html#run", "Running guest"), ]) blist += _toc_entry("api.html", u"API — раздел") top += blist doc += top return doc # THE INDEX FIXTURE covers every arm of write_index(), which is where the # formatting decisions live: an entry with NO refs emits nothing, one with a # single ref emits a plain , one with several emits one per # ref with a "[N] " prefix, and subitems are emitted with the parent's title # stripped of any trailing parenthesis by the module's own regexp. INDEX = [ ("B", [ ("build_devhelp() (in module devhelp)", ([(0, "api.html#build_devhelp")], [("method", [(0, "api.html#build_devhelp.method")])], None)), ]), ("E", [ ("escaping & ", ([(0, 'api.html#esc"1')], [], None)), (u"раздел", ([(0, "api.html#ru")], [], None)), ("duplicated", ([(0, "a.html#d"), (0, "b.html#d")], [], None)), ("orphan", ([], [], None)), ]), ] class _Config(object): html_title = "brintOS Guest Docs & " project = u"brintÖS" version = "1.0" master_doc = "index" devhelp_basename = "brintosguestdocs" class _Env(object): def __init__(self, toc): self.toc = toc self.calls = [] def get_and_resolve_doctree(self, docname, builder, prune_toctrees=True): self.calls.append((docname, prune_toctrees)) return self.toc _WORK = tempfile.mkdtemp(prefix="bst-devhelp-") _OUTDIR = os.path.join(_WORK, "out") os.makedirs(_OUTDIR) # IndexEntries is a module-level name in devhelp, so replacing it here is what # hands build_devhelp() the fixture index. The stub was already installed as the # sphinx module's attribute; this rebinds the name the module actually resolves. D.IndexEntries = _StubIndexEntries _b = object.__new__(D.DevhelpBuilder) _b.config = _Config() _b.env = _Env(_build_toc()) _b.outdir = _OUTDIR _XML = None try: _b.build_devhelp(_OUTDIR, "brintosguestdocs") _path = os.path.join(_OUTDIR, "brintosguestdocs.devhelp.gz") emit("devhelp_file", "yes" if os.path.isfile(_path) else "no") # GZIP, NOT PLAIN XML. `gzip.open(..., "w")' is the only write path in the # method and Devhelp reads the compressed form, so a release that dropped # the compression would produce a file no reader could open. Asserted by # decompressing it rather than by the name. with gzip.open(_path, "rb") as fh: _XML = fh.read().decode("utf-8") emit("devhelp_gzip", "yes") emit("devhelp_bytes", os.path.getsize(_path)) except Exception as exc: # noqa: BLE001 emit("devhelp_file", "EXC:%s" % type(exc).__name__) emit("devhelp_detail", esc(str(exc))[:200]) # THE DOCTREE IT ASKED FOR. build_devhelp() resolves the MASTER document with # prune_toctrees=False, which is what makes the whole toctree visible to the # walk rather than only the top level. A release that let it prune would emit a # one-level book index and every XML case below would still pass. guard("env_call", lambda: "%s,prune=%s" % (_b.env.calls[0][0], _b.env.calls[0][1])) # THE WHOLE DOCUMENT, on one escaped line. This is the strongest assertion in # the suite: the literal in the .test is what this package's own code produced # on an x86-64 control from these exact fixtures, so a red here is a real # behaviour change and not a formatting opinion. guard("xml", lambda: esc(_XML)) # NO XML DECLARATION, and that is ElementTree's documented behaviour rather than # an omission: `tree.write(f, "utf-8")' passes the encoding positionally and ET # emits a declaration only for an encoding outside {utf-8, us-ascii, unicode}. # Pinned because a release that started emitting one would change every byte # offset in the file and `xml' below would red with no explanation. guard("xml_has_declaration", lambda: "yes" if _XML.startswith("", _XML.index(",size. # Its own row carries no digest (a file cannot hash itself) and is skipped by # construction rather than by name. This is an end-to-end statement that what # upstream published, OE packaged, dpkg installed and virtiofs served is # byte-for-byte the same -- over all 102 files, of which 100 are the locale # catalogues no other case in this suite reads. import base64 # noqa: E402 import hashlib # noqa: E402 SITE = os.path.dirname(os.path.dirname(D.package_dir)) RECORD = os.path.join(SITE, "sphinxcontrib_devhelp-1.0.6.dist-info", "RECORD") def _record_rows(): rows = [] with open(RECORD, "r") as fh: for line in fh: line = line.rstrip("\n") if not line: continue parts = line.split(",") if len(parts) != 3 or not parts[1].startswith("sha256="): continue # RECORD's own row: digest and size are empty rows.append((parts[0], parts[1][len("sha256="):], int(parts[2]))) return rows def _b64(digest): return base64.urlsafe_b64encode(digest).decode().rstrip("=") def _audit(): checked = 0 missing = [] baddigest = [] badsize = [] for path, want, size in _record_rows(): full = os.path.join(SITE, path) if not os.path.isfile(full): missing.append(path) continue with open(full, "rb") as fh: blob = fh.read() checked += 1 if _b64(hashlib.sha256(blob).digest()) != want: baddigest.append(path) if len(blob) != size: badsize.append(path) return checked, missing, baddigest, badsize try: _checked, _missing, _baddigest, _badsize = _audit() emit("record_rows", len(_record_rows())) emit("record_checked", _checked) emit("record_missing", ",".join(sorted(_missing))) emit("record_bad_digest", ",".join(sorted(_baddigest))) emit("record_bad_size", ",".join(sorted(_badsize))) except Exception as exc: # noqa: BLE001 for _k in ("record_rows", "record_checked", "record_missing", "record_bad_digest", "record_bad_size"): emit(_k, "EXC:%s" % type(exc).__name__) emit("record_detail", esc(str(exc))[:200]) # THE LOCALE CATALOGUES, counted. setup() registers locales/ as a message # catalog directory, and what makes that registration meaningful is the .mo # files being there -- a .po with no compiled .mo beside it is a translation # that never reaches a user. def _locales(): root = os.path.join(D.package_dir, "locales") mo = po = 0 for dirpath, _dirnames, filenames in os.walk(root): for fn in filenames: if fn.endswith(".mo"): mo += 1 elif fn.endswith(".po"): po += 1 return "%d,%d" % (mo, po) guard("locales", _locales) # NOTHING THIS PACKAGE LOADED IS A COMPILED EXTENSION -- the interpreter's own # view of the packaging-side `pkg-no-so'. This image's python3 is one static # binary with an empty lib-dynload and no dynamic loader (BRINTOS-1476) and an # empty Py* export surface (BRINTOS-2046), so a module carrying an extension # could not be imported here at all. The namespace package's __file__ is None # and is not a finding. def _all_pure(): bad = [] for name, mod in list(sys.modules.items()): if not (name == "sphinxcontrib" or name.startswith("sphinxcontrib.")): continue f = getattr(mod, "__file__", None) or "" if f and not (f.endswith(".py") or f.endswith(".pyc")): bad.append(f) return ",".join(sorted(bad)) or "yes" emit("all_pure_python", _all_pure()) emit("done", "yes") # THE OUTPUT IS FORCED TO UTF-8. Two of the values above carry non-ASCII by # design -- the fixture pins an em dash and a Cyrillic title precisely because # they are what a naive writer gets wrong -- and this image starts python3 with # no locale set. A stdout that had settled on ASCII would raise # UnicodeEncodeError on the LAST line of the probe, after every measurement had # succeeded, and the suite would read it as a dead probe. try: sys.stdout.reconfigure(encoding="utf-8") except Exception: # noqa: BLE001 -- pre-3.7 or a stream that cannot be rebound pass print("\n".join(OUT))