. "$BST_LIB" # BST-TIMEOUT: 900 # this suite starts the interpreter ONCE, and that one # # start-up is the whole cost: python3 on this image is a # # single ~63 MB static binary and a start has been minutes # # on a loaded guest, so the runner's 60s default would # # report the port as broken when what ran out was the # # clock. # # python3-pygments 2.17.2 (poky meta/recipes-devtools/python, `inherit pypi # python_hatchling') on wasm32-hwjs -- the syntax-highlighting library and its # pygmentize driver, several hundred lexers and formatters, all pure Python and # all offline. # # WHY PURENESS IS THE LOAD-BEARING PROPERTY, not a footnote. This image's python3 # is one static binary: lib-dynload is empty by construction and this arm has no # dynamic loader at all (BRINTOS-1476), so nothing on this image can import an # out-of-tree .so. A version bump that grew a compiled speedup would not fail at # build time -- it would install into site-packages and die at `import pygments' # on a user's target. The no-.so case below asserts that negative directly, and # the empty lib-dynload beside it is the MECHANISM, so a reader can see why the # negative matters without leaving the file. The 2.17.2 sdist (sha256 # da46cec9..., the recipe's own SRC_URI checksum) ships zero .c and zero .pyx, # and the built package carries zero .so across its 667 entries -- both measured # 2026-08-31, not assumed. # # EVERYTHING RUNS IN ONE INTERPRETER, and that is the load-bearing constraint # rather than a tidy-up. The guest does not return memory on process exit # (brintos/hardwarejs#20, brintos/linux#21), so every interpreter start is a # permanent cost to it. The five pygmentize runs this suite asserts would be five # starts, each a shebang re-exec of that static binary, plus a sixth for the API # probe. A sibling port in this same round (BRINTOS-1751, python3-docutils) ran # exactly that many and grew the host guest process at 7788 MiB/min until the # harness ended it as a runaway -- and the suite emitted NO case at all, because # the runner buffers a suite's output and prints it only on completion. One start # is the shape every python3 suite on this image has converged on. # # WHAT THE PROBE REPLICATES, EXACTLY. /usr/bin/pygmentize is a console-script # wrapper whose entire body is `sys.exit(main())` against pygments.cmdline -- # read out of the built package, not inferred -- so data/python3-pygments-probe.py # calls pygments.cmdline.main(['pygmentize', ...]) once per case in a single # start. That is the driver's own option parsing, lexer guessing, formatter # selection and exit status; nothing here reimplements a path the driver takes. # The driver is still asserted to EXIST as an executable. What is no longer done # is running it as a process, which is the cost that kills the guest. # # EVERY NEEDLE BELOW WAS MEASURED, on an x86-64 control (CPython 3.10.12, # pygments 2.17.2 sdist sha256 da46cec9..., recorded 2026-08-31) through this # same probe over this same fixture. Highlighting is deterministic and # file-driven: nothing asserted depends on pointer width, byte order, the # platform string, the clock or hash randomisation, which is what lets # "identical to x86-64" be a claim rather than a transcription of whatever this # arm happened to do. # # THE CATALOGUE COUNTS ARE PINNED, AND THEY ARE PIL-INDEPENDENT. 575 lexers and # 18 formatters. The formatter registry includes img/bmp/gif, whose module # imports PIL inside a try/except and registers the class either way -- so the # count does not move when PIL is absent, which is the case on this image. That # was measured both ways on the control (PIL present, and PIL blocked at # sys.meta_path) rather than reasoned about, because a count that quietly # depended on an optional dependency would fail on target for a reason having # nothing to do with the port. # # NOT COVERED HERE, on purpose: the sdist's own tests/ tree, which # python_hatchling does not install and which is not on the image; and the image # formatters, which cannot produce output without PIL and whose absence is a # property of the image rather than of this port. require python3 python3-pygments/present SITE=/usr/lib/python3.12/site-packages SCRATCH=${TMPDIR:-/tmp}/bst-python3-pygments.$$ mkdir -p "$SCRATCH" 2>/dev/null # --------------------------------------------------------- the deliverable --- # The package as files, not as an import, so a red separates "not installed" # from "installed and does not work". One file per subtree the cases below # reach: a partial install that dropped `lexers' would still satisfy # `import pygments'. check python3-pygments/package-dir test -d "$SITE/pygments" check python3-pygments/init-module test -f "$SITE/pygments/__init__.py" check python3-pygments/cmdline-module test -f "$SITE/pygments/cmdline.py" check python3-pygments/lexers-mapping test -f "$SITE/pygments/lexers/_mapping.py" check python3-pygments/lexer-c test -f "$SITE/pygments/lexers/c_cpp.py" check python3-pygments/lexer-python test -f "$SITE/pygments/lexers/python.py" check python3-pygments/formatters-mapping test -f "$SITE/pygments/formatters/_mapping.py" check python3-pygments/formatter-html test -f "$SITE/pygments/formatters/html.py" check python3-pygments/formatter-terminal test -f "$SITE/pygments/formatters/terminal.py" check python3-pygments/styles-dir test -d "$SITE/pygments/styles" # The driver, as an executable. It is not RUN as a process below -- see the # header -- but its absence is a packaging failure this suite must still catch. check python3-pygments/pygmentize-exec test -x /usr/bin/pygmentize # THE NO-.so ASSERTION, and the mechanism that makes it matter. The first case is # the ticket's requirement: nothing pygments owns may be a compiled extension. # The second is why -- lib-dynload is empty on this image, so there is nowhere an # extension could be loaded from even if one shipped. # # Both are `find' over a directory, so both would pass VACUOUSLY against a # directory that is not there -- the one way this pair could report a clean port # on an image carrying no python3 at all. Each is preceded by the existence case # that makes its emptiness mean something. check python3-pygments/site-packages-dir test -d "$SITE" check_eq python3-pygments/no-so "" \ "$(find "$SITE/pygments" -name '*.so' 2>/dev/null)" check python3-pygments/lib-dynload-dir test -d /usr/lib/python3.12/lib-dynload check_eq python3-pygments/lib-dynload-empty "" \ "$(find /usr/lib/python3.12/lib-dynload -name '*.so' 2>/dev/null)" # ------------------------------------------------------------- the probe run --- # One interpreter start, forked, so a failure to stage the exec costs this file # its cases rather than the run its terminator. The probe writes one capture per # pygmentize invocation plus the API scalars; every case below reads those # captures with `cat'. FIX=$BST_DATA/python3-pygments-fixture.c HTML=$SCRATCH/c.html TERM=$SCRATCH/c.term PYLX=$SCRATCH/c.pylexer ( python3 "$BST_DATA/python3-pygments-probe.py" "$SCRATCH" "$FIX" \ > "$SCRATCH/probe.out" 2>&1 ); probe_rc=$? if [ "$probe_rc" -ne 0 ]; then bst_notok python3-pygments/probe-run \ "rc=$probe_rc $(_bst_1line "$(cat "$SCRATCH/probe.out" 2>/dev/null)")" else bst_ok python3-pygments/probe-run fi # The module resolves to the package the recipe installed, from the # interpreter's own side. `import pygments' succeeding from some other path -- a # stray copy, a namespace package -- would satisfy every case below while the # port had installed nothing. Read out of the probe's own start rather than a # second one. check_eq python3-pygments/import-path "$SITE/pygments/__init__.py" \ "$(cat "$SCRATCH/import-path" 2>/dev/null)" check_eq python3-pygments/version 2.17.2 "$(cat "$SCRATCH/version" 2>/dev/null)" # Exit status and stderr, per pygmentize invocation. The silent cases are not # decoration: pygments diagnoses an unknown lexer or formatter on stderr, so a # capture that had quietly fallen back would still carry output and only these # cases would say so. check_eq python3-pygments/html-rc 0 "$(cat "$SCRATCH/html.rc")" check_eq python3-pygments/html-silent "" "$(cat "$SCRATCH/html.err")" check_eq python3-pygments/term-rc 0 "$(cat "$SCRATCH/term.rc")" check_eq python3-pygments/term-silent "" "$(cat "$SCRATCH/term.err")" check_eq python3-pygments/pylexer-rc 0 "$(cat "$SCRATCH/pylexer.rc")" check_eq python3-pygments/pylexer-silent "" "$(cat "$SCRATCH/pylexer.err")" check_eq python3-pygments/lexlist-rc 0 "$(cat "$SCRATCH/lexlist.rc")" check_eq python3-pygments/lexlist-silent "" "$(cat "$SCRATCH/lexlist.err")" check_eq python3-pygments/fmtlist-rc 0 "$(cat "$SCRATCH/fmtlist.rc")" check_eq python3-pygments/fmtlist-silent "" "$(cat "$SCRATCH/fmtlist.err")" # ------------------------------------------------- -f html, lexer GUESSED --- # One needle per token class the fixture carries, which is what the ticket names: # the keyword, the string and the comment, plus the type, the function name, the # preprocessor line and the integer literal that come with them. Each is the # HtmlFormatter's short CSS class, so a lexer that tokenised the file as one # undifferentiated blob would fail every one of them rather than pass on the # wrapper alone -- which is why the wrapper is asserted separately, first. check_out python3-pygments/html-wrapper '
' cat "$HTML"
check_out python3-pygments/html-comment '/* A brintOS syntax-highlighting fixture' cat "$HTML"
check_out python3-pygments/html-keyword 'while' cat "$HTML"
check_out python3-pygments/html-keyword-const 'const' cat "$HTML"
check_out python3-pygments/html-string '"hello from wasm32"' cat "$HTML"
check_out python3-pygments/html-type 'int' cat "$HTML"
check_out python3-pygments/html-funcname 'main' cat "$HTML"
check_out python3-pygments/html-preproc '#include' cat "$HTML"
check_out python3-pygments/html-number '0' cat "$HTML"
# ------------------------------------------- -f terminal, the SAME input ---
# ANSI escapes rather than markup, which is the whole of what changes when only
# the formatter changes. The escape byte is built with printf -- a literal ESC in
# a .test file does not survive review or an editor -- and the colours are the
# TerminalFormatter's own defaults as measured: 34 for a keyword, 33 for a
# string, 37 for a comment, 36 for a type, and 39;49;00 to reset.
ESC=$(printf '\033')
check_out python3-pygments/term-keyword "$ESC[34mwhile$ESC[39;49;00m" cat "$TERM"
check_out python3-pygments/term-string "$ESC[33mhello from wasm32$ESC[39;49;00m" cat "$TERM"
check_out python3-pygments/term-comment "$ESC[37m/* A brintOS syntax-highlighting fixture" cat "$TERM"
check_out python3-pygments/term-type "$ESC[36mint$ESC[39;49;00m" cat "$TERM"
# The terminal capture is NOT html. Without this, a formatter selection that
# silently fell back to the html default would satisfy every escape case above
# only by failing them -- and this says which way it went, in one case, rather
# than leaving four reds to be read as a lexer fault.
check_eq python3-pygments/term-not-html "" "$(grep -l '/dev/null)"
# ------------------------------------ -l python -f html, the guess OVERRIDDEN ---
# The same C input through the Python lexer. This is a real assertion rather than
# a second run of the first only because the two tokenisations genuinely differ,
# and the pair below is where they differ MOST: the Python lexer reads `#include'
# as a line comment (c1) and emits no C block comment (cm) anywhere, so a run
# that had ignored -l and guessed C again fails both.
check_out python3-pygments/pylexer-linecomment '#include' cat "$PYLX"
check_eq python3-pygments/pylexer-no-c-comment "" \
"$(grep -l 'class="cm"' "$PYLX" 2>/dev/null)"
check_out python3-pygments/pylexer-builtin 'int' cat "$PYLX"
# ------------------------------------------------------------ the catalogue ---
# -L lexers and -L formatters, which is the driver's own listing path. The
# entries asserted are exactly the ones the runs above used, so a catalogue that
# listed a non-empty set NOT containing them would fail here rather than pass on
# a line count.
check_out python3-pygments/lexlist-header 'Pygments version 2.17.2' cat "$SCRATCH/lexers.list"
check_out python3-pygments/lexlist-c '* c:' cat "$SCRATCH/lexers.list"
check_out python3-pygments/lexlist-c-filenames 'C (filenames *.c, *.h,' cat "$SCRATCH/lexers.list"
check_out python3-pygments/lexlist-python '* python, py, sage, python3,' cat "$SCRATCH/lexers.list"
check_out python3-pygments/fmtlist-header 'Formatters:' cat "$SCRATCH/formatters.list"
check_out python3-pygments/fmtlist-html '* html:' cat "$SCRATCH/formatters.list"
check_out python3-pygments/fmtlist-terminal '* terminal, console:' cat "$SCRATCH/formatters.list"
# THE CATALOGUE SIZE, which is what separates a real install from a stub that
# imported. Taken from the API rather than by counting the listing's lines: the
# listing's layout is presentation and could change without the catalogue moving
# at all. Two cases per registry on purpose -- the RANGE is the stub guard and
# survives a version bump, the EXACT value is legitimate because the recipe pins
# 2.17.2 and it is the one that catches a partial install of the catalogue
# itself, which a range wide enough to be useful would let through.
LEXN=$(cat "$SCRATCH/lexer-count" 2>/dev/null)
FMTN=$(cat "$SCRATCH/formatter-count" 2>/dev/null)
check python3-pygments/lexer-count-range test "${LEXN:-0}" -ge 500
check_eq python3-pygments/lexer-count-exact 575 "$LEXN"
check python3-pygments/formatter-count-range test "${FMTN:-0}" -ge 15
check_eq python3-pygments/formatter-count-exact 18 "$FMTN"
# ------------------------------------------------- filename-driven selection ---
# guess_lexer_for_filename, the API half of what `-f html' did implicitly above.
# Both a .c and a .py name, so a resolver that returned one fixed lexer for
# everything fails rather than passing by luck on the case the ticket names.
check_eq python3-pygments/guess-c C "$(cat "$SCRATCH/guess-c")"
check_eq python3-pygments/guess-py Python "$(cat "$SCRATCH/guess-py")"
check_eq python3-pygments/lexer-by-name Python "$(cat "$SCRATCH/lexer-by-name")"
rm -rf "$SCRATCH" 2>/dev/null
exit 0