. "$BST_LIB" # BST-TIMEOUT: 1200 # TWO python3 starts, and they are the whole cost. Every # # other case below reads the filesystem or dpkg with shell # # builtins, or greps the output of the second start. # # # # The two are: a bare `import' (one case, so that an import # # failure is legible without reading a transcript) and the # # probe, which takes ONE start for a hundred-odd # # measurements. The work inside each is the creation of nine # # small container classes and a few dozen attribute reads -- # # microseconds beside an interpreter start on this arm, # # which is tens of seconds before the first line of the # # program runs. # # # # 1200 is 600s per start, ten times the ~60s a python3 start # # has measured at here and the same per-start budget # # 99-python3-annotated-types gives its two. Nothing below is # # quadratic in anything: the largest loop is `__invert__', # # which walks three flags. # # # # A suite-timeout red is a batch-wide cost, so if the two # # starts prove slower than that on target the RIGHT fix is # # this number, not fewer cases. # # # # 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-constantly 23.10.4 (meta-openembedded/meta-python # recipes-devtools/python/python3-constantly_23.10.4.bb, PyPI sdist) on the # wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # It is Twisted's symbolic-constant library, and it predates `enum' in the # stdlib. A consumer SUBCLASSES one of three containers -- `Names', `Values', # `Flags' -- and puts `NamedConstant()', `ValueConstant(v)' or `FlagConstant()' # instances in the class body; a metaclass walks the body at class-creation time # and REALIZES each one, giving it its name and its container. The container # class object is the enumeration; it is never instantiated. # # So every case below asserts something about a CLASS this suite's probe # defines, not about a fixture on disk. There is nothing to read, nothing to # open and nothing to talk to: the whole of the package's behaviour is visible # from one interpreter start with no filesystem beyond the import. # # ============================================================================ # WHY THIS PORT IS UNEVENTFUL, AND WHAT THAT MEANS FOR THE SUITE. # ============================================================================ # # It is PURE PYTHON: no ext_modules, no `.so', nothing to link. This image's # python3 is ONE STATIC BINARY with an empty lib-dynload and no dynamic loader # (BRINTOS-1476), so a module carrying a compiled extension could not be # imported here however well it built -- and the `no-so' case below asserts that # negative on the payload rather than on the recipe, so a release that grew a # compiled speed-up fails HERE rather than at `import' on target. # # It is also unusually arm-blind even for a pure-Python package. `_constants.py' # has NO `sys.version_info' test, no `platform' import and no `struct' use; its # whole dependency surface is `functools.partial', `itertools.count' and three # `operator' functions. There is nothing in it that can know the pointer width. # So a difference between this arm and an x86-64 control is a DEFECT, not a # property -- which is what makes the control comparison below the right shape, # and what makes `py-maxsize-bits' load-bearing: it is the one case that says the # transcript came from the wasm32 interpreter rather than from a host copy of the # module, since every other value would pass either way. # # ============================================================================ # WHERE THE EXPECTED VALUES COME FROM. # ============================================================================ # # MEASURED ON AN x86-64 CONTROL AT THE SAME VERSION, not read off the source. # The control ran THIS SUITE'S OWN PROBE against constantly 23.10.4 unpacked # from the same PyPI sdist the recipe fetches (sha256 aa92b70a...c7cbd), under # CPython 3.12 -- the same interpreter minor this image ships. The control was # then re-run against THIS BUILD's own .deb payload # (`dpkg-deb -x python3-constantly_23.10.4-r0_wasm32.deb'), which produced a # transcript BYTE-IDENTICAL to the first apart from the two path values # (`module-file' and `test-pkg-file'), both of which are the target's own paths # and are asserted directly rather than compared. # # Four control results are worth naming because they are not what the source or # this ticket reads like: # # * THE REPR IS `', NOT `Container.name'. This ticket asks for # "the documented `ContainerName.constantName' form"; `_Constant.__repr__' is # `"<%s=%s>" % (self._container.__name__, self.name)' and has never produced # a dotted form. What is asserted below is the measured angle-bracket form. # # * A COMPOSITE FLAG'S NAME SORTS ALPHABETICALLY, and that is a real asymmetry # with `iterconstants()', which is in DECLARATION order. `READ | WRITE | # EXEC' has the name `{EXEC,READ,WRITE}' -- `_realize' does # `",".join(sorted(names))'. A suite that assumed one order everywhere would # have been wrong in exactly one place. # # * `lookupByValue' RETURNS THE FIRST BY DECLARATION ORDER when two constants # share a value, and does not raise. It walks the sorted `iterconstants()' # and returns on the first `=='. This ticket asks for that behaviour and it # is the one prediction in the ticket text that the control confirmed # unchanged. # # * THE SHIPPED TEST MODULE CANNOT IMPORT, ON EITHER ARM. # `constantly/test/test_constants.py' does `from twisted.trial.unittest # import TestCase', and twisted is not ported to this image and was not # installed on the control either. The file IS in the .deb -- 37364 bytes of # it -- so the suite asserts the subpackage imports, the module does not, and # the reason. That is a fact about upstream's packaging, not a red: nothing # on this image was ever going to satisfy it, and `test-mod-import' below # pins the measured ModuleNotFoundError rather than skipping the question. # # ============================================================================ # THE BUILD BACKEND IS setuptools, NOT poetry-core. # ============================================================================ # # The recipe says `inherit pypi python_poetry_core'. The sdist's pyproject.toml # says `build-backend = "setuptools.build_meta"' with # `requires = ["setuptools>=68.2", "versioneer[toml]==0.29"]', and the wheel this # build produced says `Generator: bdist_wheel (0.42.0)'. `python_pep517' runs # `python3 -m build --no-isolation', which reads the backend out of # pyproject.toml, so the inherit contributes a `python3-poetry-core-native' # DEPENDS that nothing uses and changes nothing else -- which is why the port # builds cleanly in spite of it, and why the recipe's OTHER build dependency, # `python3-versioneer-native', is the one that is load-bearing. `wheel-generator' # below is the case that would notice if that ever stopped being true. # # ============================================================================ # # `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". P=python3-constantly bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKG=$SITE/constantly DI=$SITE/constantly-23.10.4.dist-info PKGLIST=/var/lib/dpkg/info/python3-constantly.list PROBE=$BST_DATA/python3-constantly-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-constantly.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ---------------------------------------------------------------- payload --- # WHERE THE PACKAGING SAYS THE MODULE LANDS. These run before any import, so a # red here says the .deb is wrong rather than that the module misbehaves. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKG" check $P/init-present test -s "$PKG/__init__.py" check $P/constants-present test -s "$PKG/_constants.py" check $P/version-present test -s "$PKG/_version.py" check $P/test-pkg-dir test -d "$PKG/test" check $P/test-init-present test -s "$PKG/test/__init__.py" check $P/test-module-present test -s "$PKG/test/test_constants.py" # THE SIZES ARE PINNED RATHER THAN FLOORED: a truncated or partially written # payload passes `test -s' and fails only here. Every figure is what the wheel's # RECORD declares, and each was read back off this build's own .deb. check_eq $P/init-size 497 \ "$(wc -c < "$PKG/__init__.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/constants-size 16005 \ "$(wc -c < "$PKG/_constants.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # `_version.py' IS A GENERATED FILE and it is pinned anyway. versioneer # regenerates it at build time (`versionfile_build' in pyproject.toml), and this # build's copy came out BYTE-IDENTICAL to the one the sdist ships -- measured by # diff. A build that could not work out its version would write a different file # here carrying `0+unknown', and this case is where that lands. check_eq $P/version-size 499 \ "$(wc -c < "$PKG/_version.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/test-init-size 104 \ "$(wc -c < "$PKG/test/__init__.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/test-module-size 37364 \ "$(wc -c < "$PKG/test/test_constants.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # NO `py.typed'. This package predates PEP 561 and ships no inline-annotation # marker, unlike every other python3-* port in this batch. Asserted as the # negative so a release that added one is a visible change rather than an # unexercised file. check $P/no-py-typed test ! -e "$PKG/py.typed" # NO COMPILED EXTENSION SHIPS, and this is the case that would notice a release # that grew one. On this image a `.so' in site-packages is not a speed-up, it is # an ImportError: python3 here is one static binary with an empty lib-dynload # (BRINTOS-1476). Asserted over the payload rather than over the recipe. check_eq $P/no-so "" "$(find "$PKG" -name '*.so' 2>/dev/null)" # The byte-compiled copies are part of the payload and are why `import' on this # target is a read rather than a parse of 16 KB of source. Asserted present and # non-empty rather than pinned: a .pyc header carries build-time state the # source does not. check $P/pycache-init test -s "$PKG/__pycache__/__init__.cpython-312.pyc" check $P/pycache-constants test -s "$PKG/__pycache__/_constants.cpython-312.pyc" check $P/pycache-version test -s "$PKG/__pycache__/_version.cpython-312.pyc" # THE SHIPPED TESTS ARE BYTE-COMPILED TOO, all 60 KB of them, for a module that # cannot import on this image. Asserted because it is the largest single file in # the payload and its presence is a packaging decision worth being able to see. check $P/pycache-test test -s "$PKG/test/__pycache__/test_constants.cpython-312.pyc" # NO EXECUTABLE. pyproject.toml declares no [project.scripts] table, so nothing # lands in /usr/bin and the dist-info carries no entry_points.txt. Asserted so a # future release that started shipping one is noticed rather than silently # unexercised. check $P/no-bin-script test ! -e /usr/bin/constantly check $P/no-entrypoints test ! -e "$DI/entry_points.txt" # --------------------------------------------------------------- dist-info --- check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" check $P/dist-info-wheel test -f "$DI/WHEEL" check_out $P/dist-info-version "Version: 23.10.4" cat "$DI/METADATA" check_out $P/dist-info-name "Name: constantly" cat "$DI/METADATA" # THE LICENSE LANDS AT THE ROOT OF dist-info, not under `licenses/'. That is the # setuptools placement, and it is the opposite of what hatchling does -- which is # why this line differs from 99-python3-annotated-types' and is pinned by path # rather than assumed to be the same. The recipe's LIC_FILES_CHKSUM md5 is over # this file's source. check $P/license-file test -s "$DI/LICENSE" check_out $P/meta-license-file "License-File: LICENSE" cat "$DI/METADATA" # `top_level.txt' NAMES ONE PACKAGE, and it is the setuptools artefact hatchling # does not produce. It says `constantly' and nothing else -- so the shipped # `constantly.test' subpackage is a SUBpackage rather than a second top level, # which is what keeps it from colliding with anything named `test'. check_eq $P/top-level "constantly" "$(cat "$DI/top_level.txt" 2>/dev/null)" # PURELIB, STATED BY THE PACKAGING ITSELF, and the wheel tag is RIGHT. # `Root-Is-Purelib: true' with `Tag: py3-none-any' is the wheel's own claim that # it contains nothing architecture-specific -- which is the one-line summary of # why this port is uneventful. # # `wheel-generator' is the other half of the recipe finding in the header: the # wheel was cut by setuptools' bdist_wheel, NOT by poetry-core, in spite of # `inherit python_poetry_core'. If a future poky made the inherit actually pick # the backend, this case is where it would surface -- as a changed generator on # a package whose pyproject.toml asks for setuptools. check_out $P/wheel-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/wheel-tag "Tag: py3-none-any" cat "$DI/WHEEL" check_out $P/wheel-generator "Generator: bdist_wheel" cat "$DI/WHEEL" # THE DECLARED RUNTIME DEPENDENCY SET IS EMPTY. METADATA carries no # `Requires-Dist' at all -- constantly needs nothing but the interpreter, and # `python3-json' in the recipe's RDEPENDS is there because `_version.py' does # `import json', which is a STDLIB SPLIT on this image rather than a third-party # package. `no-requires-dist' and `pkg-depends-python3-json' below are the two # halves of that, and they are asserted together because they look contradictory # apart. check_eq $P/no-requires-dist "" \ "$(grep '^Requires-Dist:' "$DI/METADATA" 2>/dev/null)" check_out $P/meta-requires-python "Requires-Python: >=3.8" cat "$DI/METADATA" # ------------------------------------------------------------ the .deb file --- # The installed file list, read once into $W. The copy is what the loop below # redirects from: a stdin redirect the top-level shell performs on the virtiofs # root makes dup2(2) return 513 and ENDS the suite silently (brintos/linux#73), # and $W is on tmpfs. cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # Every payload path is under site-packages and is source, bytecode or metadata. # Anything else means the build produced something this port did not expect and # did not measure. _bad="" while IFS= read -r _f; do case "$_f" in ''|/usr/lib/python3.12/site-packages) continue ;; */) continue ;; esac [ -d "$_f" ] && continue case "$_f" in *.py|*.pyc) ;; */METADATA|*/WHEEL|*/RECORD|*/INSTALLER|*/LICENSE|*/LICENSE.*) ;; */entry_points.txt|*/top_level.txt) ;; *) _bad="$_bad $_f" ;; esac done < "$W/pkgf" check_eq $P/pkg-only-expected "" "$_bad" # ------------------------------------------------------------- dpkg status --- sed -n '/^Package: python3-constantly$/,/^$/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-arch "Architecture: wasm32" cat "$W/st" check_out $P/pkg-version "Version: 23.10.4-r0" cat "$W/st" # THE DEPENDENCY SET, asserted by name. It is exactly two, and `python3-json' is # the interesting one: it is a SUB-PACKAGE OF THE PORTED python3 RECIPE # (BRINTOS-1476), not a third-party port, and it is here because `_version.py' # parses its version out of a JSON literal at import time. So a control stanza # that had lost it would break `import constantly' itself rather than some # optional path -- which is why it is asserted by name rather than left to the # import case to discover. 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-python3-core "python3-core" cat "$W/dep" check_out $P/pkg-depends-python3-json "python3-json" cat "$W/dep" # ------------------------------------------------------------------ import --- # ONE CASE OF ITS OWN, and the first of two python3 starts. Pure Python, so this # is expected to be uneventful -- it is here so that an import failure is legible # without reading a probe transcript. check $P/import python3 -c 'import constantly' # ------------------------------------------------------------------- probe --- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "probe missing at $PROBE" exit 0 fi bst_ok $P/probe-present # THE SECOND START, and the one that carries the suite. Bounded at 900s inside # the 1200s suite budget so a hung probe is reported as a probe overrun with the # last line it printed, rather than as a suite timeout that says nothing about # where it stopped. # # The probe EXITS 0 even when the import fails, so this case passing means "the # probe ran", not "the module works" -- `import-ok' below is what carries that. bst_probe $P/probe-run "$W/probe" 900 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)" _imp=$(pv import_ok) check_eq $P/import-ok "yes" "$_imp" if [ "$_imp" != "yes" ]; then # The module did not import. Skip -- not red -- every behavioural case, each # with the reason attached: they are all one fact, and a hundred reds would # bury it rather than report it. `import' and `import-ok' above are the two # reds that carry the verdict, and the design doc attributes them. _why="constantly did not import: $(pv import_error)" for _c in module-file is-package version-attr dist-version version-agrees \ version-json-version version-json-error version-json-dirty \ version-json-keys all-count all-names all-bound private-all \ author license copyright \ names-instantiate names-iter names-iter-count \ names-iter-repeatable names-iter-reversed names-repr \ names-repr-str names-name-attr names-container \ names-is-namedconstant names-has-value names-identity \ names-lookup names-lookup-is names-lookup-missing \ names-lookup-nonconstant names-enumerants-keys \ names-enumerants-count extras-iter extras-attr extras-method \ extras-enumerants names-reuse loose-container loose-has-name \ loose-repr-raises \ values-iter values-iter-values values-repr values-value \ values-is-valueconstant values-lookup-by-name \ values-lookup-by-name-missing values-lookup values-lookup-is \ values-lookup-missing values-dup-first values-dup-is-first \ values-mixed-text values-mixed-tuple values-mixed-list \ values-mixed-none values-mixed-number values-bool-int \ flags-values flags-iter flags-read-value flags-exec-value \ flags-repr flags-names-set flags-is-flagconstant \ flags-is-values-subclass flags-explicit-values \ flags-or-value flags-or-names flags-or-name flags-or-repr \ flags-or-is-flagconstant flags-or-container flags-or-three-value \ flags-or-three-name flags-and-value flags-and-name \ flags-and-is-write flags-and-repr flags-and-empty-value \ flags-and-empty-name flags-and-empty-bool flags-xor-value \ flags-xor-name flags-xor-two-value flags-xor-two-name \ flags-invert-value flags-invert-name flags-invert-all-value \ flags-invert-all-name flags-invert-all-bool \ flags-contains-read flags-contains-exec flags-contains-self \ flags-iter-composite flags-iter-composite-count \ flags-iter-identity flags-iter-single flags-bool-true \ flags-bool-composite flags-lookup-by-value \ flags-lookup-composite flags-lookup-by-name \ cross-eq cross-ne cross-same-name cross-hash-distinct \ cross-lt cross-gt cross-le cross-ge cross-lt-int \ within-lt within-gt within-le-self within-ge-self \ within-lt-self within-sorted \ type-not-str type-not-int type-mro flag-type-mro \ base-names-enumerants base-values-enumerants \ base-flags-enumerants base-constant-types \ test-pkg-import test-pkg-file test-mod-import test-mod-classes \ py-version py-maxsize-bits py-platform probe-count; do bst_skip $P/$_c "$_why" done rm -rf "$W" 2>/dev/null exit 0 fi # The probe emitted every key it intended to. A probe that returned 0 having # skipped a section would otherwise be indistinguishable from one that ran whole. check_eq $P/probe-count "139" "$(pv emitted_count)" # ---------------------------------------------------------------- identity --- # WHICH COPY ANSWERED. The path is the target's, not the control's, so it is # asserted directly rather than compared. check_eq $P/module-file "$SITE/constantly/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" # THE VERSION IS COMPUTED AT IMPORT, not written: `__init__.py' does # `_version.get_versions()["version"]'. So `version-agrees' says the wheel was # cut from the source this module is, and `version-json-error' says versioneer # knew its own version rather than falling back -- a build that did not would # emit `0+unknown' here with a non-null error, and the two would still agree # with each other. check_eq $P/version-attr "23.10.4" "$(pv version_attr)" check_eq $P/dist-version "23.10.4" "$(pv dist_version)" check_eq $P/version-agrees "yes" "$(pv version_agrees)" check_eq $P/version-json-version "23.10.4" "$(pv version_json_version)" check_eq $P/version-json-error "null" "$(pv version_json_error)" check_eq $P/version-json-dirty "False" "$(pv version_json_dirty)" check_eq $P/version-json-keys \ "date,dirty,error,full-revisionid,version" "$(pv version_json_keys)" # `__all__' IS THE PUBLIC SURFACE, a hand-maintained list. Pinned joined so that # a name that appeared and a name that went are each one red rather than a # renumbering of six cases. Note the order: the three CONSTANT types first, then # the three CONTAINERS -- not alphabetical, and not grouped the way the source # defines them. check_eq $P/all-count "6" "$(pv all_count)" check_eq $P/all-names \ "NamedConstant,ValueConstant,FlagConstant,Names,Values,Flags" \ "$(pv all_names)" # Every promised name is bound. `from constantly import *' is what a consumer # writes, and it is the star import -- not the module import -- that a broken # `__all__' breaks. check_eq $P/all-bound "yes" "$(pv all_bound)" # THE PRIVATE MODULE EXPORTS NOTHING. `_constants.__all__' is `[]', deliberately: # `__init__.py' is the only public door. A release that populated it would change # what `from constantly._constants import *' does for anyone who writes it. check_eq $P/private-all "[]" "$(pv private_all)" check_eq $P/author "Twisted Matrix Laboratories" "$(pv author)" check_eq $P/license "MIT" "$(pv license)" # FORMATTED FROM `__author__' AT IMPORT, not a literal -- so this case asserts # that the format ran. check_eq $P/copyright "Copyright 2011-2015 Twisted Matrix Laboratories" \ "$(pv copyright)" # ------------------------------------------------------------------- Names --- # THE CONTAINER IS THE CLASS OBJECT and is never instantiated -- `__new__' raises # outright. A consumer who writes `Colour()' has misunderstood the API, and this # is where that is stated. check_eq $P/names-instantiate "TypeError" "$(pv names_instantiate)" # `iterconstants()' YIELDS DECLARATION ORDER, which is the package's central # promise. `names-iter-reversed' is the load-bearing half: its fixture is # declared ZULU, ALPHA, MIKE, so an implementation that sorted by NAME would pass # `names-iter' (whose fixture happens to be insertion-ordered too) and fail here. check_eq $P/names-iter "RED,GREEN,BLUE" "$(pv names_iter)" check_eq $P/names-iter-count "3" "$(pv names_iter_count)" check_eq $P/names-iter-repeatable "yes" "$(pv names_iter_repeatable)" check_eq $P/names-iter-reversed "ZULU,ALPHA,MIKE" "$(pv names_iter_reversed)" # THE REPR IS `'. It is NOT `Container.name': this ticket's text # describes the dotted form and `_Constant.__repr__' has never produced it. The # measured form is pinned here, and `names-repr-str' says `str()' gives the same # thing -- the class defines no `__str__', so it falls through to `__repr__'. check_eq $P/names-repr "" "$(pv names_repr)" check_eq $P/names-repr-str "" "$(pv names_repr_str)" check_eq $P/names-name-attr "RED" "$(pv names_name_attr)" check_eq $P/names-container "Colour" "$(pv names_container)" check_eq $P/names-is-namedconstant "yes" "$(pv names_is_namedconstant)" # A NamedConstant HAS NO `value'. `_constantFactory' hands `_realize' a sentinel # and `_Constant._realize' throws it away, so the attribute is ABSENT rather than # None -- which is the difference between `Names' and `Values' stated as a fact # about an instance. check_eq $P/names-has-value "no" "$(pv names_has_value)" # IDENTITY, NOT JUST EQUALITY. The constants are plain class attributes after # realization, not descriptors that build on access. check_eq $P/names-identity "yes" "$(pv names_identity)" check_eq $P/names-lookup "GREEN" "$(pv names_lookup)" check_eq $P/names-lookup-is "yes" "$(pv names_lookup_is)" check_eq $P/names-lookup-missing "ValueError" "$(pv names_lookup_missing)" # `lookupByName' READS `_enumerants', NOT the class dictionary, so an ordinary # class attribute that is not a constant is NOT reachable through it. This is the # case that says the container knows which of its attributes are constants. check_eq $P/names-lookup-nonconstant "ValueError" \ "$(pv names_lookup_nonconstant)" check_eq $P/names-enumerants-keys "BLUE,GREEN,RED" "$(pv names_enumerants_keys)" check_eq $P/names-enumerants-count "3" "$(pv names_enumerants_count)" # A CONTAINER MAY CARRY ORDINARY ATTRIBUTES AND METHODS, and they stay ordinary: # reachable as themselves, absent from `iterconstants()' and from `_enumerants'. check_eq $P/extras-iter "ONE,TWO" "$(pv extras_iter)" check_eq $P/extras-attr "just a string" "$(pv extras_attr)" check_eq $P/extras-method "helper" "$(pv extras_method)" check_eq $P/extras-enumerants "ONE,TWO" "$(pv extras_enumerants)" # REUSING A REALIZED CONSTANT IN A SECOND CONTAINER IS A ValueError, raised at # CLASS CREATION TIME by the metaclass. A constant remembers its container, and # this is the check that keeps two containers from sharing one identity. check_eq $P/names-reuse "ValueError" "$(pv names_reuse)" # AN UNREALIZED CONSTANT AT MODULE SCOPE IS LEGAL AND STAYS UNREALIZED, and its # repr therefore RAISES AttributeError -- `__repr__' reads # `self._container.__name__' and `_container' is still None. That is upstream's # behaviour, not this port's, and it is asserted because a loose constant is what # a consumer produces by accident. check_eq $P/loose-container "None" "$(pv loose_container)" check_eq $P/loose-has-name "no" "$(pv loose_has_name)" check_eq $P/loose-repr-raises "AttributeError" "$(pv loose_repr_raises)" # ------------------------------------------------------------------ Values --- check_eq $P/values-iter "OK,NOT_FOUND,ERROR" "$(pv values_iter)" check_eq $P/values-iter-values "200,404,500" "$(pv values_iter_values)" check_eq $P/values-repr "" "$(pv values_repr)" check_eq $P/values-value "200" "$(pv values_value)" check_eq $P/values-is-valueconstant "yes" "$(pv values_is_valueconstant)" # `Values' IS A `_ConstantsContainer' LIKE `Names', so everything asserted about # Names holds here too; these two are the cases that would notice if `Values' had # grown its own `lookupByName' that did something else. check_eq $P/values-lookup-by-name "ERROR" "$(pv values_lookup_by_name)" check_eq $P/values-lookup-by-name-missing "ValueError" \ "$(pv values_lookup_by_name_missing)" check_eq $P/values-lookup "NOT_FOUND" "$(pv values_lookup)" check_eq $P/values-lookup-is "yes" "$(pv values_lookup_is)" check_eq $P/values-lookup-missing "ValueError" "$(pv values_lookup_missing)" # `lookupByValue' RETURNS THE FIRST BY DECLARATION ORDER when two constants share # a value -- it walks the sorted `iterconstants()' and returns on the first match, # rather than raising or returning an arbitrary one. check_eq $P/values-dup-first "FIRST" "$(pv values_dup_first)" check_eq $P/values-dup-is-first "yes" "$(pv values_dup_is_first)" # THE VALUE MAY BE ANY OBJECT, including an unhashable one: `_constantFactory' # does not touch it and `lookupByValue' compares with `=='. So a list value is # findable, which a dict-backed implementation could not have done. check_eq $P/values-mixed-text "TEXT" "$(pv values_mixed_text)" check_eq $P/values-mixed-tuple "TUPLE" "$(pv values_mixed_tuple)" check_eq $P/values-mixed-list "LIST" "$(pv values_mixed_list)" check_eq $P/values-mixed-none "NOTHING" "$(pv values_mixed_none)" check_eq $P/values-mixed-number "NUMBER" "$(pv values_mixed_number)" # `==' AND NOT `is': `lookupByValue(7.0)' finds the constant built with `7', # because `7 == 7.0'. Pinned as the measured consequence -- a consumer whose # values are 1 and True has an ambiguous container and this is why. check_eq $P/values-bool-int "NUMBER" "$(pv values_bool_int)" # ------------------------------------------------------------------- Flags --- # THE VALUES ARE SUCCESSIVE POWERS OF TWO, assigned by `Flags._constantFactory' # from a per-class counter that starts at 1. Nothing in the class body says so. check_eq $P/flags-values "1,2,4" "$(pv flags_values)" check_eq $P/flags-iter "READ,WRITE,EXEC" "$(pv flags_iter)" check_eq $P/flags-read-value "1" "$(pv flags_read_value)" check_eq $P/flags-exec-value "4" "$(pv flags_exec_value)" check_eq $P/flags-repr "" "$(pv flags_repr)" # A SINGLE FLAG'S `names' IS A ONE-ELEMENT SET, not a string: `_realize' wraps # the str it was given. That is what makes `|' able to union two of them. check_eq $P/flags-names-set "READ" "$(pv flags_names_set)" check_eq $P/flags-is-flagconstant "yes" "$(pv flags_is_flagconstant)" # `Flags' SUBCLASSES `Values', which is why a flag has `.value' and why # `lookupByValue' is available on it. check_eq $P/flags-is-values-subclass "yes" "$(pv flags_is_values_subclass)" # AN EXPLICIT VALUE RESETS THE COUNTER to twice itself, so the flags declared # after it continue from there rather than from where they would have been. This # is the one branch in `_constantFactory' and it is not visible in a class body. check_eq $P/flags-explicit-values "4,8,16" "$(pv flags_explicit_values)" check_eq $P/flags-or-value "3" "$(pv flags_or_value)" check_eq $P/flags-or-names "READ,WRITE" "$(pv flags_or_names)" # THE COMPOSITE'S `name' IS A BRACED, ALPHABETICALLY SORTED JOIN -- `_realize' # builds it when more than one name applies. That is a real asymmetry with # `iterconstants()', which is in declaration order, and `flags-or-three-name' # below is where it is visible: EXEC comes first there and last in `flags-iter'. check_eq $P/flags-or-name "{READ,WRITE}" "$(pv flags_or_name)" check_eq $P/flags-or-repr "" "$(pv flags_or_repr)" check_eq $P/flags-or-is-flagconstant "yes" "$(pv flags_or_is_flagconstant)" check_eq $P/flags-or-container "Perm" "$(pv flags_or_container)" check_eq $P/flags-or-three-value "7" "$(pv flags_or_three_value)" check_eq $P/flags-or-three-name "{EXEC,READ,WRITE}" "$(pv flags_or_three_name)" check_eq $P/flags-and-value "2" "$(pv flags_and_value)" # `&' DOWN TO ONE FLAG PRODUCES A SINGLE-NAME COMPOSITE whose `name' is the bare # name -- the `len(names) == 1' branch in `_realize'. It is a NEW object, not the # original constant: equal by value, DISTINCT BY IDENTITY, which is what # `flags-and-is-write' pins. A consumer who compares a masked flag with `is' gets # False, and that is the behaviour rather than a bug. check_eq $P/flags-and-name "WRITE" "$(pv flags_and_name)" check_eq $P/flags-and-is-write "no" "$(pv flags_and_is_write)" check_eq $P/flags-and-repr "" "$(pv flags_and_repr)" # THE EMPTY COMPOSITE IS `{}' AND IS FALSEY. `__bool__' is `bool(self.value)', # so a mask that matched nothing tests false -- which is what makes # `if flags & MASK:' the idiom it looks like. check_eq $P/flags-and-empty-value "0" "$(pv flags_and_empty_value)" check_eq $P/flags-and-empty-name "{}" "$(pv flags_and_empty_name)" check_eq $P/flags-and-empty-bool "False" "$(pv flags_and_empty_bool)" check_eq $P/flags-xor-value "1" "$(pv flags_xor_value)" check_eq $P/flags-xor-name "READ" "$(pv flags_xor_name)" check_eq $P/flags-xor-two-value "5" "$(pv flags_xor_two_value)" check_eq $P/flags-xor-two-name "{EXEC,READ}" "$(pv flags_xor_two_name)" # `~' IS BOUNDED BY THE CONTAINER, not by the machine word: `__invert__' walks # `iterconstants()' and ORs in every flag not set here, so the result can never # carry a bit the container does not define. `~READ' is 6, not -2 and not # 0xFFFFFFFE -- and THAT is why this package's arithmetic cannot depend on the # pointer width. On a C bitvector this is exactly where 32 against 64 would show. check_eq $P/flags-invert-value "6" "$(pv flags_invert_value)" check_eq $P/flags-invert-name "{EXEC,WRITE}" "$(pv flags_invert_name)" check_eq $P/flags-invert-all-value "0" "$(pv flags_invert_all_value)" check_eq $P/flags-invert-all-name "{}" "$(pv flags_invert_all_name)" check_eq $P/flags-invert-all-bool "False" "$(pv flags_invert_all_bool)" check_eq $P/flags-contains-read "True" "$(pv flags_contains_read)" check_eq $P/flags-contains-exec "False" "$(pv flags_contains_exec)" check_eq $P/flags-contains-self "True" "$(pv flags_contains_self)" # ITERATING A COMPOSITE YIELDS THE CONTAINER'S OWN CONSTANTS, resolved by name # through `lookupByName' -- so the objects that come out are IDENTICAL to the # class attributes rather than copies. That is the opposite of what `&' does # above, and the pair is why both are cases. check_eq $P/flags-iter-composite "READ,WRITE" "$(pv flags_iter_composite)" check_eq $P/flags-iter-composite-count "2" "$(pv flags_iter_composite_count)" check_eq $P/flags-iter-identity "yes" "$(pv flags_iter_identity)" check_eq $P/flags-iter-single "READ" "$(pv flags_iter_single)" check_eq $P/flags-bool-true "True" "$(pv flags_bool_true)" check_eq $P/flags-bool-composite "True" "$(pv flags_bool_composite)" check_eq $P/flags-lookup-by-value "WRITE" "$(pv flags_lookup_by_value)" # `lookupByValue' OVER A COMPOSITE VALUE IS A ValueError. The composite was never # in a class body, so it is not in `_enumerants' and the walk finds nothing -- # `READ|WRITE' is 3 and no declared flag has that value. check_eq $P/flags-lookup-composite "ValueError" "$(pv flags_lookup_composite)" check_eq $P/flags-lookup-by-name "WRITE" "$(pv flags_lookup_by_name)" # -------------------------------------------------- across two containers --- # CONSTANTS FROM TWO CONTAINERS NEVER COMPARE EQUAL, even with the same name in # the same position. `_Constant' defines no `__eq__', so equality is identity -- # the strongest form this could take, and the one a consumer relies on when two # enumerations coexist. check_eq $P/cross-eq "False" "$(pv cross_eq)" check_eq $P/cross-ne "True" "$(pv cross_ne)" check_eq $P/cross-same-name "True" "$(pv cross_same_name)" check_eq $P/cross-hash-distinct "yes" "$(pv cross_hash_distinct)" # ORDERING ACROSS CONTAINERS RAISES. The four comparison methods return # `NotImplemented' when the containers differ and Python turns a reflected pair of # those into TypeError -- so a cross-container `<' is an ERROR rather than a # silent answer drawn from the process-global instantiation counter, which would # otherwise have given a perfectly plausible wrong one. That is the important # half of the cross-container contract. check_eq $P/cross-lt "TypeError" "$(pv cross_lt)" check_eq $P/cross-gt "TypeError" "$(pv cross_gt)" check_eq $P/cross-le "TypeError" "$(pv cross_le)" check_eq $P/cross-ge "TypeError" "$(pv cross_ge)" check_eq $P/cross-lt-int "TypeError" "$(pv cross_lt_int)" # WITHIN ONE CONTAINER, ORDER IS DECLARATION ORDER and the four operators agree # with it. `<=' and `>=' are identity-or-index rather than index-or-equal, so the # reflexive cases are asserted separately from the strict ones. check_eq $P/within-lt "True" "$(pv within_lt)" check_eq $P/within-gt "True" "$(pv within_gt)" check_eq $P/within-le-self "True" "$(pv within_le_self)" check_eq $P/within-ge-self "True" "$(pv within_ge_self)" check_eq $P/within-lt-self "False" "$(pv within_lt_self)" check_eq $P/within-sorted "ONE,TWO" "$(pv within_sorted)" # A CONSTANT IS ITS OWN TYPE, NOT A str OR AN int. `_Constant' subclasses # `object' and nothing else, so none of the implicit conversions a consumer might # expect from an enum library exist here -- a `FlagConstant' is not an int even # though it carries one. check_eq $P/type-not-str "yes" "$(pv type_not_str)" check_eq $P/type-not-int "yes" "$(pv type_not_int)" check_eq $P/type-mro "NamedConstant,_Constant,object" "$(pv type_mro)" check_eq $P/flag-type-mro "FlagConstant,_Constant,object" "$(pv flag_type_mro)" # THE BASE CONTAINERS CARRY NO CONSTANTS. `Names', `Values' and `Flags' are # classes a consumer SUBCLASSES; each has an EMPTY `_enumerants' rather than no # attribute at all, which is what makes `iterconstants()' on a bare one return # nothing instead of raising. check_eq $P/base-names-enumerants "0" "$(pv base_names_enumerants)" check_eq $P/base-values-enumerants "0" "$(pv base_values_enumerants)" check_eq $P/base-flags-enumerants "0" "$(pv base_flags_enumerants)" check_eq $P/base-constant-types \ "NamedConstant,ValueConstant,FlagConstant" "$(pv base_constant_types)" # --------------------------------------------------- the shipped test suite --- # UPSTREAM SHIPS ITS OWN TESTS INSIDE THE PACKAGE and they land on target: 37 KB # of source and 60 KB of bytecode, asserted present above. The subpackage # imports; THE TEST MODULE DOES NOT, on either arm, because it does # `from twisted.trial.unittest import TestCase' and twisted is not ported to this # image and was not installed on the control either. # # THAT IS NOT A RED. It is a measured property of upstream's packaging -- the # expectation below is the ModuleNotFoundError, pinned, so that a future image # carrying twisted would flip this case rather than leave the question # unexercised. Nothing here runs those tests: they are a `unittest' suite and # running them would be a second measurement of CPython, not of this port. check_eq $P/test-pkg-import "yes" "$(pv test_pkg_import)" check_eq $P/test-pkg-file "$SITE/constantly/test/__init__.py" \ "$(pv test_pkg_file)" check_eq $P/test-mod-import "no:ModuleNotFoundError" "$(pv test_mod_import)" check_eq $P/test-mod-classes "" "$(pv test_mod_classes)" # ------------------------------------------------------------- interpreter --- check_eq $P/py-version "3.12" "$(pv py_version)" check_eq $P/py-platform "linux" "$(pv py_platform)" # THE ONE VALUE THAT MUST NOT MATCH THE CONTROL. It reads 64 there and must read # 32 here. Every other value in this suite is arm-independent by construction -- # which is the point of the package -- so without this case a run against a host # copy of the module would pass in full and say nothing about which interpreter # answered. # # EXPECTED, NOT YET MEASURED: derived from the 32-bit target rather than read off # a target run, since this port's first on-target run is the batch verify. A 64 # here is not a bug in this package; it is the suite reporting that it did not # run where it thought it did. check_eq $P/py-maxsize-bits "32" "$(pv py_maxsize_bits)" rm -rf "$W" 2>/dev/null