. "$BST_LIB" # BST-TIMEOUT: 1800 # the interpreter is one ~N-MB static image and the two # # comparison scripts each start it once more; on a loaded # # guest a start-up alone has been minutes, and the runner's # # 60s default would report the port as broken when what ran # # out was the clock. # # python3 3.12.13 (poky meta/recipes-devtools/python) on wasm32-hwjs -- ONE # STATIC INTERPRETER, and this suite is what says so out loud. # # HOW THE LANGUAGE CASES ARE PINNED. They are not pinned here at all. Two # scripts under $BST_DATA are run by the interpreter under test and their output # compared, line for line, against the SAME SCRIPTS' output on an x86-64 control # -- the Yocto native CPython 3.12.13 from this build's own sysroot, so every # library it linked is the version the target recipes build. Recorded # 2026-08-25. Nothing in either script may depend on pointer width, byte order, # the platform string, the clock, the filesystem or hash randomisation; anything # that legitimately differs between the arms is asserted BELOW instead, at its # wasm32 value, with the control's value written beside it. That division is # what makes "identical to x86-64" a real claim rather than a transcription of # whatever this arm happened to do. # # WHAT DIFFERS FROM THE CONTROL, and why each is a property of the arm: # # - sys.maxsize / pointer size: 2**31-1 and 4 here, 2**63-1 and 8 there. # - sys.int_info: 15 bits per digit and 2 bytes per digit here, 30 and 4 # there. CPython picks PYLONG_BITS_IN_DIGIT from SIZEOF_VOID_P, so this is # the same fact as the line above wearing different clothes. # - lib-dynload is EMPTY here and full of .so files there. configure selects # MODULE_BUILDTYPE=static from host_cpu=wasm32, so every extension module is # inside the binary. This is the single most load-bearing difference and it # gets its own case. # - _ctypes is absent here and present there. See the RED WITNESS below. # # THE CTYPES CASE IS A RED WITNESS: it FAILS IF THE IMPORT EVER SUCCEEDS. That # inversion is deliberate and it is the ticket's own requirement -- the boundary # must not drift silently. _ctypes is configured out because ffi_closure_alloc # CANNOT BE LINKED on this arm (hwjs-cc#15; libffi packages the name and wasmld # refuses any program that reaches it), and CPython reaches it unconditionally # outside Darwin. The recipe re-proves that refusal on every build. So the day # the platform defect is fixed, two things happen together: the recipe's probe # stops the build with instructions, and this case goes red. Either one is the # signal; both is belt and braces. # # NOT COVERED HERE, on purpose: the REPL. python3 has an interactive prompt and # it is not the deliverable -- this port closes on scripts. require python3 python3/present PY=/usr/bin/python3 SCRATCH=${TMPDIR:-/tmp}/bst-python3.$$ mkdir -p "$SCRATCH" 2>/dev/null # --------------------------------------------------------- the deliverable --- check python3/binary test -x $PY check python3/stdlib-dir test -d /usr/lib/python3.12 check python3/os-module test -f /usr/lib/python3.12/os.py check python3/sysconfigdata \ sh -c 'ls /usr/lib/python3.12/_sysconfigdata*.py >/dev/null 2>&1' # THE VERSION THE RECIPE PINS. poky builds Python-${PV} and the image installs # /usr/lib/python3.12, so a mismatch here means the two have gone out of step # and every path in this file is describing a different interpreter. check_eq python3/version "Python 3.12.13" "$(python3 -V 2>&1)" check_eq python3/version-tuple "3 12 13" \ "$(python3 -c 'import sys; print(*sys.version_info[:3])' 2>&1)" check_eq python3/implementation "cpython" \ "$(python3 -c 'import sys; print(sys.implementation.name)' 2>&1)" # --------------------------------------------------- the arm's own numbers --- # Each of these differs from the x86-64 control, which is why it is here rather # than in the compared scripts. The control's value is in the comment. check_eq python3/platform "linux" \ "$(python3 -c 'import sys; print(sys.platform)' 2>&1)" check_eq python3/maxsize "2147483647" \ "$(python3 -c 'import sys; print(sys.maxsize)' 2>&1)" # x86-64: 9223372036854775807 check_eq python3/pointer-size "4" \ "$(python3 -c 'import struct; print(struct.calcsize("P"))' 2>&1)" # x86-64: 8 # 30/4 on EVERY platform, and this row is not arm-specific. CPython 3.12 sets # the default in Include/pyport.h -- `#ifndef PYLONG_BITS_IN_DIGIT / #define # PYLONG_BITS_IN_DIGIT 30` -- with NO dependence on pointer size, and its own # comment says "defaulting to 30. The 15-bit digit option may be removed in the # future." configure leaves it undefined here (_sysconfigdata reports # PYLONG_BITS_IN_DIGIT: 0, SIZEOF_VOID_P: 4), so the header default stands. The # older rule that a 32-bit platform implies 15-bit digits is gone; expecting # "15 2" made this red against a correct build. check_eq python3/int-digits "30 4" \ "$(python3 -c 'import sys; print(sys.int_info.bits_per_digit, sys.int_info.sizeof_digit)' 2>&1)" # x86-64: same check_eq python3/soabi "cpython-312" \ "$(python3 -c 'import sysconfig; print(sysconfig.get_config_var("SOABI"))' 2>&1)" # x86-64: cpython-312-x86_64-linux-gnu check_eq python3/byteorder "little" \ "$(python3 -c 'import sys; print(sys.byteorder)' 2>&1)" check_eq python3/prefix "/usr /usr" \ "$(python3 -c 'import sys; print(sys.prefix, sys.exec_prefix)' 2>&1)" # THE BUILD CONFIGURATION, COMPARED AGAINST WHAT THE RECIPE PINS. Not decoration: # a shared build would put a libpython3.12.so on an image with no loader, and it # would be discovered as an import failure three tickets later. check_eq python3/not-shared "0" \ "$(python3 -c 'import sysconfig; print(sysconfig.get_config_var("Py_ENABLE_SHARED"))' 2>&1)" check_eq python3/no-shared-lib "" \ "$(ls /usr/lib/libpython3*.so* 2>/dev/null)" # LIBPL carries no platform suffix on this arm: configure appends # -${PLATFORM_TRIPLET} only when it resolved one, and the triplet probe answers # `none` here (its #error arm covers a wasm32 that is neither Emscripten nor # WASI). So the directory is config-3.12, not config-3.12- -- globbed # rather than pinned, because that is a poky-side name and not a claim of this # port. check python3/static-lib \ sh -c 'ls /usr/lib/python3.12/config-3.12*/libpython3.12.a >/dev/null 2>&1' # lib-dynload MUST BE EMPTY. This is the case that catches a poky which narrowed # configure's wasm32 case to Emscripten: every module would become a .so, the # directory would fill up, and this interpreter would import almost nothing. check_eq python3/lib-dynload-empty "" \ "$(ls -A /usr/lib/python3.12/lib-dynload 2>/dev/null)" # ...and the modules are consequently BUILTIN rather than merely importable. # sys.builtin_module_names is the interpreter's own answer, so a module that # somehow arrived as a file would not be counted. check_eq python3/builtin-modules "yes" \ "$(python3 -c ' import sys need = {"_json","_sre","_sqlite3","_ssl","_hashlib","_bz2","_lzma","zlib", "_curses","_uuid","pyexpat","_socket","select","_struct","math", "binascii","_datetime","_decimal","unicodedata","_csv","fcntl","termios"} missing = sorted(need - set(sys.builtin_module_names)) print("yes" if not missing else "missing:" + ",".join(missing))' 2>&1)" # THE SIZE OF THE ONE IMAGE, as the recipe measured it at link time. Asserted as # a range rather than a literal so an ordinary code-size drift does not go red, # and asserted at all because "one static binary" is a claim with a number # behind it: a build that silently produced a 2 MB stub, or a 400 MB image the # exec-image staging ceiling would refuse on a small guest, is the failure this # case exists to catch. The ceiling itself is a function of guest RAM # (BRINTOS-1474: 329 MiB at --ram 1024), so it is not a fixed bound to test. sz=$(cat /usr/libexec/python3-brintos/size.txt 2>/dev/null) # -L: $PY is /usr/bin/python3, a SYMLINK to python3.12. Without it `stat -c %s` # reports the LINK's own size -- 10, the length of "python3.12" -- and the case # compares that against the recorded 63467644 and goes red on a correct image. ondisk=$(stat -Lc %s $PY 2>/dev/null) if [ -z "$sz" ]; then bst_notok python3/size-recorded "the recipe's link-time size measurement is not installed" elif [ "$sz" -gt 4000000 ] && [ "$sz" -lt 200000000 ]; then bst_ok python3/size-recorded else bst_notok python3/size-recorded "linked size $sz bytes is outside the 4 MB..200 MB sanity range" fi check_eq python3/size-matches-installed "$sz" "$ondisk" # ------------------------------------------------- language, vs the control --- # One run of the script, then one case per area the ticket names. Grouping keeps # a red pointing at the area that failed instead of at a 113-line blob, and # keeps the case list stable when a case is added inside an area. CORE_OUT=$SCRATCH/core.out CORE_EXP=$BST_DATA/python3-core.expected ( python3 "$BST_DATA/python3-core.py" > "$CORE_OUT" 2>&1 ) core_rc=$? if [ $core_rc -ne 0 ]; then bst_notok python3/core-run "rc=$core_rc $(_bst_1line "$(cat "$CORE_OUT" 2>/dev/null)")" else bst_ok python3/core-run fi cmp_group() { # cmp_group check_eq "$1" \ "$(grep -E "^($2)[.=]" "$CORE_EXP" 2>/dev/null)" \ "$(grep -E "^($2)[.=]" "$CORE_OUT" 2>/dev/null)" } cmp_group python3/lang-arithmetic 'int|float' cmp_group python3/lang-strings 'str|uni' cmp_group python3/lang-containers 'dict|set|list|tuple|slice|bytes|memoryview' cmp_group python3/lang-exceptions 'exc' cmp_group python3/lang-closures 'closure' cmp_group python3/lang-generators 'gen|itertools' cmp_group python3/lang-classes 'cls|dataclass|enum|abc|property|functools' cmp_group python3/stdlib-json 'json' cmp_group python3/stdlib-re 're' cmp_group python3/stdlib-pure 'base64|hashlib|textwrap|collections|decimal|fractions|struct|binascii|string|difflib|copy|pickle|pprint|heapq|bisect|array|operator' cmp_group python3/interp-sys 'sys' # The count is its own case: a script that died halfway would otherwise match # every group it had already printed and report as a pass. cmp_group python3/core-case-count 'CASES' # ---------------------------------- native-dependency stdlib, vs the control -- # These are the modules the port exists to deliver: each is a C extension linked # into the interpreter against a library this image already carries. STD_OUT=$SCRATCH/stdlib.out STD_EXP=$BST_DATA/python3-stdlib.expected ( python3 "$BST_DATA/python3-stdlib.py" "$SCRATCH" > "$STD_OUT" 2>&1 ) std_rc=$? if [ $std_rc -ne 0 ]; then bst_notok python3/stdlib-run "rc=$std_rc $(_bst_1line "$(cat "$STD_OUT" 2>/dev/null)")" else bst_ok python3/stdlib-run fi std_group() { check_eq "python3/$1" \ "$(grep -E "^$2=" "$STD_EXP" 2>/dev/null)" \ "$(grep -E "^$2=" "$STD_OUT" 2>/dev/null)" } std_group mod-zlib 'zlib' std_group mod-bz2 'bz2' std_group mod-lzma 'lzma' std_group mod-hashlib 'hashlib' std_group mod-ssl 'ssl' std_group mod-sqlite3 'sqlite3' std_group mod-expat 'xml\.etree' std_group mod-uuid 'uuid' std_group mod-curses 'curses' std_group mod-fileio 'fileio' std_group mod-codecs 'codecs' std_group stdlib-case-count 'CASES' # THE VERSIONS THE MODULES REPORT, which the compared scripts deliberately do # not print. Pinned here against what this image's recipes build, so an # openssl/sqlite bump that silently dropped the module back to a bundled or # older copy is visible. check_out python3/ssl-openssl "OpenSSL 3." python3 -c 'import ssl; print(ssl.OPENSSL_VERSION)' check_out python3/expat-version "expat_" python3 -c 'import pyexpat; print("expat_%d.%d.%d" % pyexpat.version_info)' check python3/sqlite-version python3 -c ' import sqlite3, sys sys.exit(0 if sqlite3.sqlite_version_info >= (3, 40) else 1)' check python3/zlib-version python3 -c ' import zlib, sys sys.exit(0 if zlib.ZLIB_VERSION.startswith("1.") else 1)' # ------------------------------------------------------- the ctypes boundary -- # RED WITNESSES. Both cases FAIL IF THE BEHAVIOUR IMPROVES, which is the point: # _ctypes is excluded because ffi_closure_alloc cannot be linked on this arm # (hwjs-cc#15), and an exclusion nobody re-tests outlives its cause. # # The message is asserted in full rather than by exit status: `import ctypes` # failing for some OTHER reason -- a missing package, a broken stdlib -- would # also be non-zero, and would read as this documented boundary while being an # unrelated defect. check_eq python3/ctypes-refused \ "ModuleNotFoundError: No module named '_ctypes'" \ "$(python3 -c 'import ctypes' 2>&1 | tail -1)" # ...and the pure-Python half IS installed, which is what makes that message # name the C extension rather than the package. A stdlib module that probes for # ctypes in a try/except depends on this staying true. check python3/ctypes-package-present test -f /usr/lib/python3.12/ctypes/__init__.py # libffi itself is on the image and python3 does not link it: the archive is # BRINTOS-1475's deliverable, and this port consumes none of it. Asserted so # that a future build which quietly re-enabled _ctypes is visible from the # interpreter's own side as well as from the recipe's probe. check_eq python3/no-ctypes-builtin "absent" \ "$(python3 -c 'import sys; print("present" if "_ctypes" in sys.builtin_module_names else "absent")' 2>&1)" # ---------------------------------------------------------- threads boundary -- # ALSO A RED WITNESS, and not this port's defect: pthread_create does not produce # a running thread on this arm (linux-in-the-browser#85). The module imports, the # lock and thread-local machinery work, and a started thread never runs its body. # CPython creates no thread at start-up, so nothing above depends on this. check python3/threading-imports python3 -c 'import threading; threading.Lock()' check_eq python3/thread-start-refused "REFUSED" \ "$(python3 -c ' import threading done = [] t = threading.Thread(target=lambda: done.append(1)) try: t.start() except Exception: print("REFUSED"); raise SystemExit t.join(5) print("RAN" if done else "REFUSED")' 2>&1 | tail -1)" # ------------------------------------------------------------- runnable-ness -- # The interpreter as a user meets it: a script file, a module entry point, stdin, # and a non-zero exit that actually propagates. echo 'import sys; print("script-ok", len(sys.argv))' > "$SCRATCH/hello.py" check_out python3/run-script "script-ok 2" python3 "$SCRATCH/hello.py" arg1 check_out python3/run-module '{"a": 1}' python3 -c 'import json,sys; json.dump({"a":1}, sys.stdout)' check_out python3/run-stdin "stdin-ok" sh -c 'echo "print(\"stdin-ok\")" | python3 -' check_rc python3/exit-status 3 python3 -c 'raise SystemExit(3)' # An uncaught exception must print a traceback AND exit non-zero. check_out # requires rc=0, so the exit status is asserted separately and the text is read # from a wrapper that swallows it -- an uncaught traceback that exited 0 would # otherwise pass this file's exit-status case and never be looked at again. check_rc python3/traceback-exit-status 1 python3 -c '1/0' check_out python3/traceback-to-stderr "ZeroDivisionError: division by zero" \ sh -c 'python3 -c "1/0" 2>&1; exit 0' check_out python3/utf8-argv "é中" python3 -c 'import sys; print(sys.argv[1])' 'é中' check_out python3/compileall "compiled" python3 -c ' import py_compile, sys py_compile.compile(sys.argv[1], doraise=True); print("compiled")' "$SCRATCH/hello.py" rm -rf "$SCRATCH" 2>/dev/null