. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. Every # # assertion below either reads the filesystem/dpkg with # # shell builtins or greps the single probe run's output; the # # probe itself defines nine small enums and mangles three # # names per class, which is nothing beside the interpreter # # load. The three starts are `import', `json-import' and the # # probe -- this package ships no script, so there is no # # fourth. # # # # 1200 is 99-python3-jstyleson's and 99-python3-jsonpointer's # # figure, for the same reason: the start dominates and the # # work after it is comparable. # # # # 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-strenum 0.4.15 (meta-openembedded/meta-python # recipes-devtools/python/python3-strenum_0.4.15.bb, `inherit pypi # python_setuptools_build_meta', PyPI sdist) on the wasm32-hwjs glibc image -- # AN ENUM WHOSE MEMBERS ARE ALSO STRINGS. `class StrEnum(str, enum.Enum)', plus # a family of subclasses whose `auto()' derives each member's value from its # NAME by a documented casing rule. # # ============================================================================ # A MEMBER IS BOTH HALVES, AND EACH HALF IS ASSERTED ON ITS OWN. # ============================================================================ # # The whole point of the package is that one object satisfies two contracts at # once, so the interesting failures are the ones where only one half survives -- # a degradation to a plain `enum.Enum' (the Enum half works, the str half is # gone) or to a bare string constant (the reverse). Either would pass a suite # that asserted only its own side. # # THE str HALF is asserted through the four things a caller actually does: # `isinstance(member, str)'; `==' against the bare string, both ways round; # use as a `dict' key INTERCHANGEABLY with that string, which needs the hash to # agree and not just the equality; and `json.dumps' emitting it as a bare string # rather than as an object or a TypeError. # # THE Enum HALF is asserted as IDENTITY, not equality: `EnumName("value") is # EnumName.MEMBER'. `==' would be satisfied by any string equal to the value, # which is exactly what the str half guarantees, so an `==' here would assert # nothing. The documented failures come with it -- `ValueError' for a value # never defined, `KeyError' for a name never defined. # # ============================================================================ # THE EXPECTED VALUES ARE UPSTREAM'S OWN DOCUMENTATION, NOT A TRANSCRIPTION. # ============================================================================ # # Every class in strenum/__init__.py carries a docstring that DEFINES the class # by stating what `auto()' produces for exactly three member names -- # `UPPER_CASE', `lower_case' and `MixedCase'. The probe defines its enums from # those same three names, so each `val-*' case below asserts a value upstream # documents in the docstring of the very class being driven, rather than one # somebody measured and copied down. # # That is worth stating because the casing rules are not guessable. `MixedCase' # under LowercaseStrEnum is `mixedcase' (one word, lowered) but under # SnakeCaseStrEnum is `mixed_case' (split, then joined) -- the difference is that # the derived classes run `_NameMangler.words' first and Lowercase/Uppercase do # not. Getting that from the source rather than from the docstrings is how a # suite ends up asserting what the reader assumed. # # ============================================================================ # WHY IT PORTS AT ALL: PURE PYTHON, FOUR FILES, FOUR stdlib IMPORTS. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an EMPTY lib-dynload and no # dynamic loader (BRINTOS-1476; 99-python3 asserts both directly). A module # carrying a compiled extension could not be imported here -- not "would be # slow", could not be imported. # # strenum ships NO C: no .c, .pyx or .so in the sdist, and setup.py declares # `packages=["strenum"]' with no ext_modules at all. `pkg-no-so' below is a # REGRESSION TRIPWIRE on a version bump rather than a statement about today. # # Its import closure, measured as the sys.modules delta from a pristine # interpreter, is `enum', `re', `zlib' and `json' -- all standard library, and # all but `json' in poky's python3-core split. See the bbappend: `json' is # reached at MODULE SCOPE through the vendored `_version.py' and is the recipe's # one added RDEPENDS, which `pkg-depends-json' asserts by name. # # `zlib' is worth a word. It is `zlib.crc32', reached only from # `_NameMangler.spongebob', and on this image it is a statically linked builtin # rather than the `lib-dynload/zlib.*.so' poky's manifest names. `crc32' below # pins its value on the one input the mangler is driven with, so an arm where # the checksum disagreed with x86-64 would say so HERE rather than as an # unexplained casing difference in `spongebob'. # # ============================================================================ # THE BUILD-HOST PATCH DOES NOT REACH THIS IMAGE. # ============================================================================ # # The recipe carries 0001-patch-versioneer-for-python-3.12-compatibility.patch, # which fixes `configparser.SafeConfigParser'/`readfp' -- removed in Python 3.12 # -- inside the sdist's vendored `versioneer.py'. That file runs on the BUILD # HOST during setup.py and is not packaged. The four modules unpacked from the # built .deb are byte-identical to the sdist, so nothing the target imports was # patched; `module-answered' and the payload cases are about those bytes. # # ============================================================================ # WHAT IS NOT COVERED. # ============================================================================ # # The recipe does not `inherit ptest' and the sdist ships no tests directory at # all (upstream's suite lives in the git repo, not in the tarball), so there is # no upstream suite to run here or anywhere. These cases are the only statement # about this package on this image. # # Of the thirteen exported classes, EIGHT are driven end to end and the other # five are asserted present only (`api-*'). The five left out -- # CamelSnakeCase, PascalSnakeCase, SpongebobCase, CobolCase, HttpHeaderCase -- # are the same `_NameMangler.words' split as the eight, differing only in the # join and the casing, and `words-*' asserts that split directly. Spongebob's # mangler IS driven, through `spongebob' below, because it is the one whose # output nobody could predict by reading it. # # ============================================================================ # WHERE THE ASSERTED VALUES COME FROM. # ============================================================================ # # Every literal below was measured on x86-64 against the sdist the recipe's # SRC_URI sha256 pins (878fb5ab...eff, verified by digest before measuring), and # again against the tree unpacked from the built # python3-strenum_0.4.15-r0_wasm32.deb -- the two runs agree key for key. The # control interpreter is CPython 3.12.3 and the image ships 3.12; that is the # same major.minor, which matters here because several cases below are # properties of CPython's own `enum' rather than of strenum. The on-target # result is not yet measured -- the batch verify is this port's first run on # wasm32-hwjs. P=python3-strenum # THE CANARY, which asserts nothing about python3-strenum. # # 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' guards 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 PKGDIR=$SITE/strenum DI=$SITE/StrEnum-0.4.15.dist-info PKGLIST=/var/lib/dpkg/info/python3-strenum.list PROBE=$BST_DATA/python3-strenum-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-strenum.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import strenum' further down is the functional # statement; these are the ones that say WHICH copy answered, so a package that # imported out of some other path is distinguishable from one that imported out # of what this image installed. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" # THE FOUR MODULES, individually. `__init__.py' alone would import and would # serve nothing: the classes are built from `_name_mangler', the version from # `_version', and `mixins' is a separate importable the Comparable cases drive. # A truncated wheel is the failure these separate the import case from. check $P/module-init test -f "$PKGDIR/__init__.py" check $P/module-mangler test -f "$PKGDIR/_name_mangler.py" check $P/module-version test -f "$PKGDIR/_version.py" check $P/module-mixins test -f "$PKGDIR/mixins.py" # THE TYPE STUBS AND THE MARKER, which are what setup.py's `package_data' is for # and are the only reason this package ships anything that is not a .py. They # carry no runtime behaviour -- nothing below imports them -- but a caller # running a type checker against this rootfs gets nothing without them, and # `pkg-only-python' below would not notice their absence. check $P/stub-init test -f "$PKGDIR/__init__.pyi" check $P/stub-mixins test -f "$PKGDIR/mixins.pyi" check $P/py-typed test -f "$PKGDIR/py.typed" # NO COMPILED EXTENSION ANYWHERE THE PACKAGE PUT ANYTHING. See the header: on # this image a .so could not be loaded at all, so this is the case that fails a # version bump which grew one -- loudly, here, instead of at `import' later. # # The list is copied to tmpfs first and never redirected from the rootfs: a # top-level stdin redirect on the virtiofs root makes dup2(2) return 513 and ash # EXITS, ending the suite silently (brintos/linux#73). cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # EVERY REGULAR FILE THE PACKAGE SHIPS IS PYTHON, A TYPE STUB OR METADATA. The # case above names the failure that is expected; this one catches the shape # nobody predicted -- a binary, a .pyd, a stray README -- without having to # enumerate what it might be. This package ships no script at all, so unlike # 99-python3-cbor2's equivalent there is no executable to exempt: anything # outside site-packages is unexpected here by construction. # # A dpkg .list names DIRECTORIES too, and without a trailing slash, so a # name-only filter would report every directory in the package as an unexpected # shape; each entry is therefore tested for being a regular file first. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc|*.pyi) continue ;; */py.typed) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ----------------------------------------------------------- the dist-info --- # The installed metadata. The DIRECTORY NAME is `StrEnum-0.4.15.dist-info' -- # the distribution's name is `StrEnum' while the importable is `strenum', which # is why `PYPI_PACKAGE = "StrEnum"' is in the recipe and why the probe reads # `importlib.metadata.version("StrEnum")' rather than the module name. A wheel # built with the two conflated would land the metadata under a path nothing # looks in, and `dist-version' below would come back empty while every # behavioural case still passed. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: StrEnum" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 0.4.15" cat "$DI/METADATA" # top_level.txt names the ONE importable this package installs, and it is the # LOWERCASE name. It is what an `import strenum' has to match, and it is the # machine-readable form of the case split described above. check_out $P/dist-info-top-level "strenum" cat "$DI/top_level.txt" # PURE PYTHON, as the wheel itself declares it: `Root-Is-Purelib: true' and a # `py3-none-any' tag. This is the metadata half of `pkg-no-so' -- a wheel that # had grown a compiled extension would carry a platform tag here first. check_out $P/wheel-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/wheel-tag "Tag: py3-none-any" cat "$DI/WHEEL" # -------------------------------------------------------------- the package --- # dpkg KNOWS the package. Every case above would look identical if some other # package had installed the same files, so this is the one that says # python3-strenum is a real package this image installed rather than an # IMAGE_INSTALL line that resolved to nothing. # # The stanza is read from a tmpfs copy for the same reason as the file list, and # the anchor is exact -- an unanchored match would also take the -dev or -dbg # stanza of the same family. sed -n '/^Package: python3-strenum$/,/^$/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" # THE RUNTIME DEPENDENCIES, as dpkg recorded them. python3-core comes with the # packaging; python3-json is THIS RECIPE'S ONE ADDITION and it is load-bearing # at import time -- see the bbappend, where refusing `json' on an x86-64 control # raises ImportError during `import strenum' rather than at first use. This # image installs python3-json for other reasons, so the declaration changes no # rootfs; this case is what says the PACKAGE is correct on an image that does # not. 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-core "python3-core" cat "$W/dep" check_out $P/pkg-depends-json "python3-json" cat "$W/dep" # ---------------------------------------------------------- the interpreter --- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If strenum cannot be imported then # every probe case below reds at once and each names a different symptom; this # case is what says the cause is the import. It is also the cheapest possible # statement of the deliverable. check $P/import python3 -c 'import strenum' # ...and `json' separately, because strenum reaches it at MODULE SCOPE through # the vendored `_version.py' -- a missing python3-json presents AS a broken # strenum, with an ImportError naming `json' from inside a package that has # nothing to do with JSON. This case is what tells the two apart. check $P/json-import python3 -c 'import json' # ---------------------------------------------------------------- the probe --- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" 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. bst_probe $P/probe-run "$W/probe" 900 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 `str_member' distinct # from `str_explicit', and `val_lower_lower_case' from `val_lower_MixedCase'. 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 library, 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 in 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 # The interpreter that answered. Several cases below are properties of CPython's # own `enum' rather than of strenum -- `contains-str' and `fstring-member' most # of all, both of which moved across 3.10/3.11/3.12 for str-mixin enums. The # control was 3.12.3, so this case is what says the comparison is like for like. check_eq $P/py-version "3.12" "$(pv py_version)" # THE VERSION, THREE TIMES, FROM THREE INDEPENDENT PLACES: the module's own # `__version__' (computed at import from the vendored `_version.py'), the # `__version_info__' tuple derived from it, and importlib.metadata over the # installed dist-info. A module that landed while its dist-info did not would # import fine and serve every behavioural case below while reporting no # installed version at all -- and vice versa. check_eq $P/version-attr "0.4.15" "$(pv version_attr)" check_eq $P/version-info "0.4.15" "$(pv version_info)" check_eq $P/dist-version "0.4.15" "$(pv dist_version)" # `get_versions' is DELETED from the module namespace by `__init__.py' once it # has been called. It is the one place this package's import has a side effect # on its own namespace, and a release that stopped deleting it would leave a # versioneer internal in the public surface where `dir(strenum)' shows it. check_eq $P/no-get-versions "no" "$(pv has_get_versions)" # The module answered out of the directory this image installed, not from a # stray copy on sys.path. This is what makes the payload cases above and the # behaviour cases below statements about the SAME strenum. check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" # THE PUBLIC CLASSES, one case each. This package declares no `__all__', so # `dir()' would report what is BOUND rather than what is exported -- including # its own `import enum'. Naming the thirteen individually is the statement that # matters: a wheel that shipped a truncated module would import, and would serve # StrEnum, while every derived class was gone. for _n in StrEnum LowercaseStrEnum UppercaseStrEnum CamelCaseStrEnum \ PascalCaseStrEnum KebabCaseStrEnum SnakeCaseStrEnum \ MacroCaseStrEnum CamelSnakeCaseStrEnum PascalSnakeCaseStrEnum \ SpongebobCaseStrEnum CobolCaseStrEnum HttpHeaderCaseStrEnum; do check_eq $P/api-$_n "yes" "$(pv has_$_n)" done # `strenum.mixins.Comparable' is a SEPARATE MODULE and is NOT re-exported from # the package root, so it is asserted on its own rather than in the loop above. check_eq $P/api-Comparable "yes" "$(pv has_Comparable)" # ------------------------------------------------------------- the fixture --- # The three member names every class below is defined from. See the header: # these are upstream's own docstring names, which is what makes each `val-*' # case an assertion about documented behaviour rather than a transcription. A # red here means the probe was edited, and names the common cause of the reds # that would follow. check_eq $P/names "UPPER_CASE,lower_case,MixedCase" "$(pv names)" # --------------------------------------------- auto() PER CLASS, THE DELIVERABLE # One case per (class, name). Each derived class builds a member's value from # its NAME by a documented rule, so a mangler that regressed changes exactly one # row here rather than breaking an import. # # StrEnum itself: the value IS the name, unchanged. This is the base case and # the control for the eight rows under it -- a `_generate_next_value_' that had # stopped being called at all would leave every class agreeing with this one. check_eq $P/val-plain-UPPER_CASE "UPPER_CASE" "$(pv val_plain_UPPER_CASE)" check_eq $P/val-plain-lower_case "lower_case" "$(pv val_plain_lower_case)" check_eq $P/val-plain-MixedCase "MixedCase" "$(pv val_plain_MixedCase)" # Lowercase and Uppercase: `name.lower()' and `name.upper()', with NO word # split. `MixedCase' therefore gives `mixedcase' and `MIXEDCASE' -- one word -- # where every mangler-backed class below gives a split. That difference is the # whole reason these two are asserted separately from Snake and Macro, which # would otherwise look like the same rule. check_eq $P/val-lower-UPPER_CASE "upper_case" "$(pv val_lower_UPPER_CASE)" check_eq $P/val-lower-lower_case "lower_case" "$(pv val_lower_lower_case)" check_eq $P/val-lower-MixedCase "mixedcase" "$(pv val_lower_MixedCase)" check_eq $P/val-upper-UPPER_CASE "UPPER_CASE" "$(pv val_upper_UPPER_CASE)" check_eq $P/val-upper-lower_case "LOWER_CASE" "$(pv val_upper_lower_case)" check_eq $P/val-upper-MixedCase "MIXEDCASE" "$(pv val_upper_MixedCase)" # CamelCase: split into words, first lowered, the rest title-cased, joined with # nothing. The one the ticket names alongside Lowercase and Uppercase. check_eq $P/val-camel-UPPER_CASE "upperCase" "$(pv val_camel_UPPER_CASE)" check_eq $P/val-camel-lower_case "lowerCase" "$(pv val_camel_lower_case)" check_eq $P/val-camel-MixedCase "mixedCase" "$(pv val_camel_MixedCase)" # PascalCase: every word title-cased, joined with nothing. `MixedCase' maps to # ITSELF here, which is why the other two names carry this case: an # implementation that returned the name unchanged would pass the third row alone. check_eq $P/val-pascal-UPPER_CASE "UpperCase" "$(pv val_pascal_UPPER_CASE)" check_eq $P/val-pascal-lower_case "LowerCase" "$(pv val_pascal_lower_case)" check_eq $P/val-pascal-MixedCase "MixedCase" "$(pv val_pascal_MixedCase)" # kebab-case, snake_case and MACRO_CASE: the same word split as Camel and # Pascal, differing only in the join and the casing. `lower_case' maps to itself # under Snake and `UPPER_CASE' under Macro, so each rule is asserted on all # three names rather than on the one that happens to move. check_eq $P/val-kebab-UPPER_CASE "upper-case" "$(pv val_kebab_UPPER_CASE)" check_eq $P/val-kebab-lower_case "lower-case" "$(pv val_kebab_lower_case)" check_eq $P/val-kebab-MixedCase "mixed-case" "$(pv val_kebab_MixedCase)" check_eq $P/val-snake-UPPER_CASE "upper_case" "$(pv val_snake_UPPER_CASE)" check_eq $P/val-snake-lower_case "lower_case" "$(pv val_snake_lower_case)" check_eq $P/val-snake-MixedCase "mixed_case" "$(pv val_snake_MixedCase)" check_eq $P/val-macro-UPPER_CASE "UPPER_CASE" "$(pv val_macro_UPPER_CASE)" check_eq $P/val-macro-lower_case "LOWER_CASE" "$(pv val_macro_lower_case)" check_eq $P/val-macro-MixedCase "MIXED_CASE" "$(pv val_macro_MixedCase)" # ------------------------------------------------------------- the str half --- # A member IS a string. See the header: these are what a caller actually does # with that, and each fails a different degradation. # # `isinstance(member, str)' -- the whole claim of the package, in one case. A # plain `enum.Enum' fails here and passes nothing below it. The Enum side is # asserted beside it so a degradation the OTHER way is equally visible. check_eq $P/isinstance-str "yes" "$(pv isinstance_str)" check_eq $P/isinstance-enum "yes" "$(pv isinstance_enum)" # EQUALITY WITH THE BARE STRING, both ways round -- which is what lets a StrEnum # be dropped into code written against string constants. Both directions, # because a mixin defining only a left-hand `__eq__' would pass one and fail the # other; and the inequality control, without which an `__eq__' returning True # unconditionally would pass the pair. check_eq $P/eq-value "yes" "$(pv eq_value)" check_eq $P/eq-value-rev "yes" "$(pv eq_value_rev)" check_eq $P/ne-other "yes" "$(pv ne_other)" # HASHING AS THE STRING, which is what makes a member usable as a `dict' key # INTERCHANGEABLY with its value. Four cases, because equality without a # matching hash gives a dict that silently holds both: the hashes agree; setting # the string key over the member key leaves ONE entry; that entry reads back as # the second value; and a dict built with a plain string key answers a member # lookup. check_eq $P/hash-eq-str "yes" "$(pv hash_eq_str)" check_eq $P/dict-one-key "yes" "$(pv dict_one_key)" check_eq $P/dict-lookup-by-str "2" "$(pv dict_lookup_by_str)" check_eq $P/dict-lookup-by-member "from-str" "$(pv dict_lookup_by_member)" # `json.dumps' EMITS IT AS A BARE STRING, with no default= hook. json's encoder # special-cases `str' subclasses, so a member serialises as `"MixedCase"' -- the # property that makes these enums usable in a wire payload. A plain Enum raises # TypeError here instead. Asserted as a value, inside a dict, and AS A KEY: the # key path goes through a different branch of the encoder, which a member that # was a str for values but not for keys would fail alone. check_eq $P/json-dumps-member '"MixedCase"' "$(pv json_dumps_member)" check_eq $P/json-dumps-dict '{"k": "MixedCase"}' "$(pv json_dumps_dict)" check_eq $P/json-dumps-as-key '{"MixedCase": 1}' "$(pv json_dumps_as_key)" # STRING METHODS RETURN PLAIN STRINGS, NOT MEMBERS. `str.upper' is inherited, so # it operates on the value and gives back a `str' -- a caller that stored the # result would no longer hold an Enum member. That is worth stating rather than # leaving to be discovered, and the TYPE case is the one that states it. check_eq $P/upper-method "MIXEDCASE" "$(pv upper_method)" check_eq $P/upper-method-type "str" "$(pv upper_method_type)" check_eq $P/concat "MixedCase!" "$(pv concat)" check_eq $P/in-operator "yes" "$(pv in_operator)" # ------------------------------------------- str(), format() AND THE f-STRING -- # StrEnum overrides `__str__' to return `str(self.value)', so `str(member)' # gives the bare value rather than `EnumName.MEMBER'. But an f-string goes # through `__format__', NOT `__str__', and CPython has changed that path for # str-mixin enums across 3.10/3.11/3.12 -- so the three are separate cases, and # a platform where they disagreed would say which one moved. `py-version' above # is what makes that comparison like for like. check_eq $P/str-member "MixedCase" "$(pv str_member)" check_eq $P/format-member "MixedCase" "$(pv format_member)" check_eq $P/fstring-member "MixedCase" "$(pv fstring_member)" # `repr' is the one that KEEPS the qualified form -- `' -- which is what makes a traceback readable after `__str__' has # been flattened. A release that overrode `__repr__' too would lose that, and it # would be invisible everywhere else in this suite. check_eq $P/repr-member "" "$(pv repr_member)" # ...and the same, on an enum whose VALUES ARE EXPLICIT and differ from the # names. Everything above exercises `_generate_next_value_'; these exercise the # `__new__' that makes a member a `str' in the first place. A `__str__' that had # regressed to returning the NAME would pass every `auto()' case above and red # only here. check_eq $P/str-explicit "a-value" "$(pv str_explicit)" check_eq $P/fstring-explicit "a-value" "$(pv fstring_explicit)" check_eq $P/value-explicit "a-value" "$(pv value_explicit)" check_eq $P/name-explicit "ALPHA" "$(pv name_explicit)" # ------------------------------------------------------------ the enum half --- # LOOKUP BY VALUE and LOOKUP BY NAME, each round-tripping back to the SAME # member OBJECT -- `is', not `=='. See the header: identity is the point, since # `==' would be satisfied by any string equal to the value. check_eq $P/by-value-is-member "yes" "$(pv by_value_is_member)" check_eq $P/by-name-is-member "yes" "$(pv by_name_is_member)" check_eq $P/by-value-explicit "yes" "$(pv by_value_explicit)" # ...and the documented failures. A VALUE never defined raises ValueError; a # NAME never defined raises KeyError. Both the type and the message are # asserted: the type is the contract a caller catches on, the message is what # says the lookup failed for the reason expected rather than somewhere else. # Note the two are NOT interchangeable -- `Plain("MixedCase")' succeeds where # `Plain["MixedCase"]' also succeeds only because this enum's values equal its # names, which is why `by-value-explicit' above drives one where they differ. check_eq $P/bad-value-exc "ValueError" "$(pv bad_value_exc)" check_eq $P/bad-value-msg "'no-such-value' is not a valid Plain" "$(pv bad_value_msg)" check_eq $P/bad-name-exc "KeyError" "$(pv bad_name_exc)" check_eq $P/bad-name-msg "'NoSuchName'" "$(pv bad_name_msg)" # THE MEMBERS, IN DEFINITION ORDER, and the count. Iteration order is an Enum # guarantee, and a class that had silently gained or lost a member would show # here rather than in whichever case happened to name it. check_eq $P/members "UPPER_CASE,lower_case,MixedCase" "$(pv members)" check_eq $P/member-count "3" "$(pv member_count)" check_eq $P/values "UPPER_CASE,lower_case,MixedCase" "$(pv values)" # `"MixedCase" in Plain' with a BARE STRING. This is a property of THIS # INTERPRETER, not of strenum: 3.12 accepts a value here, and CPython before # 3.12 raised TypeError. It is asserted rather than left out because the answer # is what a caller writing `if header in HttpHeader' gets, and a red here would # be a statement about the image's python3 rather than about this package. check_eq $P/contains-str "yes" "$(pv contains_str)" # ------------------------------------------- the type check in __new__, as one # `StrEnum.__new__' raises TypeError for a non-str value, with a message naming # the offending value and its type. That is THIS PACKAGE'S own code rather than # `enum''s, so it is asserted directly: a class definition with an `int' value # must not silently produce a member. The message is asserted whole because it # is the package's own text -- it is the only error string strenum authors. check_eq $P/int-value-exc "TypeError" "$(pv int_value_exc)" check_eq $P/int-value-msg \ "Values of StrEnums must be strings: 1 is a " \ "$(pv int_value_msg)" # ------------------------------------------------- the Comparable mixin, once # The documented case-insensitive header enum, straight out of `mixins.py''s own # docstring: a KebabCaseStrEnum whose `_cmp_values' lowers both sides. It is the # one part of this package that is a MIXIN rather than a base class, so it is # also the one place a caller supplies code of their own -- and it is where the # "a bug in Python prior to 3.7.1 prevents mix-ins working with Enum subclasses" # note in that docstring would land if this image's python3 had the same defect. check_eq $P/cmp-value "content-type" "$(pv cmp_value)" check_eq $P/cmp-exact "yes" "$(pv cmp_exact)" check_eq $P/cmp-lower "yes" "$(pv cmp_lower)" check_eq $P/cmp-mixed "yes" "$(pv cmp_mixed)" check_eq $P/cmp-ne "yes" "$(pv cmp_ne)" # --------------------------------------------------------- the mangler itself # `_NameMangler.words' is the split EVERY derived class is built on, so a `val-*' # case that reds above is either the split or the casing -- these are what # separate the two. It is a private name, driven deliberately: the cases above # assert the PUBLIC values, and this is the one extra reading that says WHERE a # wrong value came from. `XForwardedFor' is upstream's own hard case, from the # `words' docstring: a leading single capital must split off on its own. check_eq $P/words-UPPER_CASE "UPPER,CASE" "$(pv words_UPPER_CASE)" check_eq $P/words-lower_case "lower,case" "$(pv words_lower_case)" check_eq $P/words-MixedCase "Mixed,Case" "$(pv words_MixedCase)" check_eq $P/words-XForwardedFor "X,Forwarded,For" "$(pv words_XForwardedFor)" # SPONGEBOB IS DETERMINISTIC, and that is a documented promise ("your tests will # thank me"): the PRNG is seeded from the word itself. It is the one mangler # whose output nobody could predict by reading it, and the value below is the # one upstream's own SpongebobCaseStrEnum docstring gives for `MixedCase'. # # It is also the only path in this package that reaches `zlib.crc32', which on # this image is a STATICALLY LINKED BUILTIN rather than the # `lib-dynload/zlib.*.so' poky's manifest names (BRINTOS-1476). `crc32' below # pins the checksum on that same input, so an arm where it disagreed with # x86-64 says THAT here instead of presenting as an unexplained casing bug. check_eq $P/spongebob "MixeD_CAse" "$(pv spongebob)" check_eq $P/crc32 "335704928" "$(pv crc32_probe)" # The probe ran to the end. Every case above greps a key, so a probe that died # halfway would leave the remaining keys absent and each case would red with # got:'' -- this case is the one that says the cause was a truncated run. check_eq $P/probe-complete "yes" "$(pv probe_complete)" rm -rf "$W"