. "$BST_LIB" # BST-TIMEOUT: 1200 # ONE python3 start, and that is the whole cost. Everything # # below either reads the filesystem/dpkg with shell builtins # # or greps the single probe run's output; the probe writes # # twenty small templates to tmpfs and renders them, whose # # largest output is under 100 bytes, which is nothing beside # # the interpreter load. It matches 99-python3-dominate's and # # 99-python3-meld3's 1200 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-jinja2 3.1.6 (poky/meta recipes-devtools/python/python3-jinja2_3.1.6.bb, # `inherit pypi python_flit_core ptest', NO meta-brintos bbappend) on the # wasm32-hwjs glibc image -- THE TEMPLATE ENGINE MOST OF THE PYTHON WORLD RENDERS # TEXT WITH. A template is compiled to Python source once, cached, and executed # against a context. # # ============================================================================ # WHY THIS PORT IS POSSIBLE AT ALL, AND WHAT THE .so CASES ARE REALLY ASSERTING. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an EMPTY lib-dynload and no # dynamic loader (BRINTOS-1476; 99-python3 asserts both directly), and its # exported symbol table carries no Py* API at all (BRINTOS-2046). A module # carrying a compiled extension therefore could not be imported here -- not # "would be slow", could not be imported. jinja2 is 24 .py files and ships no # extension, which is the entire reason it ports, and BRINTOS-2046 does not reach # it. The wheel says so itself: `Root-Is-Purelib: true', tag `py3-none-any'. # # So `pkg-no-so' is not boilerplate. It is a REGRESSION TRIPWIRE on a version # bump: jinja2 grows no extension today, and the day a release adds a compiled # speedup this case goes red at INSTALL time rather than at `import jinja2' on # somebody else's ticket three rounds later. # # ============================================================================ # THE RDEPENDS ARE COMPLETE AS THE STOCK RECIPE SHIPS THEM -- MEASURED, NOT # ASSUMED. THIS PORT ADDS NO bbappend, AND THAT IS A FINDING. # ============================================================================ # # The sibling ports in this round each needed a bbappend because their upstream # recipe under-declared. jinja2's does NOT, and the check was run the same way: # the sys.modules delta across `import jinja2' from a pristine interpreter, taken # against the 3.1.6 sdist the recipe's SRC_URI sha256 pins, with every top-level # name mapped onto its OE split through poky's python3-manifest.json. # # Every top-level jinja2 itself reaches maps to a split the recipe declares: # ast/bisect/dis/inspect/opcode/token/tokenize/linecache/fnmatch/shutil/struct/ # weakref/zlib/bz2/lzma/errno/ipaddress -> python3-core; hashlib -> python3-crypt; # json -> python3-json; random -> python3-math; numbers -> python3-numbers; # pickle -> python3-pickle; tempfile -> python3-io; urllib -> python3-netclient; # markupsafe -> python3-markupsafe. The four splits the recipe declares that a # bare import does NOT reach -- asyncio, pprint, shell and threading -- are each # reached on a real lazy path (environment.py imports asyncio inside # render_async, utils.py takes pprint.pformat inside a function and # `from threading import Lock' at module scope), so the declaration is right and # over-broad rather than wrong. # # ONE NEAR-MISS IS RECORDED HERE BECAUSE IT IS NOT OURS, AND IT IS ALREADY # ANSWERED. `import jinja2' does pull in `string', whose OE split is # python3-stringold -- but the importer is markupsafe/__init__.py line 2, not # jinja2, so it is python3-markupsafe's Depends line the name belongs on and # BRINTOS-2093's to carry. It does carry it: that ticket's built # python3-markupsafe_2.1.5-r0_wasm32.deb reads `Depends: python3-core, # python3-html, python3-stringold'. So nothing is outstanding here -- written # down so the next reader does not re-derive the closure and file it against # jinja2. # # ============================================================================ # markupsafe IS THE ONE NON-STDLIB DEPENDENCY, AND AUTOESCAPING IS THE CONTRACT. # ============================================================================ # # python3-markupsafe lands in this same round (BRINTOS-2093) and is ordered ahead # of this ticket. It is a HARD dependency, not an optimisation: `import jinja2' # reaches it unconditionally through utils.py, and `Markup' is what lets a value # pass through an autoescaping environment unescaped. `r_escape_on' is the case # that states that contract -- the same context rendered by an autoescape=True # environment must escape the plain string and leave the Markup alone. # # markupsafe ships an OPTIONAL C speedup, and this image gets the pure-Python # path. That is read off the artifact rather than assumed: BRINTOS-2093's # python3-markupsafe_2.1.5-r0_wasm32.deb ships `_speedups.c' and `_native.py' # and NO .so at all, so `_native' is what answers here. `markupsafe_speedups' is # still emitted as evidence and DELIBERATELY NOT SCORED, because on the x86-64 # control the compiled speedup IS present -- an expected value would be a # statement about whichever machine last ran the probe. What is scored is the # behaviour both paths must produce. # # ============================================================================ # WHAT IS ASSERTED IS THE RENDERING, AGAINST A LITERAL. # ============================================================================ # # Every rendering case compares a whole string against a literal, with newlines # spelled \n and spaces spelled \s -- the .test file compares with shell # `test x = y', so a value carrying a raw newline would be truncated at the first # one and runs of indentation would be unreadable in a red. Both escapes are # reversible and are performed in the probe. # # The templates are written by the probe to a private tmpfs directory rather than # shipped as data. FileSystemLoader is the loader under test, so a fixture whose # bytes the probe writes is one these assertions can state exactly; it also keeps # the fixture off the virtiofs root, where a top-level stdin redirect would end # the suite (brintos/linux#73). # # ============================================================================ # EVERY VALUE BELOW WAS MEASURED, NOT PREDICTED. # ============================================================================ # # On x86-64 (CPython 3.12.3), against THIS EXACT PROBE and against THE BYTES THIS # RECIPE BUILDS -- the 3.1.6 sdist the recipe's SRC_URI sha256 pins # (0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d, verified by # sha256sum on the fetched tarball), unpacked and put on PYTHONPATH. Not a pip # copy and not upstream git. Recorded 2026-09-02; the ticket's design doc carries # the transcript. # # What makes that control transferable is that all of it is pure-Python and # content-determined: the strings jinja2 emits, the whitespace its lexer keeps or # drops, the order of a tojson dump and the line a syntax error is reported on # depend on jinja2's code and on the literals in the probe -- not on pointer # width, byte order, the platform string, the clock or hash randomisation. # Nothing in the probe sorts by hash, formats a float, reads the environment or # opens a socket. `tojson' is asserted with its keys in the order the probe's # dict literal fixes, which json.dumps preserves. # # THE ONE VALUE THE CONTROL COULD NOT MEASURE DIRECTLY is `dist-version'. The # host that ran the control carries its own jinja2 3.1.2 dist-info on sys.path, # so importlib.metadata answered 3.1.2 there while `jinja2.__version__' answered # 3.1.6 out of the sdist. On target exactly one dist-info exists, and it is the # one this recipe builds: `Version: 3.1.6', read directly out of # jinja2-3.1.6.dist-info/METADATA inside python3-jinja2_3.1.6-r0_wasm32.deb. So # the expected value is grounded in the shipped artifact rather than in the # control run, and `version' vs `dist-version' disagreeing is what a mixed # install would look like. # # ============================================================================ # WHAT IS OUT OF SCOPE. # ============================================================================ # # The recipe `inherit ptest' and DOES build a python3-jinja2-ptest package, but # this image does not install it -- `no-ptest-package' says so. The upstream # pytest suite is therefore not what runs here; this file is. # # The ASYNC rendering path (`enable_async', render_async) is not exercised. It is # what python3-asyncio is declared for, and asserting it would need an event loop # on this target -- a separate question from whether the template engine renders, # and one this port does not claim to have answered. # The runner's read drops the first line a suite emits, per-suite and # deterministic -- not a defect in the host tool (BRINTOS-1149, filed 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 # stands in that position so the eaten line costs nothing. It sits ahead of the # `require' guard 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. P=python3-jinja2 bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/jinja2 DI=$SITE/jinja2-3.1.6.dist-info PKGLIST=/var/lib/dpkg/info/python3-jinja2.list PROBE=$BST_DATA/python3-jinja2-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-jinja2.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import jinja2' 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 on sys.path is distinguishable from one that # imported out of what this image installed. check $P/site-packages-dir test -d "$SITE" # A DIRECTORY, NOT A MODULE. pyproject.toml ships a package tree, so a single # jinja2.py would be a release that flattened -- caught here, loudly, rather than # nowhere. check $P/package-dir test -d "$PKGDIR" check $P/no-module-py test ! -f "$SITE/jinja2.py" # THE SOURCE FILES, EACH BY NAME. These are the ones every case below runs # through -- environment holds Environment and Template, loaders the # FileSystemLoader, compiler the template-to-Python step, lexer the whitespace # control, runtime the loop object and the Undefined types, exceptions the three # error types, filters and tests the two extension points, sandbox the # `from string import Formatter' the header discusses. A wheel that dropped one # would still import and would fail somewhere far from the cause. check $P/src-init test -s "$PKGDIR/__init__.py" check $P/src-environment test -s "$PKGDIR/environment.py" check $P/src-loaders test -s "$PKGDIR/loaders.py" check $P/src-compiler test -s "$PKGDIR/compiler.py" check $P/src-lexer test -s "$PKGDIR/lexer.py" check $P/src-parser test -s "$PKGDIR/parser.py" check $P/src-runtime test -s "$PKGDIR/runtime.py" check $P/src-exceptions test -s "$PKGDIR/exceptions.py" check $P/src-filters test -s "$PKGDIR/filters.py" check $P/src-tests test -s "$PKGDIR/tests.py" check $P/src-sandbox test -s "$PKGDIR/sandbox.py" check $P/src-nodes test -s "$PKGDIR/nodes.py" check $P/src-utils test -s "$PKGDIR/utils.py" check $P/src-bccache test -s "$PKGDIR/bccache.py" check $P/src-ext test -s "$PKGDIR/ext.py" check $P/src-nativetypes test -s "$PKGDIR/nativetypes.py" # The inline type marker, which is a packaging fact rather than a runtime one: # flit ships it and a wheel that dropped it would silently stop type-checking for # every consumer. PEP 561 makes it a zero-byte marker, so presence is the whole # assertion -- `test -s' here can never pass on a correct build. check $P/src-py-typed test -f "$PKGDIR/py.typed" # The precompiled bytecode, which is what `import-quiet' below turns on. check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" # NO COMPILED EXTENSION. See the header -- this is the regression tripwire, not # boilerplate. # # 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 OR ITS METADATA. The case above # names the failure that is expected; this one catches the shape nobody predicted # -- a binary, a .pyd, a stray data file -- without having to enumerate what it # might be. dpkg's .list marks no directory with a trailing slash, so the entries # are sieved by asking the filesystem what each one is. _bad= while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */jinja2/py.typed) continue ;; */jinja2-3.1.6.dist-info/METADATA) continue ;; */jinja2-3.1.6.dist-info/RECORD) continue ;; */jinja2-3.1.6.dist-info/WHEEL) continue ;; */jinja2-3.1.6.dist-info/LICENSE.txt) continue ;; */jinja2-3.1.6.dist-info/entry_points.txt) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ------------------------------------------------------------ the dist-info -- # The installed distribution metadata, which is what importlib.metadata answers # `dist-version' out of in the probe. The directory name carries the version, so # a mixed install -- new sources beside an old dist-info -- shows up as the two # disagreeing rather than as neither being wrong. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -s "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" # PURE PYTHON, AS THE WHEEL ITSELF DECLARES IT. `Root-Is-Purelib: true' is the # packaging-side statement of the header's claim -- no platform tag, no extension # module, nothing that could need the absent dynamic loader. check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag "Tag: py3-none-any" cat "$DI/WHEEL" # jinja2 ships NO top_level.txt -- flit does not write one -- so the import name # is taken from METADATA's Name instead. A wheel that renamed the tree would # change it here first. check_out $P/dist-info-name "Name: Jinja2" cat "$DI/METADATA" # The one entry point the wheel declares: the Babel message extractor. It is # metadata only -- nothing on this image consumes it -- and it is asserted # because a flit packaging change would drop it silently. check_out $P/dist-info-entry-points "jinja2.ext:babel_extract" cat "$DI/entry_points.txt" # ------------------------------------------------------------- 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-jinja2 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-jinja2$/,/^$/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-version "Version: 3.1.6-r0" cat "$W/st" check_out $P/pkg-arch "Architecture: wasm32" cat "$W/st" # THE RUNTIME DEPENDENCIES, as dpkg recorded them. Unlike this round's siblings # there is no bbappend behind these -- the stock recipe declares all twelve and # the header records how that was verified. They are asserted by name anyway, # because the measurement that says they are right is a fact about 3.1.6, and a # version bump that dropped one would break the first rootfs that took the .deb # without the rest of this image around it. # # python3-markupsafe is the one that is not a python3 sub-package, and it is the # one that would actually be missing on a minimal rootfs. 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-markupsafe "python3-markupsafe" cat "$W/dep" check_out $P/pkg-depends-asyncio "python3-asyncio" cat "$W/dep" check_out $P/pkg-depends-crypt "python3-crypt" cat "$W/dep" check_out $P/pkg-depends-io "python3-io" cat "$W/dep" check_out $P/pkg-depends-json "python3-json" cat "$W/dep" check_out $P/pkg-depends-math "python3-math" cat "$W/dep" check_out $P/pkg-depends-netclient "python3-netclient" cat "$W/dep" check_out $P/pkg-depends-numbers "python3-numbers" cat "$W/dep" check_out $P/pkg-depends-pickle "python3-pickle" cat "$W/dep" check_out $P/pkg-depends-pprint "python3-pprint" cat "$W/dep" check_out $P/pkg-depends-shell "python3-shell" cat "$W/dep" check_out $P/pkg-depends-threading "python3-threading" cat "$W/dep" # NO -ptest PACKAGE ON THIS IMAGE. The recipe DOES `inherit ptest' and builds # one; this image simply does not install it, which is why the upstream pytest # suite is out of scope in the header. The case says the image took the runtime # package alone. check_eq $P/no-ptest-package "" \ "$(sed -n '/^Package: python3-jinja2-ptest$/p' /var/lib/dpkg/status 2>/dev/null)" # --------------------------------------------------------- the interpreter --- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If jinja2 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 jinja2' # AND markupsafe SEPARATELY, because it is the one dependency that is a real # package rather than a python3 split. A red here and a red on `import' together # mean the dependency; a red here alone cannot happen, and a red on `import' # alone points at jinja2 itself. check $P/import-markupsafe python3 -c 'import markupsafe' # ...AND THE IMPORT SAYS NOTHING WHILE DOING IT. This is a statement about the # shipped bytecode: the package ships precompiled .pyc files and OE writes them # with CHECKED-HASH invalidation, so each source is hashed and never recompiled. # A red here means a shipped .pyc was REJECTED -- a stale hash, a python3 minor # bump that changed the magic, or a missing __pycache__ -- and the tree is being # compiled from source on every import. # # Nothing in jinja2 has import-time output of its own: no logging configuration, # no warning, no print. check_eq $P/import-quiet "" \ "$(python3 -c 'import jinja2' 2>&1)" # ---------------------------------------------------------------- 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 # reading an empty file with no traceback to say why. 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 `version' distinguishable # from `dist_version', and `r_ws_none' from `r_ws_left'. 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. 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 VERSION THE RECIPE PINS, twice and independently: the package's own # `__version__', and importlib.metadata over the installed dist-info. The two # disagreeing means a mixed install. See the header for why the expected # dist-version is grounded in the .deb rather than in the x86-64 control. check_eq $P/version "3.1.6" "$(pv version)" check_eq $P/dist-version "3.1.6" "$(pv dist_version)" # The package answered out of the files 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 jinja2. check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/env-answered "$PKGDIR/environment.py" "$(pv env_file)" # Everything jinja2 loaded is .py -- the interpreter's own view of the header's # claim, complementing `pkg-no-so', which is the packaging view. check_eq $P/all-pure-python "yes" "$(pv jinja2_all_py)" # ------------------------------------------------------------------ the API --- # THE PUBLIC SURFACE, sorted. jinja2 3.1.6 declares no `__all__' -- __init__.py # re-exports each name with an explicit `as' alias -- so this is read off the # module. A release that moved or dropped a name still imports and breaks every # caller; this is the case that says so. The loaders, the Undefined policies and # the three exception types are all here, and each is used below. check_eq $P/api-surface \ "BaseLoader,BytecodeCache,ChainableUndefined,ChoiceLoader,DebugUndefined,DictLoader,Environment,FileSystemBytecodeCache,FileSystemLoader,FunctionLoader,MemcachedBytecodeCache,ModuleLoader,PackageLoader,PrefixLoader,StrictUndefined,Template,TemplateAssertionError,TemplateError,TemplateNotFound,TemplateRuntimeError,TemplateSyntaxError,TemplatesNotFound,Undefined,UndefinedError,clear_caches,is_undefined,make_logging_undefined,pass_context,pass_environment,pass_eval_context,select_autoescape" \ "$(pv api_surface)" # The submodules `import jinja2' pulls in, which is the import closure the header # maps onto OE splits. `sandbox' is deliberately ABSENT: filters.py imports it # only under TYPE_CHECKING, which is why `from string import Formatter' does not # run at import and why python3-stringold is markupsafe's business, not jinja2's. check_eq $P/api-submodules \ "async_utils,bccache,compiler,defaults,environment,exceptions,filters,idtracking,lexer,loaders,nodes,optimizer,parser,runtime,tests,utils,visitor" \ "$(pv api_submodules)" # The environment's defaults, which every render below depends on: the loader is # the one under test, and autoescape is OFF unless asked for -- the opposite of # what a caller coming from a web framework expects, and the reason the two # escape cases exist. check_eq $P/loader-kind "FileSystemLoader" "$(pv loader_kind)" check_eq $P/autoescape-default "False" "$(pv autoescape_default)" check_eq $P/autoescape-on "True" "$(pv autoescape_on)" # The loader actually walks the fixture tree -- so `r_*' below rendered the # templates the probe wrote, and a loader that silently found nothing is a red # here rather than twenty confusing reds further down. check_eq $P/loader-list \ "base.txt,broken.txt,caller.txt,cond.txt,escape.txt,filters.txt,import.txt,include.txt,leaf.txt,loop.txt,macro.txt,macros_lib.txt,middle.txt,partial.txt,set.txt,tests.txt,vars.txt,ws_block.txt,ws_minus_left.txt,ws_minus_right.txt" \ "$(pv loader_list)" # ------------------------------------------------------------ the rendering --- # VARIABLE SUBSTITUTION and the two lookup forms in one template: a plain name, # an ATTRIBUTE on an object, a SUBSCRIPT on a dict and a subscript on a list. # jinja2 tries getattr then getitem for `a.b' and the reverse for `a[b]', so a # regression in either order shows here. check_eq $P/render-vars "name=world\sattr=ayurtsev\sitem=vee\sidx=b" "$(pv r_vars)" # `for' WITH THE LOOP OBJECT: loop.index is 1-based and loop.last drives the # separator, so the absence of a trailing comma is the assertion. The `else' arm # is a distinct case because it runs only on an EMPTY sequence -- the branch a # caller most often has never exercised. check_eq $P/render-loop "1:a,2:b,3:c" "$(pv r_loop)" check_eq $P/render-loop-empty "EMPTY" "$(pv r_loop_empty)" # if / elif / else, all three arms of the same template driven by one value. # Asserting only the taken branch would pass on a template that always took it. check_eq $P/render-cond-big "big" "$(pv r_cond_big)" check_eq $P/render-cond-mid "mid" "$(pv r_cond_mid)" check_eq $P/render-cond-small "small" "$(pv r_cond_small)" # `set' binds a name inside the template, and the bound name flows into a filter # -- so this covers the assignment and that the result is a normal value. check_eq $P/render-set "hi\syou|HI\sYOU" "$(pv r_set)" # THE FILTERS THE TICKET NAMES, one per line so a red names the filter. # `default' fires because `missing' is undefined; `tojson' is asserted with the # key order the probe's dict literal fixes, which json.dumps preserves -- and it # is the filter that would break first if python3-json were not installed. check_eq $P/render-filters \ "default=fallback\njoin=a-b-c\nlength=3\ntojson={\"a\":\s[1,\snull,\strue],\s\"b\":\s2}" \ "$(pv r_filters)" # THE TESTS THE TICKET NAMES. `defined'/`is not defined' are asserted in both # directions, and `divisibleby' both ways on the same number, so a test stuck at # one answer fails rather than passes half the cases. check_eq $P/render-tests "defined=yes\nundefined=yes\ndiv3=yes\ndiv4=no" "$(pv r_tests)" # THE THREE WHITESPACE-CONTROL FORMS, each isolated, each rendering to a # DIFFERENT string -- so a red names which form moved rather than only that one # did. `render-ws-none' is the control: the same template as `render-ws-trim' # with the default environment, keeping every newline the block tags sit on. # none -- nothing stripped, blank lines survive # left -- `{%-' eats the whitespace BEFORE the tag, so `A' and `[a]' join up # right -- `-%}' eats the whitespace AFTER the tag, so the indent goes and the # newline after `A' stays # trim -- trim_blocks + lstrip_blocks on the environment, the form that needs # no markup in the template at all check_eq $P/render-ws-none "A\n\n[a]\n\n[b]\n\n[c]\n\nB" "$(pv r_ws_none)" check_eq $P/render-ws-left "A[a][b][c]\nB" "$(pv r_ws_left)" check_eq $P/render-ws-right "A\n[a][b][c]\nB" "$(pv r_ws_right)" check_eq $P/render-ws-trim "A\n[a]\n[b]\n[c]\nB" "$(pv r_ws_trim)" # TEMPLATE INHERITANCE ACROSS THREE LEVELS, with super() at each. Rendering the # base alone is the control; middle wraps the base's block through super(); leaf # wraps middle's. The nesting `leaf(mid(base-body))' is the assertion -- a # super() that resolved to the wrong ancestor would still render and would give # a different nesting. check_eq $P/render-base "[base-head]\nbase-body\n[base-foot]" "$(pv r_base)" check_eq $P/render-middle "[base-head]\nmid(base-body)\n[base-foot]" "$(pv r_middle)" check_eq $P/render-leaf "[base-head]\nleaf(mid(base-body))\n[base-foot]" "$(pv r_leaf)" # MACROS: one call passes the optional argument and one falls back to the # DEFAULT, in a single rendering, so the default is asserted against the # explicit value rather than on its own. `caller' is the other macro form -- the # `{% call %}' block whose body the macro invokes through caller(). check_eq $P/render-macro "" "$(pv r_macro)" check_eq $P/render-caller "

inner

" "$(pv r_caller)" # `include' and `import' -- the two ways one template reaches another, and the # cases that say FileSystemLoader resolves a name from inside a render rather # than only from get_template(). `import' binds a macro namespace; `include' # splices a rendered partial and inherits the context. check_eq $P/render-include "[partial\s7]" "$(pv r_include)" check_eq $P/render-import "OK!" "$(pv r_import)" # AUTOESCAPING, BOTH WAYS, WITH THE SAME CONTEXT -- the contract between jinja2 # and markupsafe, and the reason python3-markupsafe is a hard dependency. # # With autoescape OFF both values pass through verbatim, which is what makes the # ON case a statement about the policy rather than about the template. # With it ON the plain string is escaped and the Markup value is NOT -- that # asymmetry is the whole point: it is how a template composes trusted HTML # without disabling escaping globally. check_eq $P/render-escape-off "raw=&\smarkup=ok" "$(pv r_escape_off)" check_eq $P/render-escape-on "raw=<b>&</b>\smarkup=ok" "$(pv r_escape_on)" # A STRING TEMPLATE, with no loader at all. This separates a failure of the # LOADER from a failure of the compiler: if every case above red and this one # passed, the template source never reached the compiler. check_eq $P/render-string-template "2+3=5" "$(pv r_string_template)" # THE COMPILE CACHE. jinja2 compiles a template to Python once and caches the # code object; `cache-identity' says the second get_template returned the SAME # object rather than recompiling, and `render-stable' says two renders of it # agree byte for byte. A cache that silently recompiled would still be correct # and would be a performance regression nothing else here would catch. check_eq $P/render-stable "same" "$(pv r_cache_stable)" check_eq $P/cache-identity "same-object" "$(pv cache_identity)" # ------------------------------------------------------------ the error paths -- # Each error is asserted by TYPE, and the two that carry a locatable fact also # carry it, so a red distinguishes "wrong exception" from "right exception, # wrong information". # # TemplateNotFound names the template it could not find -- a loader that raised # it with an empty name would still be catchable and would be useless. check_eq $P/error-not-found "TemplateNotFound:no-such-template.txt" "$(pv e_not_found)" # TemplateSyntaxError carries the LINE, and the fixture puts the malformed tag on # line 3 deliberately -- so this asserts a number the fixture fixes rather than # whatever the parser happened to say. An off-by-one in the lexer's line # accounting is exactly the regression this catches. check_eq $P/error-syntax-lineno "TemplateSyntaxError:3:broken.txt" "$(pv e_syntax)" # UndefinedError only under StrictUndefined... check_eq $P/error-strict-undefined "UndefinedError" "$(pv e_strict_undefined)" # ...and NOT under the default policy, which renders an undefined name as empty. # This is the control that makes the case above a statement about the POLICY # rather than about jinja2 raising on everything. check_eq $P/default-undefined-renders "yes" "$(pv e_default_undefined_renders)" # The hierarchy callers actually catch on: `except TemplateError' has to cover # all three, and a release that reparented one would break error handling # everywhere without breaking a single render. check_eq $P/exception-hierarchy \ "TemplateNotFound