. "$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 construction of # # small frozen dataclasses and one pass over upstream's 51 # # shipped cases -- 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-crc32c gives its three. Nothing below is # # quadratic in anything. # # # # 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-annotated-types 0.6.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-annotated-types_0.6.0.bb, `inherit pypi # python_hatchling', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # It is a VOCABULARY, not a validator. It defines the reusable constraint # objects that go inside `typing.Annotated' -- Gt, Ge, Lt, Le, Interval, # MultipleOf, MinLen, MaxLen, Len, Timezone, Predicate -- and it checks nothing # with them. `Annotated[int, Gt(0)]' records an intent; some downstream library # (pydantic is the reason this package exists) reads `__metadata__' back out and # does the enforcing. # # That is why every case below asserts THE OBJECT A CONSTRUCTOR PRODUCES rather # than any behaviour it enforces. A suite that asserted `Gt(0)' rejects -1 would # be asserting something about a validator this image does not have. # # ============================================================================ # 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. # # There is also nothing in the source that can know the pointer width. It reads # no sizeof, packs no struct, and its only conditional is `sys.version_info'. # 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 # for this package, 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 annotated_types 0.6.0 unpacked # from the same PyPI sdist the recipe fetches (sha256 563339e8...84a5d), under # CPython 3.12 with typing_extensions 4.10.0 -- the same interpreter minor and # the same typing_extensions version this image ships. The packaging cases could # not be measured there and come from THIS BUILD's own .deb instead # (`dpkg-deb -c' on python3-annotated-types_0.6.0-r0_wasm32.deb). # # Three control results are worth naming because they are not what the source or # this ticket reads like: # # * `SupportsGt' AND ITS FIVE SIBLINGS ARE NOT `@runtime_checkable'. Only # `GroupedMetadata' carries the decorator in 0.6.0. So # `isinstance(3, SupportsGt)' RAISES TypeError -- it does not answer False, # and it does not answer True for a type that implements `>'. This ticket # asked for those protocols to be asserted runtime-checkable against an # implementing and a non-implementing type; there is no such behaviour to # assert, and what IS asserted below is the refusal, both ways round. # # * ASSIGNING AN UNDECLARED ATTRIBUTE RAISES TypeError, not AttributeError. # `@dataclass(frozen=True, slots=True)' builds a new class and the generated # `__setattr__' closes over the original, so its guard misses and the # `super(cls, self)' fallback is malformed. CPython's dataclasses, measured # on the control at 3.12 -- nothing to do with this arm. # # * THE `doc()' FALLBACK IS THE LIVE ONE. __init__.py prefers # `typing_extensions.DocInfo/doc' and defines its own on ImportError; # typing_extensions 4.10.0 exports neither, so `DocInfo.__module__' is # `annotated_types' on BOTH arms. That is the only place the RDEPENDS # package changes what this one does, which is why it is a case. # # ============================================================================ # THE GENERATOR IS `cases()', NOT `test_cases()'. # ============================================================================ # # Upstream ships `annotated_types/test_cases.py' inside the wheel on purpose, so # a downstream validator can enumerate the cases and check itself against them. # The MODULE is `test_cases'; the callable inside it is `cases'. There is no # `test_cases()' function -- this ticket's text names one and it does not exist. # What the cases below assert is REACHABILITY, which is upstream's whole # contract for that file: every case's `valid_cases' and `invalid_cases' # materialise, and every annotation carries metadata. P=python3-annotated-types # ============================================================================ # 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 PKG=$SITE/annotated_types DI=$SITE/annotated_types-0.6.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-annotated-types.list PROBE=$BST_DATA/python3-annotated-types-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-annotated-types.$$ 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/test-cases-present test -s "$PKG/test_cases.py" # THE SIZES ARE PINNED RATHER THAN FLOORED: a truncated or partially written # payload passes `test -s' and fails only here. Both figures are the sdist's own # bytes, and both are what the wheel's RECORD declares. check_eq $P/init-size 12151 \ "$(wc -c < "$PKG/__init__.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/test-cases-size 6327 \ "$(wc -c < "$PKG/test_cases.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # `py.typed' IS ZERO BYTES AND MUST STILL BE THERE. It is PEP 561's marker: its # presence is what tells a type checker the inline annotations are authoritative, # and its content is nothing at all. `test -s' would report it missing, so the # two halves are asserted separately -- exists, and is empty. check $P/py-typed-present test -e "$PKG/py.typed" check_eq $P/py-typed-empty 0 \ "$(wc -c < "$PKG/py.typed" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # 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 18 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-cases test -s "$PKG/__pycache__/test_cases.cpython-312.pyc" # NO EXECUTABLE. pyproject.toml declares no [project.scripts] table, so nothing # lands in /usr/bin. Asserted so a future release that started shipping one is # noticed rather than silently unexercised. check $P/no-bin-script test ! -e /usr/bin/annotated-types # --------------------------------------------------------------- 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: 0.6.0" cat "$DI/METADATA" check_out $P/dist-info-name "Name: annotated-types" cat "$DI/METADATA" # THE LICENSE LANDS UNDER `licenses/', not at the root of dist-info. hatchling # honours pyproject.toml's `license-files = { paths = ['LICENSE'] }' by putting # it in that subdirectory, which is where the recipe's LIC_FILES_CHKSUM md5 # ultimately points. Pinned by path so a layout change is visible. check $P/license-file test -s "$DI/licenses/LICENSE" # 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, and the exact opposite of what # 99-python3-crc32c asserts about its own. # # It is also why this port does NOT inherit crc32c's wrong-tag finding: # bdist_wheel computes a tag from the build host only for a package that has # something to be specific about. Pinned so that a poky whose hatchling started # stamping a host tag on a pure wheel is a visible change. 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: hatchling" cat "$DI/WHEEL" # THE DECLARED DEPENDENCY IS CONDITIONAL AND THE CONDITION IS FALSE HERE. # The wheel asks for typing-extensions only below Python 3.9; this image is # 3.12, so nothing in the METADATA requires it. The recipe's RDEPENDS asks for # it unconditionally anyway -- see `pkg-depends-typing-extensions' below, which # is the other half of this pair. Both are asserted because they disagree, and # the disagreement is upstream's recipe rather than this port's doing. check_out $P/meta-requires-python "Requires-Python: >=3.8" cat "$DI/METADATA" check_out $P/meta-requires-marker \ "Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9'" \ 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|*/py.typed) ;; */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-annotated-types$/,/^$/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" # THE DEPENDENCY SET, asserted by name. `python3-typing-extensions' is here # because the recipe says `RDEPENDS:${PN} = "python3-typing-extensions"' # unconditionally, and it is the reason this port waits on BRINTOS-1745 rather # than on nothing at all. A control stanza that had lost it would surface as an # unsatisfied install rather than as anything this suite could otherwise see. 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-typing-extensions "python3-typing-extensions" 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 annotated_types' # ------------------------------------------------------------------- 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 eighty 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="annotated_types did not import: $(pv import_error)" for _c in module-file is-package version-attr dist-version version-agrees \ all-count all-names all-bound te-import te-version te-has-doc \ te-has-docinfo docinfo-module doc-result doc-documentation \ ann-metadata ann-metadata-len ann-origin ann-is-gt \ ann-is-basemetadata ann-gt-value ann-two \ gt-repr gt-attr ge-repr ge-attr lt-repr lt-attr le-repr le-attr \ multipleof-repr multipleof-attr minlen-repr minlen-attr \ maxlen-repr maxlen-attr len-repr len-min len-max len-defaults \ gt-datetime gt-str tz-utc tz-none tz-ellipsis tz-str \ tz-ellipsis-is predicate-repr predicate-is-islower \ predicate-calls eq-equal eq-unequal hash-equal hash-in-set \ pairs-count frozen-gt slots-gt gt-has-dict gt-slots \ interval-iter interval-iter-len interval-all interval-empty \ interval-zero-bound len-iter len-iter-len len-iter-defaults \ len-iter-minonly len-iter-maxonly grouped-runtime-checkable \ grouped-interval grouped-is-interval grouped-is-len \ grouped-is-gt grouped-marker grouped-subclass-noiter \ grouped-subclass-ok protocols-present \ protocols-runtime-checkable supportsgt-isinstance \ supportslt-isinstance supportsgt-isinstance-nonimpl \ protocols-are-protocols lowercase-meta uppercase-meta \ isdigits-meta isfinite-meta isnotfinite-meta isnan-meta \ isinfinite-meta not-finite-inf not-finite-one cases-error \ cases-import cases-count cases-valid-total cases-invalid-total \ cases-all-annotated cases-first-meta cases-first-valid \ cases-first-invalid py-version py-maxsize-bits probe-count; do bst_skip $P/$_c "$_why" done 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 "107" "$(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/annotated_types/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" # hatchling reads the wheel's version out of `__version__' in this same file, so # the attribute and the distribution metadata must agree -- `version-agrees' is # the case that says the wheel was cut from the source this module is. check_eq $P/version-attr "0.6.0" "$(pv version_attr)" check_eq $P/dist-version "0.6.0" "$(pv dist_version)" check_eq $P/version-agrees "yes" "$(pv version_agrees)" # `__all__' IS THE PUBLIC SURFACE, a hand-maintained tuple. Pinned joined so that # a name that appeared and a name that went are each one red rather than a # renumbering of twenty cases. check_eq $P/all-count "25" "$(pv all_count)" check_eq $P/all-names \ "BaseMetadata,GroupedMetadata,Gt,Ge,Lt,Le,Interval,MultipleOf,MinLen,MaxLen,Len,Timezone,Predicate,LowerCase,UpperCase,IsDigits,IsFinite,IsNotFinite,IsNan,IsNotNan,IsInfinite,IsNotInfinite,doc,DocInfo,__version__" \ "$(pv all_names)" # Every promised name is bound. `from annotated_types 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 typing_extensions --- # THE ONE RUNTIME BRANCH IN THIS PACKAGE THAT IS NOT `sys.version_info', and the # only place the RDEPENDS package changes what this one does. See the header: # 4.10.0 exports neither `doc' nor `DocInfo', so annotated_types' own fallback is # what is live -- on both arms. check_eq $P/te-import "yes" "$(pv te_import)" check_eq $P/te-has-doc "no" "$(pv te_has_doc)" check_eq $P/te-has-docinfo "no" "$(pv te_has_docinfo)" # typing_extensions defines no `__version__' at 4.10.0 -- the same absence # 99-python3-typing-extensions records, asserted here because it is what makes # the two cases above readable as a statement about a known version. check_eq $P/te-version "no-attr" "$(pv te_version)" check_eq $P/docinfo-module "annotated_types" "$(pv docinfo_module)" check_eq $P/doc-result "DocInfo(documentation='the name of the user')" \ "$(pv doc_result)" check_eq $P/doc-documentation "x" "$(pv doc_documentation)" # ------------------------------------------- the constraint inside Annotated --- # THE PACKAGE'S WHOLE PURPOSE, at the one place a consumer reads it. # `Annotated[int, Gt(0)]' is what a library writes; `__metadata__' is what a # validator reads back. The constraint has to survive that round trip AS THE SAME # OBJECT KIND -- an equal-but-different object would satisfy the repr and break # every `isinstance(m, BaseMetadata)' traversal, which is why the isinstance # cases are separate from the repr. check_eq $P/ann-metadata "(Gt(gt=0),)" "$(pv ann_metadata)" check_eq $P/ann-metadata-len "1" "$(pv ann_metadata_len)" check_eq $P/ann-origin "int" "$(pv ann_origin)" check_eq $P/ann-is-gt "yes" "$(pv ann_is_gt)" check_eq $P/ann-is-basemetadata "yes" "$(pv ann_is_basemetadata)" check_eq $P/ann-gt-value "0" "$(pv ann_gt_value)" # TWO constraints keep WRITTEN order. A validator applies them in the order it # reads them, so the order is part of the contract. check_eq $P/ann-two "(Gt(gt=0), Lt(lt=10))" "$(pv ann_two)" # ------------------------------------------------- each constraint type made --- check_eq $P/gt-repr "Gt(gt=4)" "$(pv gt_repr)" check_eq $P/gt-attr "4" "$(pv gt_attr)" check_eq $P/ge-repr "Ge(ge=4)" "$(pv ge_repr)" check_eq $P/ge-attr "4" "$(pv ge_attr)" check_eq $P/lt-repr "Lt(lt=4)" "$(pv lt_repr)" check_eq $P/lt-attr "4" "$(pv lt_attr)" check_eq $P/le-repr "Le(le=4)" "$(pv le_repr)" check_eq $P/le-attr "4" "$(pv le_attr)" check_eq $P/multipleof-repr "MultipleOf(multiple_of=3)" "$(pv multipleof_repr)" check_eq $P/multipleof-attr "3" "$(pv multipleof_attr)" check_eq $P/minlen-repr "MinLen(min_length=2)" "$(pv minlen_repr)" check_eq $P/minlen-attr "2" "$(pv minlen_attr)" check_eq $P/maxlen-repr "MaxLen(max_length=5)" "$(pv maxlen_repr)" check_eq $P/maxlen-attr "5" "$(pv maxlen_attr)" check_eq $P/len-repr "Len(min_length=1, max_length=3)" "$(pv len_repr)" check_eq $P/len-min "1" "$(pv len_min)" check_eq $P/len-max "3" "$(pv len_max)" check_eq $P/len-defaults "Len(min_length=0, max_length=None)" "$(pv len_defaults)" # THE CONSTRAINT TYPES ARE NOT NUMERIC. `Gt' takes anything supporting `>', and # these two are what say so -- a release that started coercing its argument would # still pass every int case above. check_eq $P/gt-datetime "Gt(gt=datetime.datetime(2000, 1, 1, 0, 0))" \ "$(pv gt_datetime)" check_eq $P/gt-str "Gt(gt='m')" "$(pv gt_str)" # Timezone's four documented shapes, including the ellipsis literal -- the one a # reader is most likely to take for a typo, and the one that means "aware, any # zone". check_eq $P/tz-utc "Timezone(tz=datetime.timezone.utc)" "$(pv tz_utc)" check_eq $P/tz-none "Timezone(tz=None)" "$(pv tz_none)" check_eq $P/tz-ellipsis "Timezone(tz=Ellipsis)" "$(pv tz_ellipsis)" check_eq $P/tz-str "Timezone(tz='Africa/Abidjan')" "$(pv tz_str)" check_eq $P/tz-ellipsis-is "yes" "$(pv tz_ellipsis_is)" # Predicate HOLDS THE CALLABLE IT WAS GIVEN -- identity, not equality. A release # that wrapped it would repr the same and would break every library that # special-cases `str.isdigit' by identity, which the docstring invites. check_eq $P/predicate-repr \ "Predicate(func=)" "$(pv predicate_repr)" check_eq $P/predicate-is-islower "yes" "$(pv predicate_is_islower)" check_eq $P/predicate-calls "True" "$(pv predicate_calls)" # ------------------------------------------------ frozen, equal and hashable --- # EVERY CONSTRAINT TYPE IS A FROZEN DATACLASS, which is what lets a validator use # one as a dict key or put it in a set. Four properties over the same eleven # types: an equal pair compares equal, an unequal pair does not, equal objects # hash alike, and a set of two equals collapses to one. Joined rather than # 44 cases, so a type that lost its decorator is one red naming it. _TYPES="Gt,Ge,Lt,Le,MultipleOf,MinLen,MaxLen,Len,Timezone,Predicate,Interval" check_eq $P/pairs-count "11" "$(pv pairs_count)" check_eq $P/eq-equal "$_TYPES" "$(pv eq_equal)" check_eq $P/eq-unequal "$_TYPES" "$(pv eq_unequal)" check_eq $P/hash-equal "$_TYPES" "$(pv hash_equal)" check_eq $P/hash-in-set "$_TYPES" "$(pv hash_in_set)" # FROZEN means an assignment raises rather than mutating, and the exception is # asserted BY NAME: a release that dropped `frozen=True' would leave every case # above passing and every consumer's cached constraint silently mutable. check_eq $P/frozen-gt "FrozenInstanceError" "$(pv frozen_gt)" # AN UNDECLARED NAME RAISES TypeError, NOT AttributeError. See the header: it is # CPython's `frozen=True, slots=True' closure, measured on the control, and it is # pinned rather than repaired so that an interpreter which fixed it arrives as a # visible change of value. check_eq $P/slots-gt "TypeError" "$(pv slots_gt)" check_eq $P/gt-has-dict "no" "$(pv gt_has_dict)" check_eq $P/gt-slots "('gt',)" "$(pv gt_slots)" # ------------------------------------------------------- grouped metadata --- # `Interval' AND `Len' ARE GROUPS, and the whole of what a group means is that # iterating it yields the single-bound constraints it stands for. A validator # that does not know `Interval' still handles it correctly by unpacking it, so # this expansion IS the interoperability contract. check_eq $P/interval-iter "[Gt(gt=0), Lt(lt=10)]" "$(pv interval_iter)" check_eq $P/interval-iter-len "2" "$(pv interval_iter_len)" # All four bounds come out in __iter__'s source order -- gt, ge, lt, le -- and # NOT in the order the constructor was called in. Asserted with the keywords # deliberately shuffled, which is what makes that a measurement. check_eq $P/interval-all "[Gt(gt=0), Ge(ge=1), Lt(lt=10), Le(le=9)]" \ "$(pv interval_all)" # An empty Interval yields NOTHING rather than four Nones -- the case a # `if self.gt is not None' regression breaks first -- and `ge=0' yields a bound, # which is what says the guard is `is not None' rather than a truth test. check_eq $P/interval-empty "[]" "$(pv interval_empty)" check_eq $P/interval-zero-bound "[Ge(ge=0)]" "$(pv interval_zero_bound)" check_eq $P/len-iter "[MinLen(min_length=1), MaxLen(max_length=3)]" \ "$(pv len_iter)" check_eq $P/len-iter-len "2" "$(pv len_iter_len)" # `Len()' at its defaults yields nothing: min_length 0 is not > 0 and max_length # None is absent. `Len(2)' yields the minimum only, `Len(0, 4)' the maximum only. check_eq $P/len-iter-defaults "[]" "$(pv len_iter_defaults)" check_eq $P/len-iter-minonly "[MinLen(min_length=2)]" "$(pv len_iter_minonly)" check_eq $P/len-iter-maxonly "[MaxLen(max_length=4)]" "$(pv len_iter_maxonly)" # `GroupedMetadata' IS runtime_checkable -- the ONE protocol in this package that # is -- so `isinstance' against it is the supported way for a validator to ask # "should I unpack this?". Both answers are asserted, and the `none' below is the # case that says the isinstance RETURNED rather than raising, which is exactly # what the bare protocols further down do not do. check_eq $P/grouped-runtime-checkable "yes" "$(pv grouped_runtime_checkable)" check_eq $P/grouped-interval "none" "$(pv grouped_interval)" check_eq $P/grouped-is-interval "yes" "$(pv grouped_is_interval)" check_eq $P/grouped-is-len "yes" "$(pv grouped_is_len)" check_eq $P/grouped-is-gt "no" "$(pv grouped_is_gt)" check_eq $P/grouped-marker "True" "$(pv grouped_marker)" # SUBCLASSING WITHOUT __iter__ IS REFUSED AT CLASS-CREATION TIME by # `__init_subclass__'. That is the package's only defensive behaviour and the one # thing here that can raise on a correct program's import. check_eq $P/grouped-subclass-noiter "TypeError" "$(pv grouped_subclass_noiter)" check_eq $P/grouped-subclass-ok "[Gt(gt=0)]" "$(pv grouped_subclass_ok)" # ------------------------------------------------------- the bare protocols --- # NOT `@runtime_checkable' -- see the header. `isinstance(3, SupportsGt)' RAISES # TypeError; it does not answer False, and it does not answer True for a type # that implements `>'. Both directions are asserted, which is what says the # TypeError is about the protocol rather than about the value. # # This is the ticket's one wrong prediction, measured. It is asserted rather than # dropped because a consumer reaching for one of these gets an exception rather # than a wrong answer, and because upstream adding the decorator later is a # change of meaning that should arrive as a red. check_eq $P/protocols-present \ "SupportsGt,SupportsGe,SupportsLt,SupportsLe,SupportsMod,SupportsDiv" \ "$(pv protocols_present)" check_eq $P/protocols-runtime-checkable "" "$(pv protocols_runtime_checkable)" check_eq $P/supportsgt-isinstance "TypeError" "$(pv supportsgt_isinstance)" check_eq $P/supportslt-isinstance "TypeError" "$(pv supportslt_isinstance)" check_eq $P/supportsgt-isinstance-nonimpl "TypeError" \ "$(pv supportsgt_isinstance_nonimpl)" # They ARE typing Protocols, which is what separates "not runtime-checkable" from # "not a protocol at all". check_eq $P/protocols-are-protocols \ "SupportsGt,SupportsGe,SupportsLt,SupportsLe,SupportsMod,SupportsDiv" \ "$(pv protocols_are_protocols)" # --------------------------------------------------- the predicate aliases --- # The seven pre-built `Annotated' aliases, each asserted by the metadata it # carries. These are what a consumer imports instead of writing # `Predicate(str.islower)' by hand, so a broken alias is a broken import for them # and nothing at all for the constraint types above. check_eq $P/lowercase-meta \ "(Predicate(func=),)" "$(pv lowercase_meta)" check_eq $P/uppercase-meta \ "(Predicate(func=),)" "$(pv uppercase_meta)" check_eq $P/isdigits-meta \ "(Predicate(func=),)" "$(pv isdigits_meta)" check_eq $P/isfinite-meta \ "(Predicate(func=),)" "$(pv isfinite_meta)" check_eq $P/isnotfinite-meta \ "(Predicate(func=Not(func=)),)" \ "$(pv isnotfinite_meta)" check_eq $P/isnan-meta \ "(Predicate(func=),)" "$(pv isnan_meta)" check_eq $P/isinfinite-meta \ "(Predicate(func=),)" "$(pv isinfinite_meta)" # `Not' is the negation wrapper the IsNot* aliases are built from, and the only # callable object in the package. Asserted by CALLING it, since a repr comparison # would not notice an inverted sense. check_eq $P/not-finite-inf "True" "$(pv not_finite_inf)" check_eq $P/not-finite-one "False" "$(pv not_finite_one)" # ---------------------------------------------------- upstream's own cases --- # `annotated_types/test_cases.py' IS SHIPPED IN THE WHEEL, deliberately, so a # downstream validator can enumerate the cases and check itself against them. It # is therefore part of this port's payload, and a port that shipped only the # constraint types would be incomplete in a way no case above would notice. # # What is asserted is REACHABILITY -- every case's `valid_cases' and # `invalid_cases' materialise, and every annotation carries metadata. This # package does no checking, so asserting that the valid cases VALIDATE would be # asserting something about a validator this image does not have. # # The module pulls in `decimal', `datetime' and `typing' at import, so this is # also the widest stdlib surface anything in this port touches. check_eq $P/cases-error "" "$(pv cases_error)" check_eq $P/cases-import "yes" "$(pv cases_import)" check_eq $P/cases-count "51" "$(pv cases_count)" check_eq $P/cases-valid-total "115" "$(pv cases_valid_total)" check_eq $P/cases-invalid-total "130" "$(pv cases_invalid_total)" check_eq $P/cases-all-annotated "yes" "$(pv cases_all_annotated)" # The FIRST case pinned whole. A `cases()' reordered or rewritten upstream would # keep the totals above and change this. check_eq $P/cases-first-meta "(Gt(gt=4),)" "$(pv cases_first_meta)" check_eq $P/cases-first-valid "(5, 6, 1000)" "$(pv cases_first_valid)" check_eq $P/cases-first-invalid "(4, 0, -1)" "$(pv cases_first_invalid)" # ------------------------------------------------------------- interpreter --- check_eq $P/py-version "3.12" "$(pv py_version)" # 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. # # 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