. "$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 an import # # failure is legible without reading a transcript) and the # # probe, which takes ONE start for 122 measurements. The # # work inside it is a few hundred proxy operations over # # ints, lists and dicts -- 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, the same per-start budget # # 99-python3-crc32c and 99-python3-annotated-types give # # theirs. 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-lazy-object-proxy 1.10.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-lazy-object-proxy_1.10.0.bb, `inherit pypi # setuptools3', PyPI sdist sha256 78247b6d...ac69) on the wasm32-hwjs glibc # image, WITH this layer's python3-lazy-object-proxy_%.bbappend. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # `Proxy(factory)' stands in for an object that does not exist yet. Nothing # calls `factory' until something TOUCHES the proxy -- an attribute, an # operator, an item, `len', iteration -- and from then on every operation is # forwarded to whatever the factory returned. `__class__' reports the target's # class rather than `Proxy', so `isinstance' answers about the target while # `type()' still reports the proxy. # # So the suite asserts LAZINESS FIRST and forwarding second. A proxy that # resolved eagerly would forward everything perfectly and be worthless. # # ============================================================================ # THE PORT IS THE bbappend, AND THIS SUITE IS WHERE THAT IS VISIBLE. # ============================================================================ # # The package ships the same `Proxy' twice: `lazy_object_proxy.cext', a CPython # extension, and `lazy_object_proxy.simple', pure Python. __init__.py takes the # second when the first raises ImportError. # # The extension BUILDS on this arm -- the stock recipe with no bbappend # completed 1678 of 1678 tasks with zero ERROR: lines and packaged a # 98157-byte wasm side module with dylink.0 and hwjs.abi -- and it can never be # imported: 83 of its 112 wasm imports are the CPython C API (the whole # PyNumber_* surface, PyObject_GetAttr/SetItem/RichCompare, PyType_Ready, # PyModule_Create2 ...) and this image's /usr/bin/python3.12 exports 35 symbols # of which not one is Py*. That is BRINTOS-2046. # # So the bbappend exports upstream's own SETUPPY_FORCE_PURE=1 for class-target # and the accelerator is never built. `no-so' and `record-no-so' below are the # two cases that would notice a regression putting it back, and `impl' is the # case that says which half is live -- a silent return of the extension is a # VISIBLE result here rather than an invisible one. # # ============================================================================ # 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 lazy-object-proxy 1.10.0 # installed from the same PyPI sdist the recipe fetches, under CPython 3.12 in # a venv built with SETUPPY_FORCE_PURE=1 -- the same switch the bbappend sets, # so the control exercised the same `simple' half the target runs. It emitted # 123 lines and no stderr. # # The control was ALSO run in a second venv built WITHOUT that switch, on the # `cext' half, and the two transcripts agree on 119 of 123 keys. The four that # differ are the three `cext_*'/`impl' keys and TWO REAL BEHAVIOURAL # DIVERGENCES, which are upstream's own inconsistency rather than this arm's # and are the measured cost of the fallback: # # * `op_round': `round(Proxy(3.567), 2)' answers 3.57 through `simple' and # raises `TypeError: Proxy.__round__() takes no arguments (1 given)' # through `cext'. simple.py routes __round__ through make_proxy_method, # which forwards *args; the C tp_slot takes none. The PURE half is the # correct one here. # # * `op_iadd': `q = Proxy(10); q += 5' leaves a plain `int' through `simple' # and a `Proxy' through `cext'. simple.py's __iadd__ returns # `operator.iadd(wrapped, 5)' raw, so the name rebinds to the result; the # C half returns the proxy itself. # # Both are asserted below at their `simple' values, WITH the cext value named, # so a future image that regained the accelerator fails at a case that says # exactly which behaviour changed. # # The packaging cases could not be measured on the control and come from THIS # BUILD's own .deb instead (`dpkg-deb -c/-f' on # python3-lazy-object-proxy_1.10.0-r0_wasm32.deb). # # ============================================================================ # THE TWO PAYLOAD FACTS THAT LOOK WRONG AND ARE NOT. # ============================================================================ # # * `cext.c' STILL SHIPS -- 39102 bytes of the extension's C source under # site-packages -- even with the extension not built. setup.py declares # `include_package_data=True' and setuptools_scm's file finder lists it, so # it travels as package data rather than as a build product. It is inert: # nothing imports a .c file, and `no-so' below is what asserts the part # that could actually fail. Pinned by size so a release that changed it is # visible rather than silent. # # * THE WHEEL TAG NAMES THE BUILD HOST, not this arm: `Tag: # cp312-cp312-linux_x86_64' with `Root-Is-Purelib: false', on a package # that now contains no compiled code at all. setup.py's # `BinaryDistribution.has_ext_modules()' returns True unless # SETUPPY_ALLOW_PURE is set, so bdist_wheel computes a platform tag, and it # computes it from the machine doing the cross-build. This is the same # wrong-tag finding 99-python3-crc32c records for its own wheel. It changes # nothing at runtime -- the recipe installs the payload into # site-packages by path and never consults the tag -- and it is pinned here # so that a poky whose bdist_wheel started emitting the target triple, or a # recipe that started setting SETUPPY_ALLOW_PURE, is a visible change. P=python3-lazy-object-proxy # ============================================================================ # 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/lazy_object_proxy DI=$SITE/lazy_object_proxy-1.10.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-lazy-object-proxy.list PROBE=$BST_DATA/python3-lazy-object-proxy-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-lazy-object-proxy.$$ 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/simple-present test -s "$PKG/simple.py" check $P/slots-present test -s "$PKG/slots.py" check $P/utils-present test -s "$PKG/utils.py" check $P/compat-present test -s "$PKG/compat.py" check $P/version-present test -s "$PKG/_version.py" # THE SIZES ARE PINNED RATHER THAN FLOORED: a truncated or partially written # payload passes `test -s' and fails only here. Every figure is the sdist's own # bytes and is what this build's RECORD declares. check_eq $P/init-size 414 \ "$(wc -c < "$PKG/__init__.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/simple-size 8680 \ "$(wc -c < "$PKG/simple.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/slots-size 12183 \ "$(wc -c < "$PKG/slots.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/utils-size 1205 \ "$(wc -c < "$PKG/utils.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/compat-size 148 \ "$(wc -c < "$PKG/compat.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) check_eq $P/version-size 413 \ "$(wc -c < "$PKG/_version.py" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # NO COMPILED EXTENSION SHIPS, AND THAT IS THE PORT. This is the case that # fails if the bbappend's SETUPPY_FORCE_PURE stopped taking, or if a poky # change made the export invisible to setup.py. On this image a `.so' in # site-packages is not a speed-up, it is an ImportError against an interpreter # that exports no Py* symbol (BRINTOS-1476, BRINTOS-2046). Asserted over the # INSTALLED PAYLOAD rather than over the recipe. check_eq $P/no-so "" "$(find "$PKG" -name '*.so' 2>/dev/null)" # THE C SOURCE STILL SHIPS AND IS INERT -- see the header. Pinned by size, not # merely asserted absent, because asserting it absent would be a claim this # port does not make. check $P/cext-c-present test -s "$PKG/cext.c" check_eq $P/cext-c-size 39102 \ "$(wc -c < "$PKG/cext.c" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # The byte-compiled copies are part of the payload and are why `import' on this # target is a read rather than a parse of 22 KB of source. Asserted present and # non-empty rather than pinned: a .pyc header carries build-time state the # source does not. `simple' is the one that matters -- it is the implementation # this image runs -- and `slots' is byte-compiled too even though nothing # imports it, which is dpkg-deb's own listing rather than an inference. check $P/pycache-init test -s "$PKG/__pycache__/__init__.cpython-312.pyc" check $P/pycache-simple test -s "$PKG/__pycache__/simple.cpython-312.pyc" check $P/pycache-slots test -s "$PKG/__pycache__/slots.cpython-312.pyc" check $P/pycache-utils test -s "$PKG/__pycache__/utils.cpython-312.pyc" check $P/pycache-compat test -s "$PKG/__pycache__/compat.cpython-312.pyc" check $P/pycache-version test -s "$PKG/__pycache__/_version.cpython-312.pyc" # NO EXECUTABLE. setup.py declares no console_scripts and no [project.scripts], # 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/lazy-object-proxy # --------------------------------------------------------------- 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 $P/dist-info-license test -s "$DI/LICENSE" check $P/dist-info-authors test -s "$DI/AUTHORS.rst" check_out $P/dist-info-version "Version: 1.10.0" cat "$DI/METADATA" check_out $P/dist-info-name "Name: lazy-object-proxy" cat "$DI/METADATA" check_out $P/dist-info-license-field "License: BSD-2-Clause" cat "$DI/METADATA" check_out $P/meta-requires-python "Requires-Python: >=3.8" cat "$DI/METADATA" check_eq $P/top-level "lazy_object_proxy" \ "$(cat "$DI/top_level.txt" 2>/dev/null | tr -d '\n\r')" # BST-REDIRECT-OK: forked -- inside $( ) # THE PACKAGE DECLARES NO RUNTIME DEPENDENCY AT ALL. setup.py's # `install_requires' is empty and there is no environment-marked extra, so a # METADATA that grew a `Requires-Dist:' line would mean upstream changed shape. check_eq $P/meta-no-requires-dist "" \ "$(grep '^Requires-Dist:' "$DI/METADATA" 2>/dev/null)" # BST-REDIRECT-OK: forked -- inside $( ) # THE WHEEL TAG NAMES THE BUILD HOST -- see the header. Pinned so a change in # either direction is visible. check_out $P/wheel-generator "Generator: bdist_wheel (0.42.0)" cat "$DI/WHEEL" check_out $P/wheel-purelib "Root-Is-Purelib: false" cat "$DI/WHEEL" check_out $P/wheel-tag "Tag: cp312-cp312-linux_x86_64" cat "$DI/WHEEL" # THE RECORD IS THE WHEEL'S OWN MANIFEST, and it is the second place a returned # accelerator would show. `no-so' reads the filesystem; this reads what the # build declared it installed. Both, because they can disagree. check_eq $P/record-no-so "" \ "$(grep '\.so,' "$DI/RECORD" 2>/dev/null)" # BST-REDIRECT-OK: forked -- inside $( ) check_out $P/record-has-simple "lazy_object_proxy/simple.py" cat "$DI/RECORD" check_out $P/record-has-cext-c "lazy_object_proxy/cext.c" cat "$DI/RECORD" # ------------------------------------------------------------ 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, the inert # C source 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|*.c) ;; */METADATA|*/WHEEL|*/RECORD|*/INSTALLER|*/LICENSE|*/LICENSE.*) ;; */AUTHORS.rst|*/entry_points.txt|*/top_level.txt) ;; *) _bad="$_bad $_f" ;; esac done < "$W/pkgf" check_eq $P/pkg-only-expected "" "$_bad" # And the same list carries no shared object, which is the packaging-side # statement of what `no-so' asserts on disk. check_eq $P/pkg-list-no-so "" \ "$(grep '\.so$' "$W/pkgf" 2>/dev/null)" # BST-REDIRECT-OK: forked -- inside $( ) # ------------------------------------------------------------- dpkg status --- sed -n '/^Package: python3-lazy-object-proxy$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $P/pkg-known test -s "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" check_out $P/pkg-arch "Architecture: wasm32" cat "$W/st" check_out $P/pkg-version "Version: 1.10.0-r0" cat "$W/st" # THE DEPENDENCY SET, asserted by name AND by exhaustion. The recipe declares # no RDEPENDS of its own and install_requires is empty, so `python3-core' is # the whole of it -- this port waits on nothing, which is why it could ride # this batch at all. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" check_eq $P/pkg-depends-exact "Depends: python3-core" \ "$(cat "$W/dep" 2>/dev/null | tr -d '\r')" # BST-REDIRECT-OK: forked -- inside $( ) # ------------------------------------------------------------------ import --- # ONE CASE OF ITS OWN, and the first of two python3 starts. It is here so that # an import failure is legible without reading a probe transcript. check $P/import python3 -c 'import lazy_object_proxy' # THE FALLBACK IS TAKEN, asserted in one line without the probe. This is the # port's whole claim, and it is worth a case that does not depend on the probe # having run: `lazy_object_proxy.cext' must NOT be importable. check_rc $P/cext-not-importable 1 python3 -c 'import lazy_object_proxy.cext' # ------------------------------------------------------------------- probe --- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "probe missing at $PROBE" exit 0 fi bst_ok $P/probe-present # THE SECOND START, and the one that carries the suite. Bounded at 900s inside # the 1200s suite budget so a hung probe is reported as a probe overrun with # the last line it printed, rather than as a suite timeout that says nothing # about where it stopped. # # The probe EXITS 0 even when the import fails, so this case passing means "the # probe ran", not "the module works" -- `import-ok' below is what carries that. bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" # pv : the value the probe emitted for , or empty. `head -n 1' # because a key is emitted once and a duplicate would otherwise concatenate; # `cut -f2-' because a value may legitimately contain `='. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # The probe reached its end. Every case below reads a value it printed, so a # probe that stopped early would otherwise turn into a run of empty-string # mismatches that all name the wrong thing. check_eq $P/probe-complete "yes" "$(pv probe_done)" _imp=$(pv import_ok) check_eq $P/import-ok "yes" "$_imp" if [ "$_imp" != "yes" ]; then # The module did not import. Skip -- not red -- every behavioural case, each # with the reason attached: they are all one fact, and a hundred reds would # bury it rather than report it. `import' and `import-ok' above are the two # reds that carry the verdict, and the design doc attributes them. _why="lazy_object_proxy did not import: $(pv import_error)" for _c in impl cext-not-importable-probe cext-error version-attr module-file \ all-names proxy-name has-simple \ lazy-calls-at-construction lazy-resolved-at-construction \ repr-unresolved-calls repr-unresolved-has-factory \ repr-unresolved-not-wrapping first-touch-len \ lazy-calls-after-one-touch lazy-resolved-after-touch \ lazy-calls-after-many-touches repr-resolved-says-wrapping \ second-proxy-calls-before second-proxy-calls-after \ raising-factory-constructs raising-factory-touch \ raising-factory-repr raising-factory-touch-again \ attr-get attr-get-class attr-method attr-missing attr-set \ attr-set-new attr-del attr-del-missing attr-hasattr-true \ attr-hasattr-false attr-dir-has-method wrapped-is-target \ factory-callable class-is-target class-name type-name \ type-is-not-target isinstance-target isinstance-object \ isinstance-int-false isinstance-int-true int-class \ isinstance-str str-class str-forwards repr-wraps-target-repr \ str-of-int-proxy bytes-forwards format-forwards \ op-add op-add-plain op-radd op-sub op-rsub op-mul op-rmul \ op-truediv op-floordiv op-mod op-divmod op-pow op-lshift \ op-rshift op-and op-or op-xor op-neg op-pos op-abs op-invert \ op-int op-float op-round op-index op-bool-true op-bool-false \ op-hash-matches op-str-concat op-str-rconcat op-iadd \ cmp-lt cmp-le cmp-gt cmp-ge cmp-eq-proxy cmp-eq-plain \ cmp-ne-plain cmp-plain-lt-proxy cmp-plain-eq-proxy \ len iter-list iter-sum getitem getitem-slice getitem-negative \ contains-true contains-false reversed getitem-out-of-range \ setitem delitem dict-getitem dict-keys dict-missing-key \ dict-len call-forwards call-kwargs context-manager \ reduce-shape pickle-roundtrip pickle-roundtrip-type \ copy-shallow deepcopy py-version py-maxsize-bits probe-count; do bst_skip $P/$_c "$_why" done exit 0 fi # ------------------------------------------------- which half is live --- # THE PORT'S CENTRAL CLAIM. `simple', not `cext' and not `other'. A build that # regained the accelerator fails HERE with the value that says so, rather than # somewhere downstream with a behavioural difference nobody can trace. check_eq $P/impl "simple" "$(pv impl)" check_eq $P/cext-not-importable-probe "no" "$(pv cext_importable)" check_eq $P/cext-error \ "ModuleNotFoundError: No module named 'lazy_object_proxy.cext'" \ "$(pv cext_error)" check_eq $P/version-attr "1.10.0" "$(pv version)" check_eq $P/module-file "$PKG/__init__.py" "$(pv module_file)" check_eq $P/all-names "Proxy" "$(pv all_names)" check_eq $P/proxy-name "Proxy" "$(pv proxy_name)" check_eq $P/has-simple "True" "$(pv has_simple)" # ---------------------------------------------------------------- laziness --- # ASSERTED BEFORE ANY FORWARDING, because it is the property the package # exists for. The factory increments a counter; the counter is the assertion. check_eq $P/lazy-calls-at-construction "0" "$(pv lazy_calls_at_construction)" check_eq $P/lazy-resolved-at-construction "False" "$(pv lazy_resolved_at_construction)" # `repr' READS A PROXY WITHOUT WAKING IT. Both implementations branch on # whether the target exists rather than reaching for it, so the counter is # still 0 afterwards and the text says `with factory' rather than `wrapping'. check_eq $P/repr-unresolved-calls "0" "$(pv repr_unresolved_calls)" check_eq $P/repr-unresolved-has-factory "True" "$(pv repr_unresolved_has_factory)" check_eq $P/repr-unresolved-not-wrapping "False" "$(pv repr_unresolved_says_wrapping)" # THE FIRST TOUCH RESOLVES, and every touch after it is free. check_eq $P/first-touch-len "3" "$(pv first_touch_len)" check_eq $P/lazy-calls-after-one-touch "1" "$(pv lazy_calls_after_one_touch)" check_eq $P/lazy-resolved-after-touch "True" "$(pv lazy_resolved_after_touch)" check_eq $P/lazy-calls-after-many-touches "1" "$(pv lazy_calls_after_many_touches)" check_eq $P/repr-resolved-says-wrapping "True" "$(pv repr_resolved_says_wrapping)" # LAZINESS IS PER-PROXY, not memoised on the factory: a second proxy over the # same callable calls it again. check_eq $P/second-proxy-calls-before "1" "$(pv second_proxy_calls_before)" check_eq $P/second-proxy-calls-after "2" "$(pv second_proxy_calls_after)" # A FACTORY THAT RAISES propagates AT FIRST TOUCH, not at construction, and it # propagates its OWN exception rather than something the proxy wrapped it in. # It raises again on the next touch: nothing caches a broken resolution. check_eq $P/raising-factory-constructs "yes" "$(pv raising_factory_constructs)" check_eq $P/raising-factory-touch "!ValueError: factory exploded" \ "$(pv raising_factory_touch)" check_eq $P/raising-factory-repr "True" "$(pv raising_factory_repr)" check_eq $P/raising-factory-touch-again "!ValueError: factory exploded" \ "$(pv raising_factory_touch_again)" # --------------------------------------------------------------- attributes --- check_eq $P/attr-get "original" "$(pv attr_get)" check_eq $P/attr-get-class "from-class" "$(pv attr_get_class)" check_eq $P/attr-method "42" "$(pv attr_method)" check_eq $P/attr-missing \ "!AttributeError: 'Target' object has no attribute 'nonexistent'" \ "$(pv attr_missing)" # A SET REACHES THE TARGET rather than shadowing on the proxy: both sides read # back changed. Same for an attribute the target did not have. check_eq $P/attr-set "changed|changed" "$(pv attr_set)" check_eq $P/attr-set-new "new-value|new-value" "$(pv attr_set_new)" # A DELETE REACHES THE TARGET: hasattr on the target answers False afterwards. check_eq $P/attr-del "False" "$(pv attr_del)" check_eq $P/attr-del-missing \ "!AttributeError: 'Target' object has no attribute 'never_existed'" \ "$(pv attr_del_missing)" check_eq $P/attr-hasattr-true "True" "$(pv attr_hasattr_true)" check_eq $P/attr-hasattr-false "False" "$(pv attr_hasattr_false)" check_eq $P/attr-dir-has-method "True" "$(pv attr_dir_has_method)" # `__wrapped__' and `__factory__' are the PROXY's own surface, not forwarded. check_eq $P/wrapped-is-target "True" "$(pv wrapped_is_target)" check_eq $P/factory-callable "True" "$(pv factory_callable)" # ------------------------------------------------- identity and __class__ --- # THE GAP BETWEEN `__class__' AND `type()' IS THE WHOLE TRICK, so both halves # are asserted: __class__ lies to make isinstance work, type() does not. check_eq $P/class-is-target "True" "$(pv class_is_target)" check_eq $P/class-name "Target" "$(pv class_name)" check_eq $P/type-name "Proxy" "$(pv type_name)" check_eq $P/type-is-not-target "True" "$(pv type_is_not_target)" check_eq $P/isinstance-target "True" "$(pv isinstance_target)" check_eq $P/isinstance-object "True" "$(pv isinstance_object)" check_eq $P/isinstance-int-false "False" "$(pv isinstance_int_false)" check_eq $P/isinstance-int-true "True" "$(pv isinstance_int_true)" check_eq $P/int-class "int" "$(pv int_class)" check_eq $P/isinstance-str "True" "$(pv isinstance_str)" check_eq $P/str-class "str" "$(pv str_class)" # ------------------------------------------------------- repr, str, bytes --- check_eq $P/str-forwards "target-str" "$(pv str_forwards)" check_eq $P/repr-wraps-target-repr "True" "$(pv repr_wraps_target_repr)" check_eq $P/str-of-int-proxy "42" "$(pv str_of_int_proxy)" check_eq $P/bytes-forwards "abc" "$(pv bytes_forwards)" check_eq $P/format-forwards "hello" "$(pv format_forwards)" # ---------------------------------------------------------------- operators --- # EVERY ARITHMETIC OPERATOR THE PACKAGE DOCUMENTS, proxy-to-proxy where that is # meaningful and proxy-to-plain and plain-to-proxy where the reflected slot is # a separate implementation -- a proxy that forwarded only the forward half # would pass a suite that asserted only `a + b'. check_eq $P/op-add "13" "$(pv op_add)" check_eq $P/op-add-plain "15" "$(pv op_add_plain)" check_eq $P/op-radd "15" "$(pv op_radd)" check_eq $P/op-sub "7" "$(pv op_sub)" check_eq $P/op-rsub "90" "$(pv op_rsub)" check_eq $P/op-mul "30" "$(pv op_mul)" check_eq $P/op-rmul "30" "$(pv op_rmul)" check_eq $P/op-truediv "3.3333333333333335" "$(pv op_truediv)" check_eq $P/op-floordiv "3" "$(pv op_floordiv)" check_eq $P/op-mod "1" "$(pv op_mod)" check_eq $P/op-divmod "(3, 1)" "$(pv op_divmod)" check_eq $P/op-pow "1000" "$(pv op_pow)" check_eq $P/op-lshift "80" "$(pv op_lshift)" check_eq $P/op-rshift "1" "$(pv op_rshift)" check_eq $P/op-and "2" "$(pv op_and)" check_eq $P/op-or "11" "$(pv op_or)" check_eq $P/op-xor "9" "$(pv op_xor)" check_eq $P/op-neg "-10" "$(pv op_neg)" check_eq $P/op-pos "10" "$(pv op_pos)" check_eq $P/op-abs "7" "$(pv op_abs)" check_eq $P/op-invert "-11" "$(pv op_invert)" check_eq $P/op-int "3" "$(pv op_int)" check_eq $P/op-float "10.0" "$(pv op_float)" check_eq $P/op-index "2" "$(pv op_index)" check_eq $P/op-bool-true "True" "$(pv op_bool_true)" check_eq $P/op-bool-false "False" "$(pv op_bool_false)" check_eq $P/op-hash-matches "True" "$(pv op_hash_matches)" check_eq $P/op-str-concat "hello world" "$(pv op_str_concat)" check_eq $P/op-str-rconcat "say hello" "$(pv op_str_rconcat)" # THE TWO CASES WHERE THE TWO IMPLEMENTATIONS DISAGREE, at their `simple' # values. `op-round': the C `__round__' takes no arguments and would answer # `!TypeError: Proxy.__round__() takes no arguments (1 given)' here. check_eq $P/op-round "3.57" "$(pv op_round)" # `op-iadd': `q += 5' rebinds to a plain int through `simple' and would answer # `15|Proxy' through `cext'. check_eq $P/op-iadd "15|int" "$(pv op_iadd)" # COMPARISON, each direction, so a missing reflected slot is caught. check_eq $P/cmp-lt "True" "$(pv cmp_lt)" check_eq $P/cmp-le "True" "$(pv cmp_le)" check_eq $P/cmp-gt "True" "$(pv cmp_gt)" check_eq $P/cmp-ge "True" "$(pv cmp_ge)" check_eq $P/cmp-eq-proxy "True" "$(pv cmp_eq_proxy)" check_eq $P/cmp-eq-plain "True" "$(pv cmp_eq_plain)" check_eq $P/cmp-ne-plain "True" "$(pv cmp_ne_plain)" check_eq $P/cmp-plain-lt-proxy "True" "$(pv cmp_plain_lt_proxy)" check_eq $P/cmp-plain-eq-proxy "True" "$(pv cmp_plain_eq_proxy)" # ------------------------------------------------ containers and the items --- check_eq $P/len "3" "$(pv len)" check_eq $P/iter-list "[10, 20, 30]" "$(pv iter_list)" check_eq $P/iter-sum "60" "$(pv iter_sum)" check_eq $P/getitem "20" "$(pv getitem)" check_eq $P/getitem-slice "[20, 30]" "$(pv getitem_slice)" check_eq $P/getitem-negative "30" "$(pv getitem_negative)" check_eq $P/contains-true "True" "$(pv contains_true)" check_eq $P/contains-false "False" "$(pv contains_false)" check_eq $P/reversed "[30, 20, 10]" "$(pv reversed)" # THE TARGET'S OWN EXCEPTIONS COME THROUGH UNCHANGED -- the proxy adds no # wrapping, which is what makes a proxied container substitutable. check_eq $P/getitem-out-of-range "!IndexError: list index out of range" \ "$(pv getitem_out_of_range)" check_eq $P/setitem "11" "$(pv setitem)" check_eq $P/delitem "[2, 3]" "$(pv delitem)" check_eq $P/dict-getitem "v" "$(pv dict_getitem)" check_eq $P/dict-keys "['k']" "$(pv dict_keys)" check_eq $P/dict-missing-key "!KeyError: 'absent'" "$(pv dict_missing_key)" check_eq $P/dict-len "1" "$(pv dict_len)" # ---------------------------------------------------------- call and context --- check_eq $P/call-forwards "42" "$(pv call_forwards)" check_eq $P/call-kwargs "42" "$(pv call_kwargs)" # `with Proxy(ctx)' must enter and exit the TARGET, and hand back what the # target's __enter__ returned rather than the proxy. check_eq $P/context-manager "ctx-value|enter,exit" "$(pv context_manager)" # ------------------------------------------------------------ copy / pickle --- # `__reduce__' returns (identity, (wrapped,)), so a round trip yields the # WRAPPED object rather than another proxy. That is upstream's documented # behaviour and the reason __init__.py registers `identity' with copyreg -- # without that registration pickle would refuse the callable. check_eq $P/reduce-shape "identity" "$(pv reduce_shape)" check_eq $P/pickle-roundtrip "[1, 2]" "$(pv pickle_roundtrip)" check_eq $P/pickle-roundtrip-type "list" "$(pv pickle_roundtrip_type)" check_eq $P/copy-shallow "[1, 2]" "$(pv copy_shallow)" check_eq $P/deepcopy "[1, 2]" "$(pv deepcopy)" # ----------------------------------------------------------------- the arm --- check_eq $P/py-version "3.12" "$(pv py_version)" # THE ONE CASE THAT SAYS THE TRANSCRIPT CAME FROM THE wasm32 INTERPRETER rather # than from a host copy of the module: every other value above reads the same # on either arm. 32 here, 64 on the x86-64 control. check_eq $P/py-maxsize-bits "32" "$(pv py_maxsize_bits)" # The probe emitted every key it was written to emit. A drift here means the # probe and this file went out of step, which would otherwise show as a run of # empty-string mismatches naming the wrong thing. check_eq $P/probe-count "122" "$(pv probe_count)" rm -rf "$W" 2>/dev/null