. "$BST_LIB" # BST-TIMEOUT: 1200 # TWO python3 starts, and they are the bulk of the cost. # # Every other case here reads the filesystem or dpkg with # # shell builtins, or greps the second start's output. # # # # The two are: one `importlib.import_module' call (so the # # import result is legible from the console without reading # # a transcript) and the probe, which takes ONE start for a # # hundred-odd measurements. # # # # THE PROBE FORKS NOTHING. It has no fixtures to build and # # no children to spawn -- a stub-only distribution has # # nothing to drive. It reads 90 files, sha256s 86 of them, # # and parses 84. That is more file work than the other stub # # ports in this directory do, and still no syscall surface # # beyond open/read: 1200 is what the pure-python probes here # # use and it is not tight. # # # # NOTHING HERE SLEEPS, opens a socket, starts a thread, or # # reaches a device, a peer or a terminal. Nothing is written # # anywhere: this suite is entirely read-only, which is # # unusual here and is a property of the package. # # python3-types-setuptools 69.0.0.20240125 (meta-openembedded meta-python, # recipes-devtools/python/python3-types-setuptools_69.0.0.20240125.bb, `inherit # pypi setuptools3', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE IS, AND WHAT THAT MAKES THE SUITE ASSERT. # ============================================================================ # # It is the typeshed stub distribution for `setuptools', and it installs TWO # top-level packages from one wheel: `setuptools-stubs' (79 files across five # subdirectories -- the command classes, the `setup'/`Distribution' entry # points, the extension and dependency declarations) and `pkg_resources-stubs' # (7 files -- the `pkg_resources' API and its vendored packaging shims). There # is NOT ONE EXECUTABLE LINE in either of them: no `.py', no `.so', no console # script, no entry point. `install_requires' is empty upstream, and specifically # it does NOT require `setuptools' itself. # # So there is no behaviour to drive, and a suite that pretended otherwise would # be asserting things it made up. What CAN be asserted, and what this suite # does, is four things: # # 1. THE TWO-PACKAGE SHAPE, asserted explicitly rather than as a total. A # partial install that dropped one of the two is the most likely packaging # failure here, and it would leave the other entirely intact -- so every # count below is kept per package rather than summed; # 2. PACKAGING -- that the files the wheel promises are the files on disk, # byte for byte, scored against the wheel's OWN `RECORD' rather than # against a list typed into this suite; # 3. PARSE -- that this arm's interpreter can compile all 84 `.pyi' with its # own `ast.parse' in type-comment mode. This is the one genuine demand a # stubs package makes of a ported interpreter, and this payload is a large # body of it: stub syntax (`@overload' stacks, `...' as an entire function # body, PEP 604 unions in annotation position, bare annotated names, `if # sys.version_info' at class scope) is a corner of the grammar that appears # almost nowhere else in this image's python payload; # 4. STRUCTURE -- that a sample of the declarations is really there, because # an empty file parses perfectly and "it parsed" would otherwise pass a # truncated install. # # ---------------------------------------------------------------------------- # THE SHADOW CASE IS THE ONE THAT MATTERS TO THE REST OF THE IMAGE. # ---------------------------------------------------------------------------- # # `python3-setuptools' is on this image (BRINTOS-2237) and installs BOTH # `setuptools' and `pkg_resources' into the SAME site-packages these stubs land # in. So this port puts annotations for two libraries beside the two libraries, # and the one way a stubs package can break an unrelated part of an image is by # displacing what those names resolve to. `setuptools-no-shadow' and # `pkg-resources-no-shadow' are measured by the resolved `__file__', not by the # import merely succeeding, and `setuptools-setup-callable' reads one attribute # through the real module so "resolved to the right file" and "actually # initialised" stay two separate findings. # # ---------------------------------------------------------------------------- # TWO THINGS THE SUITE RECORDS DIFFERENTLY FROM HOW THEY READ AT FIRST. # ---------------------------------------------------------------------------- # # Both were MEASURED on an x86-64 control against this build's own .deb payload, # rather than reasoned about, and in both cases the obvious wording would have # put a false statement into the record. # # - `pkg_resources.require' IS PRESENT BUT IS NOT A FUNCTION. Upstream # declares it as the module-level ASSIGNMENT `require = working_set.require' # -- a binding of the bound method of the module's own `WorkingSet' instance # -- so it does not appear among the `def's at all. The ticket asks for # `require' and `resource_filename' to be "present"; that is true of both # and means two different things, and a headcount over the module's # functions would have reported `require' missing. `require-not-a-function', # `require-target' and `workingset-require-returns' carry the measured # shape. (`resource_filename' IS an ordinary `def', and is asserted as one.) # # - THE STUBS DIRECTORIES DO IMPORT, EMPTY. `import setuptools-stubs' is a # SyntaxError in any CPython, because the name upstream chose is # deliberately not an identifier. But # `importlib.import_module("setuptools-stubs")' SUCCEEDS: PEP 420 makes any # directory on sys.path a namespace-package portion whatever it is called, # so the name resolves to a module with `__file__' None, a `NamespaceLoader' # and an empty public surface. Its SUBDIRECTORIES resolve too -- `command' # and `config' are directories, so they are nested namespace portions -- and # they are equally empty, which `command-namespace-surface' asserts. Nothing # in the payload is reachable through any of it, because `.pyi' is not in # `SOURCE_SUFFIXES'. "Imports empty and reaches nothing" is the accurate # statement; "cannot be imported" would have been a false one nobody could # later tell from a measurement. # # ---------------------------------------------------------------------------- # WHERE THE EXPECTATIONS COME FROM. # ---------------------------------------------------------------------------- # # Every value asserted below was MEASURED on an x86-64 control under CPython # 3.12.3 -- the same 3.12 this image ships -- running THIS PROBE against THIS # BUILD's own .deb payload, unpacked with `dpkg-deb -x'. The payload was also # compared against the sdist whose sha256 the recipe pins # (`22ad498cb585b22ce8c97ada1fccdf294a2e0dd7dc984a28535a84ea82f45b3f'): # `diff -r' over both `setuptools-stubs' and `pkg_resources-stubs' is clean, so # the recipe repackages upstream without touching a byte. A red here is # therefore this arm, not a version skew and not a repackaging. # # TWO EXPECTED VALUES ARE NOT TAKEN FROM THE CONTROL: `setuptools-file' and # `pkg-resources-file'. The control's own `import setuptools' finds the host's # copy under /usr/lib/python3/dist-packages, so those two are cross-checked # instead against 99-python3-setuptools.test, which already pins # $SITE/setuptools/__init__.py and $SITE/pkg_resources/__init__.py and lands # green. # # ---------------------------------------------------------------------------- # WHAT IS PREDICTED RATHER THAN MEASURED, AND SO MIGHT BE RED. # ---------------------------------------------------------------------------- # # EXPECTED, NOT YET MEASURED -- this is a prediction and is written as one. The # recipe phase cannot build the image and so cannot run this suite; its first # on-target run is the batch verify. # # The prediction is that NOTHING here is red, and it is a weaker claim than it # looks rather than a stronger one: this package ships no code, so the suite's # only exposure to the arm is `ast.parse' and the file reads, and both are # interpreter-internal work over bytes already on the rootfs. There is no # syscall surface for the port to fall through -- no fork, no socket, no device, # no clock, no thread. # # The two places a surprise could come from, in the order they are likely: # # 1. THE PARSE, which is the reason it is in the suite at all. # `parse-node-count' (19378), `ellipsis-body-count' (1074), # `pep604-union-count' (402) and `annassign-no-value-count' (639) pin the # parser's OUTPUT SHAPE and not merely its exit, so a parser that accepted # the files and built a different tree is a red here rather than a silent # pass. If this arm's parser diverges, the predicted shape is a mismatch in # those counts with `parse-failures' still empty -- not an exception. That # is a prediction about the shape, not a claim about the result. # # 2. THE TWO SHADOW CASES, which are the only ones here that import something # this port did not install. They depend on `python3-setuptools' behaving # on this image as 99-python3-setuptools.test already measures it to; if # that suite is red on the same run, these two are its consequence and not # this port's. P=python3-types-setuptools # ============================================================================ # The suite proper. # ============================================================================ # # `suite-canary' is emitted FIRST and unconditionally. A suite that dies before # its first result line is indistinguishable from one that was never scheduled, # so this one line is what separates "ran and found nothing" from "did not run". bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages SD=$SITE/setuptools-stubs PD=$SITE/pkg_resources-stubs DI=$SITE/types_setuptools-69.0.0.20240125.dist-info PKGLIST=/var/lib/dpkg/info/python3-types-setuptools.list PROBE=$BST_DATA/python3-types-setuptools-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-types-setuptools.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ---------------------------------------------------------------- payload --- # WHERE THE PACKAGING SAYS THE FILES LAND. These run before any import, so a red # here says the .deb is wrong rather than that something misbehaved. # # THE TWO PACKAGES ARE ASSERTED AS TWO FACTS. One wheel installs both, and a # packaging failure that dropped either would leave the other whole -- so # `setuptools-stubs-dir' and `pkg-resources-stubs-dir' are separate cases and # every count below is per package. check $P/site-packages-dir test -d "$SITE" check $P/setuptools-stubs-dir test -d "$SD" check $P/pkg-resources-stubs-dir test -d "$PD" check $P/st-init-pyi-present test -s "$SD/__init__.pyi" check $P/pr-init-pyi-present test -s "$PD/__init__.pyi" check $P/st-metadata-toml-present test -s "$SD/METADATA.toml" check $P/pr-metadata-toml-present test -s "$PD/METADATA.toml" # THE SUBPACKAGE DIRECTORIES, which are what make this stub distribution a tree # rather than the flat directory the other stub ports in this batch install. # `_distutils' is the vendored distutils surface setuptools re-exports; # `command' and `_distutils/command' are the command classes the ticket names. check $P/st-dir-command test -d "$SD/command" check $P/st-dir-config test -d "$SD/config" check $P/st-dir-distutils test -d "$SD/_distutils" check $P/st-dir-distutils-command test -d "$SD/_distutils/command" check $P/st-dir-extern test -d "$SD/extern" check $P/pr-dir-vendored test -d "$PD/_vendored_packaging" # THE TOP-LEVEL NAMES OF EACH PACKAGE, in one string each, rather than only the # entries this suite happens to name. LC_ALL=C so the order is the byte order # rather than the guest's locale. The setuptools side's 79 files are too long to # read on a console, so its depth-1 listing is pinned here and the full set is # scored by sha256 against RECORD inside the probe (`record-*' below); the # pkg_resources side is small enough to print whole. check_eq $P/st-top-level-set \ "METADATA.toml,__init__.pyi,_distutils,archive_util.pyi,build_meta.pyi,command,config,dep_util.pyi,depends.pyi,discovery.pyi,dist.pyi,errors.pyi,extension.pyi,extern,glob.pyi,installer.pyi,launch.pyi,logging.pyi,modified.pyi,monkey.pyi,msvc.pyi,namespaces.pyi,package_index.pyi,py312compat.pyi,sandbox.pyi,unicode_utils.pyi,version.pyi,warnings.pyi,wheel.pyi,windows_support.pyi," \ "$(LC_ALL=C ls -1 "$SD" 2>/dev/null | LC_ALL=C sort | tr '\n' ',')" check_eq $P/pr-top-level-set \ "METADATA.toml,__init__.pyi,_vendored_packaging," \ "$(LC_ALL=C ls -1 "$PD" 2>/dev/null | LC_ALL=C sort | tr '\n' ',')" check_eq $P/pr-vendored-set \ "__init__.pyi,markers.pyi,requirements.pyi,specifiers.pyi,version.pyi," \ "$(LC_ALL=C ls -1 "$PD/_vendored_packaging" 2>/dev/null | LC_ALL=C sort | tr '\n' ',')" # THREE SIZES PINNED IN THE SHELL, so a truncated payload is a red that does not # depend on the probe having started. The full set is scored by sha256 against # RECORD inside the probe; these three are the console-legible version of it, # and one of them is taken from each package so a half-install is visible here # too. check_eq $P/st-init-pyi-size 2883 \ "$(wc -c < "$SD/__init__.pyi" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/pr-init-pyi-size 15428 \ "$(wc -c < "$PD/__init__.pyi" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/st-metadata-toml-size 182 \ "$(wc -c < "$SD/METADATA.toml" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # NO EXECUTABLE CODE SHIPS. Asserted RECURSIVELY over both trees, because # "stub-only" is the entire claim this package makes and nothing else in the # suite would notice it being broken. # # The `.so' case is not decoration on this image: python3 here is ONE STATIC # BINARY with an empty lib-dynload and no dynamic loader (BRINTOS-1476), so a # compiled extension would not be a speed-up but an ImportError. check_eq $P/no-py-files "" "$(find "$SD" "$PD" -name '*.py' 2>/dev/null)" check_eq $P/no-so-files "" "$(find "$SD" "$PD" -name '*.so' 2>/dev/null)" check_eq $P/no-pycache "" "$(find "$SD" "$PD" -name '__pycache__' 2>/dev/null)" # NO `py.typed', in either package. A stub-only distribution is discovered by # its `-stubs' suffix under PEP 561 and must NOT carry the marker an # inline-typed package uses; asserted as an absence so a release that added one # is visible. check_eq $P/no-py-typed "" "$(find "$SD" "$PD" -name 'py.typed' 2>/dev/null)" # NOTHING LANDS IN /usr/bin. setup.py declares no `scripts' and no entry points # at all -- the dist-info carries no `entry_points.txt', which # `dist-info-file-set' below pins. check $P/no-bin-script test ! -e /usr/bin/types-setuptools # ------------------------------------------------------------- dist-info --- check $P/dist-info-dir test -d "$DI" check $P/dist-info-record test -s "$DI/RECORD" check $P/dist-info-metadata test -s "$DI/METADATA" check $P/dist-info-wheel test -s "$DI/WHEEL" # THE WHOLE METADATA FILE SET. Four files and no `entry_points.txt' -- the # absence is the assertion, and it is the packaging half of `no-bin-script'. check_eq $P/dist-info-file-set \ "METADATA,RECORD,WHEEL,top_level.txt," \ "$(LC_ALL=C ls -1 "$DI" 2>/dev/null | LC_ALL=C sort | tr '\n' ',')" # THE HYPHENS ARE LOAD-BEARING and are asserted here as data. `top_level.txt' # names BOTH packages -- `pkg_resources-stubs' and `setuptools-stubs', not # `..._stubs' -- because setup.py declares # `packages=["pkg_resources-stubs", "setuptools-stubs"]', names deliberately NOT # valid python identifiers. That single fact is what the two # `import-stmt-syntaxerror' cases below are about, and the pair here is the # packaging-side statement of the two-package shape. check_eq $P/dist-info-top-level "pkg_resources-stubs,setuptools-stubs," \ "$(LC_ALL=C sort "$DI/top_level.txt" 2>/dev/null | tr -d ' \r' | tr '\n' ',')" # --------------------------------------------------------------- dpkg --- # The package is REGISTERED, not merely unpacked. A payload that arrived some # other way would pass every path case above and fail here. One entry from each # package, for the same reason the sizes are. check $P/dpkg-list test -s "$PKGLIST" check $P/dpkg-lists-st-init grep -q "^$SD/__init__\.pyi\$" "$PKGLIST" check $P/dpkg-lists-pr-init grep -q "^$PD/__init__\.pyi\$" "$PKGLIST" check $P/dpkg-lists-command grep -q "^$SD/command/build_ext\.pyi\$" "$PKGLIST" # THE `-dev' AND `-dbg' SPLITS ARE PRODUCED AND EMPTY, so they are not installed # and must not be here. Measured on this build: `python3-types-setuptools-dev' # contains `./' and nothing else, and `-dbg' likewise. Only the main package is # in IMAGE_INSTALL. check $P/no-dev-installed test ! -e /var/lib/dpkg/info/python3-types-setuptools-dev.list check $P/no-dbg-installed test ! -e /var/lib/dpkg/info/python3-types-setuptools-dbg.list # ------------------------------------------------------------------ import --- # ONE case for the import, so the result is legible from the console without # reading the probe's transcript. It is `importlib' and not an `import' # statement, which cannot be written for this name at all -- see the header, and # `st-import-stmt-syntaxerror' below. check $P/import python3 -c 'import importlib; importlib.import_module("setuptools-stubs")' # ------------------------------------------------------------------- probe --- if [ ! -s "$PROBE" ]; then bst_notok $P/probe-present "probe missing at $PROBE" exit 0 fi bst_ok $P/probe-present # 1000s, inside the 1200s suite budget so a hung probe is reported as a probe # overrun naming the last line it printed, rather than as a bare suite timeout. # # The probe EXITS 0 even when the payload is absent, so this case passing means # "the probe ran", not "the package is right" -- `stubs-found' below is what # carries that. bst_probe $P/probe-run "$W/probe" 1000 python3 "$PROBE" OUT="$W/probe" # pv : the value the probe emitted for , or empty. `head -n 1' # because a key is emitted once and a duplicate would otherwise concatenate; # `cut -f2-' because a value may legitimately contain `='. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # The probe reached its end. Every case below reads a value it printed, so a # probe that stopped early would otherwise turn into a run of empty-string # mismatches that all name the wrong thing. check_eq $P/probe-complete "yes" "$(pv probe_done)" _found=$(pv stubs_found) check_eq $P/stubs-found "yes" "$_found" if [ "$_found" != "yes" ]; then # The probe found no `setuptools-stubs' anywhere on sys.path. Skip -- not red # -- every case that reads the payload, each with the reason attached: they # are all one fact, and eighty reds would bury it rather than report it. The # path cases above and `stubs-found' are the reds that carry the verdict. _why="setuptools-stubs not on sys.path; site_dir=$(pv site_dir)" for _c in probe-count site-dir probe-two-packages probe-st-subdirs \ probe-st-file-count probe-st-pyi-count probe-st-top-level-pyi-count \ probe-st-file-set-sha256 probe-pr-file-set probe-pr-file-count \ probe-pr-pyi-count probe-pr-subdirs probe-no-py probe-no-so \ probe-no-pycache probe-no-py-typed probe-no-init-py \ probe-st-has-init-pyi probe-pr-has-init-pyi \ st-metadata-toml-bytes st-metadata-toml-text \ pr-metadata-toml-bytes pr-metadata-toml-text \ record-read record-entries record-missing record-size-mismatch \ record-hash-mismatch record-unlisted record-st-entries \ record-pr-entries parse-ok-count parse-failures parse-node-count \ parse-negative-control ellipsis-body-count \ ellipsis-class-body-count overload-count \ annassign-no-value-count pep604-union-count \ st-init-parsed st-init-classes st-init-functions st-init-all \ st-init-reexports setup-declared setup-kwonly-params \ setup-kwonly-count setup-positional-count setup-kwargs \ setup-returns st-command-bases extension-pyi-parsed \ extension-is-class extension-bases extension-init-params \ extension-classes dist-pyi-parsed distribution-is-class \ distribution-bases distribution-methods distribution-method-count \ st-command-modules st-distutils-command-modules \ pr-init-parsed pr-class-count pr-function-count pr-functions \ resource-filename-declared resource-filename-params \ require-not-a-function require-target \ workingset-require-returns pr-distribution-not-found-bases \ pr-version-conflict-bases st-import-stmt-syntaxerror \ pr-import-stmt-syntaxerror st-import-module-ok \ st-import-module-file st-import-module-origin \ st-import-module-loader st-import-module-is-namespace \ st-import-module-surface pr-import-module-ok \ pr-import-module-file pr-import-module-loader \ pr-import-module-is-namespace pr-import-module-surface \ st-importable-submodules pr-importable-submodules \ command-namespace-surface pyi-in-source-suffixes \ setuptools-import-ok setuptools-file setuptools-no-shadow \ pkg-resources-import-ok pkg-resources-file \ pkg-resources-no-shadow setuptools-setup-callable \ probe-dist-info-file-set meta-name meta-version meta-license \ meta-requires-python meta-requires-dist probe-dist-top-level \ wheel-root-is-purelib importlib-metadata-version \ importlib-metadata-file-count; do bst_skip $P/$_c "$_why" done rm -rf "$W" exit 0 fi # The probe emitted every key it intended to. A probe that returned 0 having # skipped a section would otherwise leave those cases reading empty strings. check_eq $P/probe-count "113" "$(pv emitted_count)" # WHERE THE PAYLOAD WAS FOUND. Pinned to site-packages so a directory satisfied # from somewhere else on sys.path -- a leftover in the cwd, a second install -- # is a red here rather than a green suite measuring the wrong tree. check_eq $P/site-dir "$SITE" "$(pv site_dir)" # THE TWO-PACKAGE SHAPE, read a second way. The shell asserted both directories # above; this is the probe's own view, and a disagreement between them would # itself be the finding. check_eq $P/probe-two-packages "yes,yes" \ "$(pv has_setuptools_stubs),$(pv has_pkg_resources_stubs)" # ---------------------------------------------------- the setuptools side --- # 79 files across five subdirectories. The subdirectory set is what a release # adding or dropping a stub subpackage changes; the counts are what a partial # install changes; the sha256 over the sorted path list is what ANYTHING # changes. Which file differs is answered by `record-missing' / # `record-unlisted' below, so nothing is lost by not printing all 79 here. check_eq $P/probe-st-subdirs \ "_distutils,_distutils/command,command,config,extern" "$(pv st_subdir_set)" check_eq $P/probe-st-file-count "79" "$(pv st_file_count)" check_eq $P/probe-st-pyi-count "78" "$(pv st_pyi_count)" check_eq $P/probe-st-top-level-pyi-count "25" "$(pv st_top_level_pyi_count)" check_eq $P/probe-st-file-set-sha256 \ "abeec71de13218fb2516288cf807572e2da699ada6100ed62d088cf8d65241b8" \ "$(pv st_file_set_sha256)" # ------------------------------------------------- the pkg_resources side --- # Seven files, printed whole. This is the half a partial install is most likely # to drop -- it is the smaller of the two and is installed from the same wheel # -- and a name is more use than a hash when it does. check_eq $P/probe-pr-file-set \ "METADATA.toml,__init__.pyi,_vendored_packaging/__init__.pyi,_vendored_packaging/markers.pyi,_vendored_packaging/requirements.pyi,_vendored_packaging/specifiers.pyi,_vendored_packaging/version.pyi" \ "$(pv pr_file_set)" check_eq $P/probe-pr-file-count "7" "$(pv pr_file_count)" check_eq $P/probe-pr-pyi-count "6" "$(pv pr_pyi_count)" check_eq $P/probe-pr-subdirs "_vendored_packaging" "$(pv pr_subdir_set)" check_eq $P/probe-no-py "" "$(pv py_files)" check_eq $P/probe-no-so "" "$(pv so_files)" check_eq $P/probe-no-pycache "" "$(pv pycache_dirs)" check_eq $P/probe-no-py-typed "" "$(pv py_typed_files)" check_eq $P/probe-no-init-py "" "$(pv init_py_files)" check_eq $P/probe-st-has-init-pyi "yes" "$(pv st_has_init_pyi)" check_eq $P/probe-pr-has-init-pyi "yes" "$(pv pr_has_init_pyi)" # EACH STUB PACKAGE CARRIES ITS OWN MARKER FILE, and they are read separately # rather than assumed identical -- the pair is what says both halves of the # wheel landed and both aim at the same setuptools release. That release # (`69.0.*') is the one fact about this package not derivable from the file # names, and it is the fact that makes these annotations meaningful beside this # image's setuptools 69.1.1 rather than merely present. Newlines are flattened # to `|' by the probe so each value stays one counted result. _toml='version = "69.0.*"|upstream_repository = "https://github.com/pypa/setuptools"||[tool.stubtest]|# darwin is equivalent to linux for OS-specific methods|platforms = ["linux", "win32"]' check_eq $P/st-metadata-toml-bytes "182" "$(pv st_metadata_toml_bytes)" check_eq $P/st-metadata-toml-text "$_toml" "$(pv st_metadata_toml_text)" check_eq $P/pr-metadata-toml-bytes "182" "$(pv pr_metadata_toml_bytes)" check_eq $P/pr-metadata-toml-text "$_toml" "$(pv pr_metadata_toml_text)" # ------------------------------------------------- payload vs the wheel --- # THE INSTALLED SET SCORED AGAINST THE WHEEL'S OWN `RECORD' -- its sha256 and # byte count for every file it installed -- rather than against a list typed # into this suite. This is the case the ticket asks for by name, and it is the # strongest one here: it makes a packaging change VISIBLE instead of leaving it # to be noticed. # # Four empties and a count, deliberately split rather than folded into one # verdict: "a file is missing", "a file is the wrong length", "a file has the # wrong bytes" and "a file is installed that RECORD does not list" are four # different faults with four different causes, and one combined red would name # none of them. check_eq $P/record-read "yes" "$(pv record_read)" check_eq $P/record-entries "90" "$(pv record_entries)" check_eq $P/record-missing "" "$(pv record_missing)" check_eq $P/record-size-mismatch "" "$(pv record_size_mismatch)" check_eq $P/record-hash-mismatch "" "$(pv record_hash_mismatch)" check_eq $P/record-unlisted "" "$(pv record_unlisted)" # RECORD'S OWN TWO-PACKAGE SPLIT, counted apart from the total. A wheel that # shipped only one of the two would still produce a self-consistent RECORD, so # "the manifest matches the disk" alone cannot see it. 79 + 7 + the four # dist-info entries is the 90 above. check_eq $P/record-st-entries "79" "$(pv record_setuptools_entries)" check_eq $P/record-pr-entries "7" "$(pv record_pkg_resources_entries)" # ------------------------------------------------------------------ parse --- # ALL 84 `.pyi' THROUGH THIS ARM'S OWN `ast.parse', in `type_comments=True' mode # -- the mode a type checker uses, and a genuinely different code path in # CPython's parser rather than a flag it ignores. check_eq $P/parse-ok-count "84" "$(pv parse_ok_count)" check_eq $P/parse-failures "" "$(pv parse_failures)" # THE PARSER'S OUTPUT SHAPE, not merely its exit status. An empty file parses # perfectly and a parser that silently dropped constructs would still report 84 # successes; the node count is what makes those two different results. check_eq $P/parse-node-count "19378" "$(pv parse_node_count)" # THE PARSE HAS TEETH. A harness that caught everything would report # `parse-ok-count' 84 for 84 good files and for 84 empty ones alike. Feeding the # same parser, in the same mode, a file that MUST fail is what says the mode is # really on and the exception really propagates. check_eq $P/parse-negative-control "yes" "$(pv parse_negative_control)" # THE FOUR CONSTRUCTS THAT MAKE STUB SYNTAX ITS OWN CORNER OF THE GRAMMAR, # counted structurally off the tree rather than assumed. `...' as an entire # function body is the form this whole distribution is written in; the class-body # form is a different production and is counted apart (`class Library(Extension): # ...' is how the command and error hierarchies here are written). `@overload' is # the one a parser is most likely to get wrong. The last two are ANNOTATION # grammar rather than statement grammar -- a bare annotated name with no value, # and a PEP 604 `X | Y' union in annotation position -- and this payload is # unusually dense in both. check_eq $P/ellipsis-body-count "1074" "$(pv ellipsis_body_count)" check_eq $P/ellipsis-class-body-count "54" "$(pv ellipsis_class_body_count)" check_eq $P/overload-count "22" "$(pv overload_count)" check_eq $P/annassign-no-value-count "639" "$(pv annassign_no_value_count)" check_eq $P/pep604-union-count "402" "$(pv pep604_union_count)" # -------------------------------------------------------------- structure --- # A SAMPLE OF THE DECLARATIONS, so a truncated install is caught rather than # passing as "it parsed". check_eq $P/st-init-parsed "yes" "$(pv st_init_parsed)" check_eq $P/st-init-classes "Command,sic" "$(pv st_init_classes)" check_eq $P/st-init-functions "find_packages,find_namespace_packages,setup" \ "$(pv st_init_functions)" # `__all__' AS THE PACKAGE'S OWN STATEMENT OF ITS SURFACE, read off the tree # rather than off a name list typed here. It is the one place the stub says # which names it means to export, in upstream's own order. check_eq $P/st-init-all \ "setup,Distribution,Command,Extension,Require,SetuptoolsDeprecationWarning,find_packages,find_namespace_packages" \ "$(pv st_init_all)" # THE RE-EXPORTS. `Distribution' and `Extension' -- two of the three entry points # the ticket names -- are not DECLARED in `__init__.pyi'; they are imported from # `.dist' and `.extension' under the PEP 484 `import X as X' form that makes an # import a re-export. Asserting the alias is how this suite says "the entry point # is reachable from the package root" without pretending it is a class definition # in that file -- and the classes themselves are asserted below, where they are. check_eq $P/st-init-reexports \ "depends:Require,dist:Distribution,extension:Extension,warnings:SetuptoolsDeprecationWarning" \ "$(pv st_init_reexports)" # `setup' -- THE ENTRY POINT THE TICKET NAMES FIRST -- BY ITS DECLARED KEYWORDS. # Upstream declares it keyword-only (a bare `*' first) with a `**attrs' # catch-all, which is the shape a caller writing a setup.py actually meets. All # four halves are asserted: the 39 parameter names in upstream's order, the count, # the fact that there is NOT ONE positional among them, and the `-> None'. The # name list alone would not notice the `*' being dropped, and the count alone # would not notice a keyword being renamed. check_eq $P/setup-declared "yes" "$(pv setup_declared)" check_eq $P/setup-kwonly-params \ "name,version,description,long_description,long_description_content_type,author,author_email,maintainer,maintainer_email,url,download_url,packages,py_modules,scripts,ext_modules,classifiers,distclass,script_name,script_args,options,license,keywords,platforms,cmdclass,data_files,package_dir,obsoletes,provides,requires,command_packages,command_options,package_data,include_package_data,libraries,headers,ext_package,include_dirs,password,fullname" \ "$(pv setup_kwonly_params)" check_eq $P/setup-kwonly-count "39" "$(pv setup_kwonly_count)" check_eq $P/setup-positional-count "0" "$(pv setup_positional_count)" check_eq $P/setup-kwargs "attrs" "$(pv setup_kwargs)" check_eq $P/setup-returns "None" "$(pv setup_returns)" # `Command' IS DECLARED IN `__init__.pyi' and inherits distutils' own, which is # the fact that makes the command modules below a hierarchy rather than a flat # set. `_Command' is the alias `from ._distutils.cmd import Command as _Command' # binds, so the base being spelled that way is itself part of the assertion. check_eq $P/st-command-bases "_Command" "$(pv st_command_bases)" # `Extension' AS A CLASS, in the file that declares it -- the ticket asks for it # by name. Its base is distutils' own `Extension', which is the whole content of # this stub: setuptools' version adds `py_limited_api' and nothing else. The # `__init__' parameter list is pinned because it is the compiled-extension # declaration surface, and `Library' is asserted beside it so a release that # dropped the subclass is visible. check_eq $P/extension-pyi-parsed "yes" "$(pv extension_pyi_parsed)" check_eq $P/extension-is-class "yes" "$(pv extension_is_class)" check_eq $P/extension-bases "_Extension" "$(pv extension_bases)" check_eq $P/extension-init-params \ "self,name,sources,include_dirs,define_macros,undef_macros,library_dirs,libraries,runtime_library_dirs,extra_objects,extra_compile_args,extra_link_args,export_symbols,swig_opts,depends,language,optional,py_limited_api" \ "$(pv extension_init_params)" check_eq $P/extension-classes "Extension,Library" "$(pv extension_classes)" # `Distribution' AS A CLASS -- the third entry point the ticket names. Same # shape: it extends distutils' `Distribution' and adds the setuptools-only # surface, so the base and the method set are both asserted. The count is kept # beside the set because the set alone would not notice an overload being lost. check_eq $P/dist-pyi-parsed "yes" "$(pv dist_pyi_parsed)" check_eq $P/distribution-is-class "yes" "$(pv distribution_is_class)" check_eq $P/distribution-bases "_Distribution" "$(pv distribution_bases)" check_eq $P/distribution-methods \ "__init__,exclude,exclude_package,fetch_build_egg,fetch_build_eggs,get_cmdline_options,get_command_class,get_egg_cache_dir,handle_display_options,has_contents_for,include,iter_distribution_names,make_option_lowercase,parse_config_files,patch_missing_pkg_info,warn_dash_deprecation" \ "$(pv distribution_methods)" check_eq $P/distribution-method-count "16" "$(pv distribution_method_count)" # THE COMMAND CLASSES, which are the bulk of what this distribution describes # and the part the ticket calls "the command classes". Both directories are # pinned as their module sets: setuptools' own 25 (including the `__init__.pyi' # that makes `command' a stub subpackage) and the 11 distutils commands # setuptools re-declares. check_eq $P/st-command-modules \ "__init__.pyi,alias.pyi,bdist_egg.pyi,bdist_rpm.pyi,build.pyi,build_clib.pyi,build_ext.pyi,build_py.pyi,develop.pyi,dist_info.pyi,easy_install.pyi,editable_wheel.pyi,egg_info.pyi,install.pyi,install_egg_info.pyi,install_lib.pyi,install_scripts.pyi,register.pyi,rotate.pyi,saveopts.pyi,sdist.pyi,setopt.pyi,test.pyi,upload.pyi,upload_docs.pyi" \ "$(pv st_command_modules)" check_eq $P/st-distutils-command-modules \ "bdist_rpm.pyi,build.pyi,build_clib.pyi,build_ext.pyi,build_py.pyi,install.pyi,install_lib.pyi,install_scripts.pyi,register.pyi,sdist.pyi,upload.pyi" \ "$(pv st_distutils_command_modules)" # ----------------------------------------------- pkg_resources structure --- # THE SECOND PACKAGE'S OWN SURFACE. 27 classes and 39 function declarations over # 36 names -- the gap is `@overload' stacks and is not an error, which is why # both the count and the set are asserted. check_eq $P/pr-init-parsed "yes" "$(pv pr_init_parsed)" check_eq $P/pr-class-count "27" "$(pv pr_class_count)" check_eq $P/pr-function-count "39" "$(pv pr_function_count)" check_eq $P/pr-functions \ "cleanup_resources,compatible_platforms,declare_namespace,ensure_directory,evaluate_marker,find_distributions,fixup_namespace_packages,get_build_platform,get_default_cache,get_distribution,get_entry_info,get_entry_map,get_importer,get_platform,get_provider,get_supported_platform,invalid_marker,load_entry_point,normalize_path,parse_requirements,register_finder,register_loader_type,register_namespace_handler,resource_exists,resource_filename,resource_isdir,resource_listdir,resource_stream,resource_string,safe_extra,safe_name,safe_version,set_extraction_path,split_sections,to_filename,yield_lines" \ "$(pv pr_functions)" # `resource_filename' IS AN ORDINARY `def', and is asserted as one, with its two # parameters. check_eq $P/resource-filename-declared "yes" "$(pv pr_has_resource_filename)" check_eq $P/resource-filename-params \ "package_or_requirement,resource_name" "$(pv pr_resource_filename_params)" # `require' IS PRESENT AND IS NOT A FUNCTION, which is the header's first # correction and the reason these three cases exist rather than one. Upstream # declares it as the module-level ASSIGNMENT `require = working_set.require', # binding the bound method of the module's own `WorkingSet' instance -- so it # does not appear among the `def's at all, and a headcount over # `pr-functions' above would have reported it missing. The three cases say, in # order: it is not a function; it is bound to that expression; and the method # that expression names is really declared, with its return type. The third is # what stops a stub that had dropped `WorkingSet' from passing on the alias # alone. check_eq $P/require-not-a-function "no" "$(pv pr_require_is_function)" check_eq $P/require-target "working_set.require" "$(pv pr_require_target)" check_eq $P/workingset-require-returns "Sequence[Distribution]" \ "$(pv pr_workingset_require_returns)" # THE ERROR HIERARCHY, which is the part of `pkg_resources' a caller catches # rather than calls, and the part a truncated stub would silently lose. Both # derive from `ResolutionError' -- asserted as the base rather than as # "the class exists", because a class that lost its base still exists. check_eq $P/pr-distribution-not-found-bases "ResolutionError" \ "$(pv pr_distribution_not_found_bases)" check_eq $P/pr-version-conflict-bases "ResolutionError" \ "$(pv pr_version_conflict_bases)" # ----------------------------------------------------------------- import --- # WHAT `import' ACTUALLY DOES WITH A STUBS-ONLY DIRECTORY. The header explains # why these cases say "imports empty and reaches nothing" rather than "cannot be # imported"; the short version is that the second was measured to be false and # the first to be true. # THE IMPORT STATEMENT CANNOT BE WRITTEN, for either package. Neither # `setuptools-stubs' nor `pkg_resources-stubs' is an identifier, so both are a # SyntaxError in any CPython -- a fact about the names upstream chose, asserted # because it is the accurate half of the "not importable" premise. check_eq $P/st-import-stmt-syntaxerror "yes" "$(pv st_import_stmt_syntaxerror)" check_eq $P/pr-import-stmt-syntaxerror "yes" "$(pv pr_import_stmt_syntaxerror)" # BUT `importlib.import_module' RESOLVES BOTH, as PEP 420 namespace packages. check_eq $P/st-import-module-ok "yes" "$(pv st_import_module_ok)" check_eq $P/st-import-module-file "None" "$(pv st_import_module_file)" check_eq $P/st-import-module-origin "None" "$(pv st_import_module_origin)" check_eq $P/st-import-module-loader "NamespaceLoader" "$(pv st_import_module_loader)" check_eq $P/st-import-module-is-namespace "yes" "$(pv st_import_module_is_namespace)" check_eq $P/st-import-module-surface "" "$(pv st_import_module_surface)" check_eq $P/pr-import-module-ok "yes" "$(pv pr_import_module_ok)" check_eq $P/pr-import-module-file "None" "$(pv pr_import_module_file)" check_eq $P/pr-import-module-loader "NamespaceLoader" "$(pv pr_import_module_loader)" check_eq $P/pr-import-module-is-namespace "yes" "$(pv pr_import_module_is_namespace)" check_eq $P/pr-import-module-surface "" "$(pv pr_import_module_surface)" # AND THEY DELIVER NOTHING. This is the half that makes the namespace packages # harmless -- and it is the case that differs most from the flat stub ports # beside this one, so the expected value is NOT empty and is not a mistake. # # `setuptools-stubs.command' and `setuptools-stubs.config' ARE directories, so # PEP 420 gives each a spec of its own and `import_module' resolves them as # nested namespace portions; `pkg_resources-stubs._vendored_packaging' likewise. # What does NOT resolve is any `.pyi' -- `dist', `extension', # `command.build_ext' and `__init__' are all absent from these lists -- because # `.pyi' is not a source suffix. `command-namespace-surface' is the other half: # the directory that resolved hands back nothing. check_eq $P/st-importable-submodules "command,config" "$(pv st_importable_submodules)" check_eq $P/pr-importable-submodules "_vendored_packaging" "$(pv pr_importable_submodules)" check_eq $P/command-namespace-surface "" "$(pv st_command_namespace_surface)" # THE MECHANISM UNDERNEATH THAT. `.pyi' is not a source suffix, so the finder # never offers one of these 84 files however the name is spelled -- which is why # the namespace packages above can be empty while 84 parseable modules sit # inside them. check_eq $P/pyi-in-source-suffixes "no" "$(pv pyi_in_source_suffixes)" # ------------------------------------------------------------- no shadow --- # THE REAL LIBRARIES ARE UNAFFECTED, and this is the one way a stubs package can # break an unrelated part of the image. `python3-setuptools' (BRINTOS-2237) puts # BOTH `setuptools' and `pkg_resources' into this same site-packages, so these # two cases say that installing annotations for them did not change what # importing them resolves to. Measured by the resolved `__file__', not by the # import merely succeeding. # # `setuptools-file' and `pkg-resources-file' are the two expected values here NOT # taken from the x86-64 control -- the control's own imports find the host's # copies under /usr/lib/python3/dist-packages. They are cross-checked instead # against 99-python3-setuptools.test, which pins these same two paths and lands # green. check_eq $P/setuptools-import-ok "yes" "$(pv setuptools_import_ok)" check_eq $P/setuptools-file "$SITE/setuptools/__init__.py" "$(pv setuptools_file)" check_eq $P/setuptools-no-shadow "no" "$(pv setuptools_from_stubs_dir)" check_eq $P/pkg-resources-import-ok "yes" "$(pv pkg_resources_import_ok)" check_eq $P/pkg-resources-file "$SITE/pkg_resources/__init__.py" \ "$(pv pkg_resources_file)" check_eq $P/pkg-resources-no-shadow "no" "$(pv pkg_resources_from_stubs_dir)" # RESOLVING TO THE RIGHT FILE AND ACTUALLY INITIALISING ARE TWO FACTS, and only # the second is what a caller gets. One attribute read through the real module # is what separates them. check_eq $P/setuptools-setup-callable "yes" "$(pv setuptools_setup_callable)" # ---------------------------------------------------------- installed meta --- check_eq $P/probe-dist-info-file-set "METADATA,RECORD,WHEEL,top_level.txt" \ "$(pv dist_info_file_set)" check_eq $P/meta-name "types-setuptools" "$(pv meta_name)" check_eq $P/meta-version "69.0.0.20240125" "$(pv meta_version)" # `Apache-2.0 license' -- the trailing word is upstream's, from setup.py's # `license=' string, and the recipe's LICENSE is the clean `Apache-2.0'. Pinned # as it is rather than as it ought to be. check_eq $P/meta-license "Apache-2.0 license" "$(pv meta_license)" check_eq $P/meta-requires-python ">=3.8" "$(pv meta_requires_python)" # NO RUNTIME DEPENDENCY IS DECLARED, AND SPECIFICALLY NOT ON `setuptools'. This # is the fact that lets a stubs package be installed on an image that does not # carry the library it describes -- `install_requires=[]' upstream -- and it is # what makes the recipe's `Depends: python3-core' correct rather than thin. A # `Requires-Dist:' line appearing here is a release changing its mind. It is # also what keeps this port independent of BRINTOS-2237: the two are neighbours # on the image, not a dependency pair. check_eq $P/meta-requires-dist "none" "$(pv meta_requires_dist)" # BOTH NAMES, from the wheel's own `top_level.txt', flattened to `|'. The # metadata half of the two-package shape. check_eq $P/probe-dist-top-level "pkg_resources-stubs|setuptools-stubs" \ "$(pv dist_top_level)" # A PURE-PYTHON WHEEL, from the wheel's own metadata. It is the packaging-side # statement of `no-so-files' above: `Root-Is-Purelib: true' means nothing in it # is platform-specific, which for a wasm32 image is the difference between a # package that ports and one that cannot. check_eq $P/wheel-root-is-purelib "true" "$(pv wheel_root_is_purelib)" # `importlib.metadata' MUST ANSWER FOR IT. The dist-info being on disk and the # interpreter being able to FIND it are two different facts, and only the second # is what a caller asking "is types-setuptools installed" actually uses. The file # count is RECORD's 90 read back through the metadata API. check_eq $P/importlib-metadata-version "69.0.0.20240125" "$(pv importlib_metadata_version)" check_eq $P/importlib-metadata-file-count "90" "$(pv importlib_metadata_file_count)" rm -rf "$W" exit 0