. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. Every # # other assertion below reads the filesystem or dpkg with # # shell builtins, or greps the output of one of those three. # # # # They are a bare `import', a second one whose STDERR is # # the assertion (`import-quiet'), and the probe -- which # # takes ONE start for seventy-eight measurements. The work # # inside the probe is ElementTree parsing and string # # serialisation over a fixture under 1 kB: microseconds # # beside an interpreter start on this arm, which is tens of # # seconds before the first line of the program runs. # # # # 1200 is 400s per start, the budget 99-python3-aniso8601 # # and 99-python3-parse set for their three, and roughly six # # times the ~60s a python3 start has measured at here. # # # # A suite-timeout red is a batch-wide cost, so if the three # # 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-meld3 2.0.1 (meta-openembedded/meta-python # recipes-devtools/python/python3-meld3_2.0.1.bb, `inherit pypi setuptools3', # PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # An HTML/XML templating system that keeps markup and rendering logic apart. # The template is well-formed markup carrying no logic at all -- no loops, no # conditionals, no substitution syntax -- only `meld:id' attributes naming the # nodes a program may touch. The program parses it, finds those nodes by id, # and fills, repeats or removes them. A designer can open the template in a # browser, because it is just a document. # # The whole API is FIVE METHODS on an element -- findmeld, repeat, content, # attributes, deparent -- plus the writers. This suite asserts each of them # against a fixture it carries, and then asserts the rendered document against # a literal for both the HTML and the XML writer. # # ============================================================================ # THE PACKAGE IS PURE PYTHON, WHICH IS WHY IT PORTS AT ALL. # ============================================================================ # # It ships no compiled extension. This image's python3 is ONE STATIC BINARY # with an empty lib-dynload and no dynamic loader (BRINTOS-1476), so a module # carrying a `.so' could not be imported here whatever it built to -- that is # BRINTOS-2046, and it does not reach this package. `pkg-no-so' and # `pkg-only-python' assert the negative directly off the packaged file list, # and `closure-nonstd' asserts the import reaches nothing outside the standard # library. # # The XML side does rest on a compiled module, and it is a BUILT-IN one rather # than a loadable one: `pyexpat' and `_elementtree' are linked INTO this # interpreter because python3_%.bbappend forces MODULE_BUILDTYPE=static for # wasm32. That is why `xml.etree' works here at all, and the stdlib census # (data/python3-stdlib.expected, `xml.etree=') already measures it on target. # # ============================================================================ # THE DECLARED DEPENDENCIES WERE WRONG IN BOTH DIRECTIONS, AND THIS SUITE # ASSERTS THE CORRECTED LINE BY NAME. # ============================================================================ # # meta-python declares `python3-cgitb' and `python3-xml'. Measured against the # real import closure (see python3-meld3_%.bbappend, which carries the numbers): # # - `email' is imported AT MODULE SCOPE, line 1 of __init__.py, and used at # line 802 to parse an http-equiv meta header. It is its own split and was # NOT declared -- so the recipe named no dependency for a module the import # cannot proceed without. python3-meld3_%.bbappend adds python3-email. # - `html.parser' and `html.entities' are imported at module scope by # _compat.py. Also undeclared -- and reached today only BY ACCIDENT, # because the python3-cgitb split happens to rdepend on html. The bbappend # declares python3-html directly rather than resting on that. # - `cgitb' IS DECLARED AND IS NOT REFERENCED ANYWHERE IN THE SOURCE. It is # kept anyway; the bbappend says why at length. `pkg-depends-cgitb' # asserts it is still on the line, so a later removal is a deliberate act. # # `pkg-depends-*' asserts all five names. The point is the LINE, not the image: # every one of these splits is already installed here for other reasons, so # none of this changes the rootfs -- it makes the package correct. # # ============================================================================ # THE meld:id ATTRIBUTE SURVIVES PARSING. THE WRITERS DROP IT. # ============================================================================ # # It is natural to assume the parser consumes the annotation. It does not: the # element's attrib still carries it, under its full namespaced name # `{http://www.plope.com/software/meld3}id', which is why a caller iterating # attrib sees a key it never wrote. What leaves it out is the SERIALISER. # # Both halves are asserted rather than either: `meldid-in-attrib' for the tree, # and every `render-*' literal for the output. # # ============================================================================ # repeat() RETURNS THE TEMPLATE ELEMENT ITSELF AS THE FIRST PAIR. # ============================================================================ # # `row.repeat(["a","b","c"])' gives three pairs, and the first one's element IS # the row that was already in the tree -- not a copy of it. Only the second and # later are clones. A caller who believes it gets three fresh nodes is wrong # about one of them, mutates the template, and NOTHING RAISES. # # `repeat-first-is-template' asserts the identity and `repeat-later-are-clones' # asserts the complement, so the case says what it means rather than implying # it through a count. # # The empty iterable is the other trap and gets two cases of its own: # `repeat([])' yields nothing AND LEAVES THE TEMPLATE ROW IN THE TREE # (`repeat-empty-leaves-row'), so a table a caller meant to empty still renders # one row. That is the documented behaviour, not a defect, and it is the one # most likely to be met in production. # # ============================================================================ # content() ESCAPES; content(..., structure=True) DOES NOT. # ============================================================================ # # This pair is the library's entire answer to "how do I put markup in", and # getting it backwards is an injection. Both are asserted against rendered # literals -- `content-escaped' and `content-structure' -- rather than against # a property, because the exact escaping is the thing at issue. # # And the escaping is NARROWER THAN A READER EXPECTS: in character data meld3 # escapes `&' and `<' and leaves `>' and `"' alone. `a&"c' renders as # `a<b>&"c', with the `>' and the `"' untouched. That is correct XML -- # neither needs escaping in cdata -- but it is not what someone diffing against # a naive escaper will predict, so the literal is asserted exactly. # # content() also DELETES the node's existing children rather than appending # beside them (`content-drops-children', `content-row-gone'). # # ============================================================================ # attributes() SETS. IT HAS NO REMOVAL ARM AT ALL. # ============================================================================ # # The method body is a loop that assigns `self.attrib[k] = v' and nothing else. # There is no spelling of attributes() that removes an attribute: a caller # reaching for `attributes(class=None)' gets a ValueError, because the method # type-checks both key and value and None is not a string # (`attrib-set-nonstring-val'). Removal goes through the attrib mapping # directly -- `del el.attrib["class"]' -- and `attrib-after-del' plus # `attrib-rendered' assert that path end to end. # # The boundary is asserted rather than assumed because the ticket that opened # this port described attributes() as setting AND removing. It does not, in # 2.0.1, and a suite asserting the ticket rather than the code would have been # red on a package that is behaving correctly. # # ============================================================================ # THE TWO WRITERS DIFFER ON THE NAMESPACE, NOT JUST THE DOCTYPE. # ============================================================================ # # The fixture is XHTML, so every element carries the XHTML namespace. The XML # writer PRESERVES it and renders prefixed -- `'. The HTML and XHTML writers strip it and render plain # `', each with its own DOCTYPE. # # So the two literals are not the same document with a different prologue, and # both are asserted in full: `render-xml', `render-html', `render-xhtml', plus # `render-xml-fragment' for the prologue-less form. # # ============================================================================ # RE-RENDERING THE SAME TREE TWICE MUST GIVE IDENTICAL BYTES. # ============================================================================ # # This is the mutation-safety property the clone-on-repeat design exists for, # and the reason it is worth a case: a writer that mutated the tree it walked # would give a different answer the SECOND time, so the failure would appear # only in production, only under reuse, and only on the second request. # # Three cases, because one would not be evidence: the same tree rendered twice # through the XML writer (`render-twice-identical'), the same through the HTML # writer, and two independently parsed trees filled identically # (`render-two-trees-agree') -- which is what rules out "stable because nothing # was measured". # # ============================================================================ # THE UPSTREAM TEST SUITE SHIPS IN THE RUNTIME PACKAGE, AND IS LEFT THERE. # ============================================================================ # # setup.py packages `meld3' with a bare package list, so test_meld3.py -- 62 kB # of source plus 90 kB of bytecode -- lands in the runtime .deb. The recipe does # not `inherit ptest', so there is no -ptest split it was meant to go to. # # Not repackaged, deliberately, on the same reasoning already recorded for # python3-aniso8601 in this corpus: dropping it would make this recipe disagree # with meta-python for ~150 kB on an image measured in hundreds of megabytes, # and would remove a file a caller may be running with `python3 -m unittest'. # `tests-present' asserts it is there and `shim-not-imported' asserts importing # the package does not reach it, so its cost is disk and nothing else. # # meld3/meld3.py is a four-line backwards-compatibility shim re-exporting the # four parse functions. It is NOT imported by the package root; the pair # `shim-not-imported' / `shim-importable' asserts both halves of that. # # ============================================================================ # EVERY EXPECTED VALUE BELOW WAS MEASURED, NOT PREDICTED. # ============================================================================ # # The literals come from running data/python3-meld3-probe.py against the tree # unpacked from THIS BUILD's .deb on an x86-64 control, on CPython 3.12.3 -- # and this image ships 3.12, so unlike several neighbours in this corpus there # is no minor-version gap to discount. The package is pure Python and reaches # nothing outside the standard library, so the control and the target run the # same code; what the target adds is whether this interpreter's static # pyexpat, its packaging and its filesystem agree. # # What is NOT yet measured on target is the run itself. This is the port's # first on-target execution and it happens in the batch verify. P=python3-meld3 bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/meld3 DI=$SITE/meld3-2.0.1.dist-info PKGLIST=/var/lib/dpkg/info/python3-meld3.list PROBE=$BST_DATA/python3-meld3-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-meld3.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # --- layout ---------------------------------------------------------------- check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/no-module-py test ! -f "$SITE/meld3.py" check $P/init-file test -s "$PKGDIR/__init__.py" check $P/compat-file test -s "$PKGDIR/_compat.py" check $P/shim-file test -s "$PKGDIR/meld3.py" check $P/tests-present test -s "$PKGDIR/test_meld3.py" check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" check $P/no-cli test ! -e /usr/bin/meld3 # --- the packaged file list ------------------------------------------------ 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)" _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # --- dist-info ------------------------------------------------------------- check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 2.0.1" cat "$DI/METADATA" check_out $P/dist-info-name "Name: meld3" cat "$DI/METADATA" check_out $P/dist-info-top-level "meld3" cat "$DI/top_level.txt" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check $P/dist-info-licence test -s "$DI/LICENSE.txt" # The distribution's own Summary calls the package UNMAINTAINED, and says so # because Supervisor dropped it as a dependency in 4.1.0 (October 2019). That # is asserted here so the fact travels with the port rather than living only in # a design doc: anything built on this package is building on a dead one. check_out $P/dist-info-summary-unmaintained "Unmaintained templating system" \ cat "$DI/METADATA" # --- dpkg ------------------------------------------------------------------ sed -n '/^Package: python3-meld3$/,/^$/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" 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-xml "python3-xml" cat "$W/dep" check_out $P/pkg-depends-cgitb "python3-cgitb" cat "$W/dep" check_out $P/pkg-depends-email "python3-email" cat "$W/dep" check_out $P/pkg-depends-html "python3-html" cat "$W/dep" check_eq $P/no-ptest-package "" \ "$(sed -n '/^Package: python3-meld3-ptest$/p' /var/lib/dpkg/status 2>/dev/null)" # --- the import ------------------------------------------------------------ check $P/import python3 -c 'import meld3' check_eq $P/import-quiet "" \ "$(python3 -c 'import meld3' 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 bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } 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 check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "78" "$(pv emitted_count)" # --- module identity and closure ------------------------------------------- check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/dist-version "2.0.1" "$(pv dist_version)" # meld3 2.0.1 defines NO __version__; the version lives only in the installed # metadata. Asserted as a negative so a release that added one is NAMED here. check_eq $P/no-version-attr "no" "$(pv has_version_attr)" # The closure is a sys.modules DELTA, so it is measured against whatever the # interpreter had already loaded -- and that baseline belongs to the CPython # build, not to meld3. This image's static interpreter starts leaner than the # x86-64 control the set below was cut from, so its delta additionally carries # the private `_collections', `_functools', `_operator' and `_weakrefset'. # Asserting the whole set therefore asserted the startup path, and that is what # it failed on. What the port actually rests on is asserted directly instead. _closure=",$(pv closure_top)," # The three splits python3-meld3_%.bbappend adds to RDEPENDS, each asserted to # be REACHED by the import rather than inferred from the source. for _m in email html xml; do case "$_closure" in *,$_m,*) bst_ok $P/closure-has-$_m ;; *) bst_notok $P/closure-has-$_m \ "'$_m' is not in the import closure: $(pv closure_top)" ;; esac done # `cgitb' is DECLARED and is referenced nowhere in the source; the bbappend # keeps it deliberately. Measured, not assumed -- so a release that starts # using it is named here. case "$_closure" in *,cgitb,*) bst_notok $P/closure-no-cgitb "cgitb IS reached: $(pv closure_top)" ;; *) bst_ok $P/closure-no-cgitb ;; esac # The property that makes the dependency list finite and checkable: the import # reaches nothing outside the standard library. Baseline-independent, because # it scores every name in the delta rather than the delta's membership. check_eq $P/closure-nonstd "" "$(pv closure_nonstd)" for _n in parse_xml parse_html parse_xmlstring parse_htmlstring Replace \ doctype MeldTreeBuilder HTMLMeldParser insert_doctype prefeed; do check_eq $P/api-$_n "yes" "$(pv has_$_n)" done check_eq $P/shim-not-imported "yes" "$(pv shim_not_imported)" check_eq $P/shim-importable "yes" "$(pv shim_importable)" # --- parsing --------------------------------------------------------------- check_eq $P/fixture-parses "_MeldElementInterface" "$(pv fixture_parses)" check_eq $P/root-tag '{http://www.w3.org/1999/xhtml}html' "$(pv root_tag)" # --- findmeld -------------------------------------------------------------- check_eq $P/find-title '{http://www.w3.org/1999/xhtml}h1' "$(pv find_title)" check_eq $P/find-table '{http://www.w3.org/1999/xhtml}table' "$(pv find_table)" check_eq $P/find-row '{http://www.w3.org/1999/xhtml}tr' "$(pv find_row)" check_eq $P/find-cell '{http://www.w3.org/1999/xhtml}td' "$(pv find_cell)" check_eq $P/find-note '{http://www.w3.org/1999/xhtml}p' "$(pv find_note)" check_eq $P/find-missing "" "$(pv find_missing)" check_eq $P/find-missing-default "DEF" "$(pv find_missing_default)" # `cell' is two levels under `table' -- findmeld searches the subtree, it is # not a direct-child lookup. check_eq $P/find-nested '{http://www.w3.org/1999/xhtml}td' \ "$(pv find_nested_from_table)" check_eq $P/meldid-of-title "title" "$(pv meldid_of_title)" check_eq $P/findmelds-ids "cell,note,row,table,title" "$(pv findmelds_ids)" check_eq $P/meldid-in-attrib '{http://www.plope.com/software/meld3}id' \ "$(pv meldid_in_attrib)" # --- repeat ---------------------------------------------------------------- check_eq $P/repeat-pairs "3" "$(pv repeat_pairs)" check_eq $P/repeat-first-is-template "yes" "$(pv repeat_first_is_template)" check_eq $P/repeat-later-are-clones "yes" "$(pv repeat_later_are_clones)" check_eq $P/repeat-data "a,b,c" "$(pv repeat_data)" check_eq $P/repeat-same-parent "yes" "$(pv repeat_same_parent)" check_eq $P/repeat-children-of-table "3" "$(pv repeat_children_of_table)" check_eq $P/repeat-rendered \ '\ncell-acell-bcell-c' \ "$(pv repeat_rendered)" check_eq $P/repeat-empty-pairs "0" "$(pv repeat_empty_pairs)" check_eq $P/repeat-empty-leaves-row "yes" "$(pv repeat_empty_leaves_row)" check_eq $P/repeat-childname-pairs "2" "$(pv repeat_childname_pairs)" check_eq $P/repeat-childname-target "yes" "$(pv repeat_childname_target)" # --- content --------------------------------------------------------------- # `>' and `"' are deliberately NOT escaped in character data -- see the header. check_eq $P/content-escaped \ '\na<b>&"c' \ "$(pv content_escaped)" check_eq $P/content-structure \ '\nraw' \ "$(pv content_structure)" check_eq $P/content-drops-children "1" "$(pv content_drops_children)" check_eq $P/content-row-gone "yes" "$(pv content_row_gone)" check_eq $P/fillmelds-unfilled "nope" "$(pv fillmelds_unfilled)" check_eq $P/fillmelds-applied \ '\nT' \ "$(pv fillmelds_applied)" check_eq $P/replace-index "2" "$(pv replace_index)" check_eq $P/replace-node-gone "yes" "$(pv replace_node_gone)" # --- attributes ------------------------------------------------------------ check_eq $P/attrib-initial "plain" "$(pv attrib_initial)" check_eq $P/attrib-after-set \ 'class=plain,id=n1,{http://www.plope.com/software/meld3}id=note' \ "$(pv attrib_after_set)" # There is no removal arm: both type-checks raise rather than dropping a key. check_eq $P/attrib-set-nonstring-key "ValueError" "$(pv attrib_set_nonstring_key)" check_eq $P/attrib-set-nonstring-val "ValueError" "$(pv attrib_set_nonstring_val)" check_eq $P/attrib-after-del \ 'id=n1,{http://www.plope.com/software/meld3}id=note' \ "$(pv attrib_after_del)" # The meld id is in attrib above and absent from the render below -- the pair. check_eq $P/attrib-rendered \ '\nNOTE' \ "$(pv attrib_rendered)" # --- deparent / clone ------------------------------------------------------ check_eq $P/deparent-before "3" "$(pv deparent_before)" check_eq $P/deparent-index "0" "$(pv deparent_index)" check_eq $P/deparent-after "2" "$(pv deparent_after)" check_eq $P/deparent-gone "yes" "$(pv deparent_gone)" check_eq $P/deparent-twice "" "$(pv deparent_twice)" check_eq $P/parentindex "2" "$(pv parentindex_of_note)" check_eq $P/clone-parent-none "yes" "$(pv clone_parent_none)" check_eq $P/clone-is-copy "yes" "$(pv clone_is_copy)" check_eq $P/clone-children "1" "$(pv clone_children)" # --- the writers ----------------------------------------------------------- check_eq $P/render-xml \ '\nHello & <World>c1c2NOTE' \ "$(pv render_xml)" check_eq $P/render-html \ '\n

Hello & <World>

c1
c2

NOTE

' \ "$(pv render_html)" check_eq $P/render-xhtml \ '\n

Hello & <World>

c1
c2

NOTE

' \ "$(pv render_xhtml)" check_eq $P/render-xml-fragment \ 'Hello & <World>c1c2NOTE' \ "$(pv render_xml_fragment)" # --- mutation safety ------------------------------------------------------- check_eq $P/render-twice-identical "yes" "$(pv render_twice_identical)" check_eq $P/render-twice-len "298" "$(pv render_twice_len)" check_eq $P/render-html-twice-identical "yes" "$(pv render_html_twice_identical)" check_eq $P/render-two-trees-agree "yes" "$(pv render_two_trees_agree)" # --- the HTML front door --------------------------------------------------- check_eq $P/html-parses "_MeldElementInterface" "$(pv html_parses)" check_eq $P/html-find-title "h1" "$(pv html_find_title)" check_eq $P/parse-bad-xml "ParseError" "$(pv parse_xmlstring_bad)" rm -rf "$W"