. "$BST_LIB" # BST-TIMEOUT: 900 # # ESTIMATED, NOT MEASURED. This suite was authored in the recipe phase of a # batched Epic pass (BRINTOS-2299, batch-2271), which forbids building the image # and so could not run it. The bound is sized from the neighbouring python # ports rather than from a run of this file: ONE interpreter start at ~18 s # (99-pyxdg.test's header, batch-1649), then pure attribute reads over an # already-imported module, and shell builtins over a dpkg file list for the # rest. 900 is what the neighbouring python suites give strictly larger # measurements, so it is a backstop against a slow interpreter start rather # than a prediction of the runtime. 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-eth-typing 4.1.0 (meta-openembedded meta-python/recipes-devtools/ # python/python3-eth-typing_4.1.0.bb -- `inherit pypi setuptools3`, PyPI sdist) # -- THE SHARED TYPE VOCABULARY OF THE PYTHON ETHEREUM STACK, on the wasm32-hwjs # glibc image. # # ============================================================================ # WHAT THE PACKAGE IS, IN ONE PARAGRAPH. # ============================================================================ # # Nine pure-Python source files that DECLARE AND DO NOT COMPUTE. `Address`, # `Hash32`, `HexStr`, `BlockNumber` and nine more are `typing.NewType` aliases; # `ChainId` is a 1116-member `IntEnum` of registered chain ids; `ForkName` is a # plain class of sixteen string constants; the rest are `Union`s and one # constrained `TypeVar`. There is no logic, no I/O and no state -- eth-utils, # eth-account and web3.py depend on it so that they all spell "an address" the # same way. # # ============================================================================ # WHAT A DECLARATIONS-ONLY SUITE CAN ACTUALLY ASSERT. # ============================================================================ # # There is no behaviour to exercise, so every case here is about KIND, IDENTITY # or PACKAGING -- and those are precisely what a repackaging breaks while # leaving `import eth_typing` green. Three failures that a naive "does it # import" check would all miss: # # * A NewType FLATTENED to its supertype (`Address = bytes`). Everything # still imports and every annotation still evaluates; the type distinction # the package exists to provide is simply gone. `nt-kind-*` is the case. # * A CHAINED alias broken. `HexAddress` is a NewType over `HexStr`, and # `ChecksumAddress` one over `HexAddress` -- a three-deep chain down to # `str`. Flattening a link leaves the transparency cases green, because a # flattened chain is still transparent. `nt-chain-*` is the case. # * `__all__` and the module DISAGREEING, in either direction. A name # advertised but not resolvable, or a submodule that failed to ship whose # names survive only as the package-level re-export. `all-*`/`reexport-*`. # # ============================================================================ # THE ONE RUNTIME DEPENDENCY THAT IS NOT A PACKAGE, AND WHY IT HAS CASES. # ============================================================================ # # `eth_typing/__init__.py` ENDS with `__version__ = __version("eth-typing")`, # resolved through `importlib.metadata` AT IMPORT TIME. Two consequences worth # having cases for: # # * The installed `.dist-info` is load-bearing for a plain `import # eth_typing`. Strip the metadata -- which a size-minded image is exactly # the sort of thing to do -- and the module does not import AT ALL, rather # than merely losing an attribute. `dist-info-metadata` reads the file and # `version` reads it the way the package does. # * The distribution is asked for by its HYPHENATED name while the import # name is underscored. `dist-name` pins that, because a repackaging that # normalises the dist-info directory the other way breaks the import and # nothing else. # # MEASURED, and stated because the opposite would have been a genuine defect: # the .deb's `Depends:` is `python3-core` ALONE, and python3-core is what ships # `importlib/metadata/` on this image (measured off python3-core_3.12.14-r0's # own file list -- the poky manifest's `core` stanza does not name it, and it # arrives through the FILES glob). So the dependency is COMPLETE. It is not # `python3-typing-extensions`: eth-typing 4.1.0's `setup.py` carries # `install_requires=[]`, and typing-extensions is not imported anywhere in the # tree. See the design doc -- the ticket's own text says otherwise and the # ticket is describing a later release. # # ============================================================================ # WHY THIS PORT IS POSSIBLE AT ALL, AND WHAT THE .so CASES ARE SAYING. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an empty lib-dynload: an # extension module cannot be dlopened here at all. eth-typing needs none -- # `Root-Is-Purelib: true` in its own WHEEL, nine `.py` files and a zero-byte # `py.typed`, with no C in the sdist. `no-so-files` and `so-count` are the two # statements of that, one from the payload on disk and one from inside the # interpreter, and they are what make this a port rather than a hope. # # MEASURED at recipe scope (1622 of 1622 tasks, zero ERROR: lines): all nine # `.py` files and `py.typed` are BYTE-IDENTICAL to an x86-64 control install of # the same sdist. There is no arch-conditional code in this package, so the # suite asserts CONTENT, not merely presence. # # ============================================================================ # WHAT 4.1.0 DOES NOT HAVE, ASSERTED AS AN ABSENCE. # ============================================================================ # # The ticket's acceptance text names TypedDicts, Protocol classes and ENS types. # NONE OF THE THREE IS IN 4.1.0 -- they arrive in eth-typing 5.x, whose `abi` # module carries the `ABIFunction`/`ABIEvent` TypedDicts and which adds an `ens` # module. The `v5-*` and `ens-module-absent` cases pin their absence rather # than leaving it unsaid, so that a later version bump turns them RED and forces # the suite to be extended instead of silently under-asserting the new surface. # A green `v5-names-absent` is this suite saying "still 4.1.0-shaped". # # ============================================================================ # NOTHING HERE REACHES A DEVICE, A PEER, A CLOCK OR A TERMINAL. # ============================================================================ # # The package holds no state and opens nothing, so the probe writes no fixture # and needs no working directory to tear down. Every asserted value is a # constant of the source. The suite is non-interactive and closes the port with # no browser pass and no follow-on under BRINTOS-780. P=python3-eth-typing # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and NOT a defect in the host tool # (BRINTOS-1149 established that and filed it as linux-in-the-browser#177). The # harness computes its pass count as total-minus-failures, so THE DROPPED LINE # IS COUNTED AS A PASS. A nothing-case therefore stands in that position and the # eaten line costs nothing. It sits ahead of the `require` guard deliberately, # because a `require` that skips would emit its skip as the first line and be # eaten too. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages ET=$SITE/eth_typing DI=$SITE/eth_typing-4.1.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-eth-typing.list D=$BST_DATA PROBE=$D/python3-eth-typing-probe.py # ------------------------------------------------------------ the payload --- # Where the module has to land. The probe's `module-file` further down is the # functional statement; these are the ones that say WHICH copy answered, so a # module that imported out of some other path is distinguishable from one that # imported out of the package this image installed. check $P/site-packages-dir test -d "$SITE" check $P/module-dir test -d "$ET" # THE NINE SOURCE FILES, NAMED SEPARATELY BECAUSE THEY FAIL DIFFERENTLY. A # missing `__init__.py` breaks the import outright. A missing SUBMODULE breaks # it just as completely, because `__init__.py` imports from every one of the # eight at module level -- there is no lazy path and no optional submodule # here, which is a fact worth a case each, since a reader would reasonably # expect `networks.py` (the 1122-line chain table) to be optional. It is not. check $P/src-init test -f "$ET/__init__.py" check $P/src-abi test -f "$ET/abi.py" check $P/src-bls test -f "$ET/bls.py" check $P/src-discovery test -f "$ET/discovery.py" check $P/src-encoding test -f "$ET/encoding.py" check $P/src-enums test -f "$ET/enums.py" check $P/src-ethpm test -f "$ET/ethpm.py" check $P/src-evm test -f "$ET/evm.py" check $P/src-networks test -f "$ET/networks.py" # PEP 561's MARKER FILE. Without it every annotation this package exists to # provide is INVISIBLE to any checker consuming it -- the package still imports # and still works at runtime, and is silently useless for its actual purpose. # That is exactly the failure mode a stub-shaped package has, and it is a # packaging failure rather than a code one: `setup.py` needs the # `package_data={"eth_typing": ["py.typed"]}` entry to carry it, and a # repackaging that rebuilds from the tree without it produces a working import # and a broken product. check $P/py-typed test -f "$ET/py.typed" # It is a MARKER, so PEP 561 gives it no contents. Asserted empty, because # something written into it would mean the build did something unexpected. check_eq $P/py-typed-empty "0" "$(wc -c < "$ET/py.typed" 2>/dev/null | tr -d ' ')" # THE INSTALLED DISTRIBUTION METADATA, which this package needs to IMPORT and # not merely to introspect. See the header. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check $P/dist-info-record test -f "$DI/RECORD" check $P/dist-info-wheel test -f "$DI/WHEEL" # `Root-Is-Purelib: true` is the wheel's own statement that it is # architecture-independent -- the packaging-level form of the .so negative. check $P/wheel-purelib grep -q '^Root-Is-Purelib: true$' "$DI/WHEEL" check $P/top-level grep -qx 'eth_typing' "$DI/top_level.txt" # NO EXTENSION MODULES, from the payload side. On an image whose python3 has an # empty lib-dynload, a .so here would be unloadable -- so this case failing # would mean the package cannot work at all, not that it is merely unusual. check_eq $P/no-so-files "0" "$(find "$ET" -name '*.so' 2>/dev/null | wc -l | tr -d ' ')" # WHAT DPKG THINKS IT INSTALLED. The file-list cases above read the filesystem; # this reads the package database, so a file present on disk but not owned by # this package (a stray from another recipe, an unpackaged leftover) is # distinguishable from one this port actually shipped. check $P/dpkg-list test -f "$PKGLIST" check $P/dpkg-owns-init grep -qx "$ET/__init__.py" "$PKGLIST" check $P/dpkg-owns-typed grep -qx "$ET/py.typed" "$PKGLIST" check $P/dpkg-owns-meta grep -qx "$DI/METADATA" "$PKGLIST" # ------------------------------------------------------------- the probe ----- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" exit 0 fi bst_ok $P/probe-present # ONE interpreter start for everything that follows. bst_probe bounds it and # keeps BOTH streams: a probe that dies early otherwise leaves every case below # reporting got:'' and naming a symptom each, with the actual traceback nowhere. # The bound is generous because a python3 start on this arm is tens of seconds # before any work begins. W=${TMPDIR:-/tmp}/bst-eth-typing.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } bst_probe $P/probe-run "$W/probe" 600 python3 "$PROBE" OUT="$W/probe" # Read a single key. The probe emits each key at most once, so head -n 1 is # defensive rather than load-bearing; the anchors keep `version` and # `version_error` distinguishable. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # THE EVIDENCE PLANE. If the probe emitted nothing, every case below is reading # an absent file: each would red with got:'' and the set would read exactly like # a broken package, when what actually happened is that no measurement was # taken. The word `unscorable` is PARSED -- brintos_local_verify re-measures a # suite that prints it on a fresh guest rather than trusting a shared boot's # numbers (BRINTOS-1644) -- so it goes in this structural check, not a comment. # # The cases below still print their reds: withdraw, do not hide. This case is # what says what those reds measure. if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi # A traceback the probe caught itself, surfaced as its own case rather than as # sixty separate got:'' reds. `fatal` is also what an IMPORT failure reports, # which is the one failure that would make every other case here meaningless. check_eq $P/probe-no-fatal "" "$(pv fatal)" # ...and the probe ran to its LAST line, so an absent key below is a real # absence rather than a probe that stopped halfway with no traceback. check_eq $P/probe-complete "yes" "$(pv probe_complete)" # ------------------------------------------------------------ identity ------- # THE VERSION THE RECIPE PINS, read the way the package itself reads it -- # through importlib.metadata off the installed dist-info. A mismatch means the # recipe and what actually landed have gone out of step, and every expectation # in this file is then describing a different eth-typing. check_eq $P/version "4.1.0" "$(pv version)" # A NON-EMPTY value here means the metadata lookup RAISED. That is the # load-bearing failure from the header: it happens at import time, so the whole # module is gone, not just the attribute. check_eq $P/version-readable "" "$(pv version_error)" check_eq $P/dist-error "" "$(pv dist_error)" # The HYPHENATED distribution name against the UNDERSCORED import name. check_eq $P/dist-name "eth-typing" "$(pv dist_name)" check_eq $P/dist-version "4.1.0" "$(pv dist_version)" # The module answered out of the package this image installed, not from a stray # copy on sys.path. This is what makes the payload cases above and the # declaration cases below statements about the SAME eth_typing. check_eq $P/module-file "$ET/__init__.py" "$(pv module_file)" # ...and it answered out of a .py, not an extension. The third statement of the # .so negative, this one from inside the interpreter. check_eq $P/module-is-py "yes" "$(pv module_is_py)" # ========================================================= THE EXPORT SET ===== # `__all__` IS THE PACKAGE'S CONTRACT, and it is a tuple rather than a list -- # pinned because a consumer may rely on it being immutable. check_eq $P/all-len "20" "$(pv all_len)" check_eq $P/all-is-tuple "yes" "$(pv all_is_tuple)" check_eq $P/all-unique "yes" "$(pv all_unique)" # THE FULL SET, IN SOURCE ORDER, as one case. A single name added, removed or # renamed upstream lands here with both sides printed, which is more useful # than twenty separate membership cases would be. check_eq $P/all-names \ "Decodable,TypeStr,BLSPrivateKey,BLSPubkey,BLSSignature,NodeID,HexStr,Primitives,ForkName,ChainId,URI,ContractName,Manifest,Address,AnyAddress,BlockIdentifier,BlockNumber,ChecksumAddress,Hash32,HexAddress" \ "$(pv all_names)" # EVERY ADVERTISED NAME RESOLVES. This is the direction that catches an export # renamed in a submodule and not re-exported here -- `__all__` would still list # the old name and `from eth_typing import *` would raise. check_eq $P/all-resolve "yes" "$(pv all_resolve)" check_eq $P/all-missing "" "$(pv all_missing)" # ALL EIGHT SUBMODULES IMPORT INDEPENDENTLY. The package-level import already # pulls each one in, so this is not redundant only because it names WHICH # failed: a single broken submodule otherwise reports as one opaque `fatal`. check_eq $P/submodules-import "yes" "$(pv submodules_import)" check_eq $P/submodules-bad "" "$(pv submodules_bad)" # THE OTHER DIRECTION OF THE EXPORT CHECK: every public name is the SAME OBJECT # on the package and on the submodule that declares it. A name reachable from # the package but not from its home module means the re-export is the only copy # -- which is how a submodule that failed to ship stays invisible, since # `__init__.py` would have raised long before. `home-covers-all` is the case # that keeps the mapping honest as the export set changes. check_eq $P/home-covers-all "yes" "$(pv home_covers_all)" check_eq $P/reexport-identical "yes" "$(pv reexport_identical)" check_eq $P/reexport-differing "" "$(pv reexport_differing)" # ========================================================== THE NewTypes ====== # THIRTEEN of the twenty names are `typing.NewType`. KIND, SUPERTYPE and # TRANSPARENCY are three INDEPENDENT failures -- see the header. A flattened # alias passes none of the three; a NewType built over the wrong supertype # passes the kind case alone. check_eq $P/nt-count "13" "$(pv newtype_count)" # KIND, one name at a time inside a single case's value, so the red names the # offender rather than saying "something is not a NewType". check_eq $P/nt-kinds \ "Address:yes,BLSPrivateKey:yes,BLSPubkey:yes,BLSSignature:yes,BlockNumber:yes,ChecksumAddress:yes,ContractName:yes,Hash32:yes,HexAddress:yes,HexStr:yes,NodeID:yes,URI:yes,Manifest:yes" \ "$(pv newtype_kinds)" # SUPERTYPES. Note `ChecksumAddress:HexAddress` and `HexAddress:HexStr` -- the # two whose supertype is ANOTHER NewType rather than a builtin. check_eq $P/nt-supers \ "Address:bytes,BLSPrivateKey:int,BLSPubkey:bytes,BLSSignature:bytes,BlockNumber:int,ChecksumAddress:HexAddress,ContractName:str,Hash32:bytes,HexAddress:HexStr,HexStr:str,NodeID:bytes,URI:str,Manifest:Dict" \ "$(pv newtype_supers)" # `Manifest` is the one whose supertype is a SUBSCRIPTED GENERIC rather than a # class, so it reports as bare `Dict` above and is pinned in full here. check_eq $P/nt-manifest-super "typing.Dict[str, typing.Any]" "$(pv manifest_super)" # TRANSPARENCY: a NewType is the IDENTITY FUNCTION at runtime -- that is what # makes the annotation free, and it is the property a consumer relies on when # it passes a plain `bytes` where an `Address` is annotated. Asserted with # `is`, so a NewType somebody replaced with a validating or converting callable # fails HERE and nowhere else: every kind and supertype case above would stay # green. check_eq $P/nt-transparent-address "yes" "$(pv transparent_address)" check_eq $P/nt-transparent-hash32 "yes" "$(pv transparent_hash32)" check_eq $P/nt-transparent-hexstr "yes" "$(pv transparent_hexstr)" # Over a LARGE int, deliberately -- CPython caches -5..256, so `is` on a small # literal would hold whatever the NewType did. check_eq $P/nt-transparent-blocknumber "yes" "$(pv transparent_blocknumber)" # THE THREE-DEEP CHAIN, `ChecksumAddress` -> `HexAddress` -> `HexStr` -> `str`. # The end-to-end case and the three link cases fail differently: a FLATTENED # chain is still transparent end to end, so `nt-chain-applied` alone would not # catch it. The links are what say the chain is really a chain. check_eq $P/nt-chain-applied "yes" "$(pv transparent_chain)" check_eq $P/nt-chain-hexstr-str "yes" "$(pv chain_hexstr_super)" check_eq $P/nt-chain-hexaddress "yes" "$(pv chain_hexaddress_super)" check_eq $P/nt-chain-checksum "yes" "$(pv chain_checksum_super)" # A NewType carries the name and the DEFINING module it was declared with -- # which is what makes a type checker's error message name `eth_typing.evm` # rather than the re-export. check_eq $P/nt-name-address "Address" "$(pv newtype_name_address)" check_eq $P/nt-mod-address "eth_typing.evm" "$(pv newtype_mod_address)" check_eq $P/nt-mod-hexstr "eth_typing.encoding" "$(pv newtype_mod_hexstr)" # ====================================================== THE NON-NewTypes ====== # SEVEN NAMES THAT ARE NOT NewTypes, and each is a DIFFERENT kind of thing. # Asserted one at a time on purpose: a single "the aliases are fine" case would # report `TypeStr` having stopped being `str` itself and `Decodable` having # lost a union member identically. # # `TypeStr` IS the builtin `str` object -- not a NewType and not a subclass. So # `TypeStr("x")` really does construct a str, and a checker treats the two as # interchangeable. That is deliberate upstream: an ABI type is spelled as a # plain string. check_eq $P/alias-typestr-is-str "yes" "$(pv typestr_is_str)" check_eq $P/alias-decodable "typing.Union[bytes, bytearray]" "$(pv decodable)" check_eq $P/alias-decodable-args "bytes,bytearray" "$(pv decodable_args)" check_eq $P/alias-primitives "bytes,int,bool" "$(pv primitives_args)" # `BlockIdentifier` is the union a JSON-RPC caller passes where a block is # wanted. Its members are asserted BY NAME, because the two NewTypes in it # report as their own names and a flattened one would report as `bytes`/`int` # -- so this case doubles as a flattening detector for the union's members. check_eq $P/union-blockid-len "5" "$(pv blockid_len)" check_eq $P/union-blockid-args "Literal,BlockNumber,Hash32,HexStr,int" "$(pv blockid_args)" # The `Literal`'s own members -- the five block tags the JSON-RPC spec allows. # "safe" and "finalized" are the post-Merge additions, so their presence is a # statement about which era of the spec this version tracks. check_eq $P/union-blockparams "latest,earliest,pending,safe,finalized" "$(pv blockparams)" # `AnyAddress` is a CONSTRAINED TypeVar -- not a NewType and not a Union. The # distinction is load-bearing for consumers: a function generic over # `AnyAddress` takes any ONE of the three address spellings and returns THAT # one, where a Union would let the three be mixed within a single call. check_eq $P/typevar-anyaddress-kind "yes" "$(pv anyaddress_is_typevar)" check_eq $P/typevar-anyaddress-constraints \ "Address,HexAddress,ChecksumAddress" "$(pv anyaddress_constraints)" # ============================================================== ForkName ====== # A PLAIN CLASS OF STRING CONSTANTS, deliberately NOT an Enum. So # `ForkName.Berlin` IS the str "Berlin" and compares equal to it, with no # `.value` in the way. The negative is asserted rather than left implied, # because a reader who assumed an Enum would write `.value` and get an # AttributeError -- and because "upstream turned this into an Enum" is a real # and silent API break that only `fork-is-enum` would catch. check_eq $P/fork-count "16" "$(pv forkname_count)" check_eq $P/fork-is-enum "no" "$(pv forkname_is_enum)" check_eq $P/fork-all-str "yes" "$(pv forkname_all_str)" check_eq $P/fork-berlin-str "yes" "$(pv forkname_berlin_is_str)" # EVERY NAME EQUALS ITS OWN VALUE. That invariant is the whole design of the # class, and it is what lets a caller pass either the constant or the literal. check_eq $P/fork-values-match-names "yes" "$(pv forkname_values_match_names)" # The sixteen forks in declaration order, which is chronological -- Frontier # through Cancun. A fork appended upstream lands here. check_eq $P/fork-names \ "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,Metropolis,ConstantinopleFix,Istanbul,Berlin,London,ArrowGlacier,GrayGlacier,Paris,Shanghai,Cancun" \ "$(pv forkname_names)" # =============================================================== ChainId ====== # AN IntEnum, so a member IS an int: it compares equal to one, indexes with one # and serialises as one. That is what lets a chain id go straight into a # JSON-RPC payload without a `.value`, and it is the property `chain-is-int` # and `chain-eth-eq-int` pin. check_eq $P/chain-is-intenum "yes" "$(pv chainid_is_intenum)" check_eq $P/chain-is-int "yes" "$(pv chainid_is_int)" check_eq $P/chain-eth-eq-int "yes" "$(pv chainid_eth_eq_int)" # THE TABLE'S SIZE, and -- the interesting half -- that it contains ZERO # ALIASES. An IntEnum silently collapses two names sharing a value into one # member plus an alias, so `members` and `canonical` agreeing at 1116 is a real # statement: every registered chain id in this table is distinct. A duplicate # introduced upstream would show as a gap between these two numbers, and # `list(ChainId)` would quietly stop yielding one of the names. check_eq $P/chain-members "1116" "$(pv chainid_members)" check_eq $P/chain-canonical "1116" "$(pv chainid_canonical)" check_eq $P/chain-aliases "0" "$(pv chainid_aliases)" # THREE ANCHOR VALUES a consumer would actually use, and the reverse lookup. check_eq $P/chain-eth "1" "$(pv chainid_eth)" check_eq $P/chain-oeth "10" "$(pv chainid_oeth)" check_eq $P/chain-matic "137" "$(pv chainid_matic)" check_eq $P/chain-lookup-1 "ETH" "$(pv chainid_lookup_1)" # THE ENDS OF THE TABLE. The max is well past 2^32, so it also asserts that # nothing truncated the table's widest entry on a 32-bit target -- which, on a # wasm32 image, is the case worth having. check_eq $P/chain-min "ETH=1" "$(pv chainid_min)" check_eq $P/chain-max "GW_TESTNET_V1_DEPRECATED=868455272153094" "$(pv chainid_max)" # python3.11 CHANGED `str()` ON AN IntEnum to the plain int's text. Pinned # because a consumer formatting a chain id into a URL or a JSON body gets "1" # and not "ChainId.ETH" -- the difference is a wire-format change, and it is a # property of the interpreter this image ships rather than of the package. # `repr` still carries the name, and the pair is what distinguishes "3.11+ # behaviour" from "the enum lost its names". check_eq $P/chain-str "1" "$(pv chainid_str)" check_eq $P/chain-repr-has-name "yes" "$(pv chainid_repr_has_name)" # =========================================================== ERROR PATHS ====== # A NAME THE PACKAGE DOES NOT DEFINE MUST RAISE, not resolve. This is what # catches a wildcard re-export or a `__getattr__` fallback introduced by a # repackaging -- either of which would make every typo in a consumer's import # silently succeed and yield None. check_eq $P/err-import-absent "ImportError" "$(pv err_import_absent)" # ...and the message NAMES the missing symbol, which is what makes the failure # diagnosable rather than merely correct. check_eq $P/err-import-absent-msg "yes" "$(pv err_import_absent_msg)" check_eq $P/err-getattr-absent "AttributeError" "$(pv err_getattr_absent)" # ================================== WHAT 4.1.0 DOES NOT HAVE, ASSERTED ======== # See the header. These pin the ABSENCE of the 5.x surface the ticket's # acceptance text describes -- the ABI TypedDicts, the ENS module. A GREEN case # here means "still 4.1.0-shaped"; a RED one means the recipe was bumped and # this suite now under-asserts, which is exactly when a reader needs telling. check_eq $P/v5-names-absent "yes" "$(pv v5_names_absent)" check_eq $P/v5-names-present "" "$(pv v5_names_present)" check_eq $P/ens-module-absent "absent" "$(pv ens_module)" # NOT ONE TypedDict in the public surface. Stated positively, because "we found # no TypedDicts" and "we did not look" are otherwise the same result. check_eq $P/typeddict-count "0" "$(pv typeddict_count)" # ========================================== NO COMPILED OBJECTS, FROM INSIDE == # The interpreter's own view of the installed package directory, and the list of # `.py` files it holds. Together with `no-so-files` above this is the statement # that the port needs no extension module -- which is what makes it possible on # an image whose python3 cannot dlopen one. check_eq $P/so-count "0" "$(pv so_count)" check_eq $P/py-files \ "__init__.py,abi.py,bls.py,discovery.py,encoding.py,enums.py,ethpm.py,evm.py,networks.py" \ "$(pv py_files)" check_eq $P/py-typed-seen "yes" "$(pv py_typed_present)" check_eq $P/py-typed-size "0" "$(pv py_typed_size)" rm -rf "$W"