# BST-TIMEOUT: 300 # 79 cases; measured elapsed=8s on an idle local guest # # (2026-08-22). The default 60 would very likely hold -- # # the number is declared anyway because the two duk runs # # over a 58 kB and an 18 kB script are the only genuinely # # CPU-heavy work any libjs-* suite does, and this bound # # protects the ==BST-END== sentinel on the host's worst # # day rather than its median one. # # libjs-sizzle 1.10.18 -- Sizzle, the standalone CSS selector engine jQuery is # built on, shipped as a system JavaScript asset under # /usr/share/javascript/sizzle. # # DATA AND NOTHING BUT DATA. The recipe has no do_compile, no DEPENDS and no # configure: it unpacks a tarball and copies dist/*.js out of it. So byte # identity with x86-64 is expected BY CONSTRUCTION and asserting it says nothing # about the compiler. What it IS a statement about is the install and packaging # path, which is the part that can break on this arm: that the payload landed in # the package the image installs rather than in the -doc or -dev package it does # not, that ${datadir} resolved to /usr/share, that nothing truncated it on the # way to a virtiofs root, and that no other recipe claims the same paths. # # BUT THIS PAYLOAD IS ALSO A PROGRAM, AND IT IS RUN. Unlike its three libjs-* # siblings, Sizzle has a large DOM-free surface -- the tokenizer, the selector # compiler, Expr.filter's matcher factories and the error paths -- and duktape # 2.7.0 is on this image. So both scripts are loaded into a real JavaScript # engine on the target and made to compile a selector using every combinator, an # attribute prefix match and a positional pseudo. That is a statement no byte # comparison can make, and it is what separates "the bytes arrived" from "the # selector engine works here". # # THE MINIFIED AND UNMINIFIED SCRIPTS ARE PROBED SEPARATELY AND THEN COMPARED. # They are two independent builds of the same library, so agreement between them # on the API surface is a check neither can make alone: a payload where only one # of the two was replaced passes every per-file case and fails min-agrees. # # THE SOURCE MAP IS NOT INSTALLED, AND ITS REFERENCE IS DANGLING TWICE OVER. # dist/ carries three files; the recipe's do_install globs *.js, so # sizzle.min.map is dropped -- while sizzle.min.js's last line still reads # //# sourceMappingURL=dist/sizzle.min.map. That URL would not resolve even if # the map WERE installed, because it names a dist/ subdirectory this install # layout does not have. Both halves are pinned below rather than left implicit, # so a later recipe that starts shipping the map is visible as a red rather than # as a silent change. It costs nothing at runtime: a source map is read only by # a browser devtool that asked for it, and there is no browser on this image. # # WRITTEN TO EXEC AS LITTLE AS POSSIBLE -- one find, one sha256sum, one gawk, # and two duk runs. On this arm a process's memory is not returned when it exits # and /bin/sh is a 16 MB wasm module, so a suite's cost is dominated by how many # programs it starts. . "$BST_LIB" S=libjs-sizzle # THE CANARY, which asserts nothing about libjs-sizzle. # # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and NOT a defect in the host tool # (BRINTOS-1149 established that and filed it as linux-in-the-browser#177). # The harness reports the deficit but computes its pass count as # total-minus-failures, so THE DROPPED LINE IS COUNTED AS A PASS. A nothing-case # therefore stands in that position and the eaten line costs nothing; every real # case below is still counted. It sits ahead of the `require` guards # deliberately, because a `require` that skips would emit its skip as the first # line and that line would be eaten too. Same idiom and reason as 99-mailcap. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $S/suite-canary P=/usr/share/javascript/sizzle D=$BST_DATA tmp=${TMPDIR:-/tmp} W=$tmp/bst-libjs-sizzle.$$ require gawk $S/gawk-present require sha256sum $S/sha256sum-present require find $S/find-present if [ -d "$P" ]; then bst_ok $S/root; else bst_notok $S/root "$P is not a directory"; return 2>/dev/null || exit 0; fi # ------------------------------------------------------------- payload shape # One find for the whole subtree. -mindepth 1 with %P means a nested directory # would show up as a path with a slash in it, so the flat layout the recipe # produces is checked rather than assumed -- and it is what makes the dangling # dist/ in the source-map URL provable below. find "$P" -mindepth 1 -printf '%P %y %s %m %l\n' 2>/dev/null | LC_ALL=C sort > "$W.inv" sha256sum "$P/sizzle.js" "$P/sizzle.min.js" > "$W.sums" 2>&1 LC_ALL=C gawk \ -v INV="$W.inv" -v SUMS="$W.sums" \ -v REF='sizzle.js:4890ed2ea7969008b2f8bc52e36106f0eb2f7c700fc64f3a89b1a6ad1da83f27 sizzle.min.js:76cd5ec4a6776b2afd14a1310e64f4cb6df6bfb907d86638c01affdcc2f271c3' \ -f "$D/libjs-sizzle-scan.awk" \ "$W.inv" "$W.sums" "$P/sizzle.js" "$P/sizzle.min.js" > "$W.f1" . "$W.f1" # THE SCAN RAN AT ALL. Two cases below assert an empty value, and an empty value # is exactly what a gawk that died before END hands back -- which is what a # payload file that is not there produces, gawk being fatal on an unreadable # input. This case is what those two rest on, so it is asserted before them. check_eq $S/scan-ran 'ok' "$SZ_SCAN" # The two entries the recipe's do_install produces, by name, type, size and # mode. A directory that exists and holds one of them is the partial install # `test -d` cannot see. check_eq $S/payload-entries 2 "$SZ_INV_N" check_eq $S/payload-files 2 "$SZ_FILES" check_eq $S/payload-links 0 "$SZ_LINKS" check_eq $S/payload-dirs 0 "$SZ_DIRS" check_eq $S/payload-bytes 76736 "$SZ_BYTES" check_eq $S/payload-inventory \ 'sizzle.js:f 58234 644 sizzle.min.js:f 18502 644' "$SZ_INV" check_eq $S/file-modes '644:2' "$SZ_MODES" check_eq $S/payload-js-count 2 "$SZ_JS_N" # The source map upstream ships in dist/ and the *.js glob drops. Asserted as an # absence so a recipe that starts installing it is a red here rather than a # silent change to the payload the reference manifest describes. check_eq $S/payload-no-map 0 "$SZ_MAP_N" # ------------------------------------------- content, against x86-64 reference # These two sums are of the upstream sizzle-1.10.18 tarball -- the same # sizzle_1.10.18.orig.tar.gz the recipe fetches, at the sha256sum the recipe # declares -- unpacked on x86-64 and put through the recipe's install rules: the # upstream artifact by its own route, not a copy of the package this image # built, which would have made the comparison tautological. check_eq $S/sha256-match '2/2' "$SZ_SHA_MATCH" check_eq $S/sha256-first-mismatch '' "$SZ_SHA_FIRST_BAD" check_eq $S/sha256-sizzle-js \ 4890ed2ea7969008b2f8bc52e36106f0eb2f7c700fc64f3a89b1a6ad1da83f27 "$SZ_SHA_JS" check_eq $S/sha256-sizzle-min-js \ 76cd5ec4a6776b2afd14a1310e64f4cb6df6bfb907d86638c01affdcc2f271c3 "$SZ_SHA_MIN" # ------------------------------------------------ read back through the target # An INDEPENDENT way of saying the files are complete: these counts come from # gawk's own reader rather than from sha256sum's, and they localise a short read # instead of merely detecting one -- a truncation shows up as a short line and # byte count against the named file. check_eq $S/readback 'sizzle.js:2034:58234 sizzle.min.js:3:18502' "$SZ_RB" check_eq $S/readback-lines 2037 "$SZ_RB_LINES" check_eq $S/readback-bytes 76736 "$SZ_RB_BYTES" check_eq $S/readback-first-short '' "$SZ_RB_FIRST_SHORT" # Every byte of this payload is ASCII. Asserting it is what makes the byte counts # above locale-independent, and it fails if a transfer ever re-encoded the tree # on the way to the virtiofs root. It also inverts the libjs-jquery finding, # whose jquery.js carries one U+FFFD inside Sizzle's own escape handler -- the # bundled copy there is a LATER Sizzle than this standalone one. check_eq $S/text-high-bytes 0 "$SZ_HI" # Which files end with a newline, read off gawk's RT rather than assumed. # Upstream's minifier writes none, and the last thing in sizzle.min.js is a // # comment -- which is why anything concatenated after it must be preceded by a # newline, as the probe staging below is careful to do. check_eq $S/text-final-newline 'sizzle.js' "$SZ_FINAL_NL" # ------------------------------------------------------------------- identity # Every vN.N.N string anywhere in the payload, deduplicated. This is the version # assertion: a 1.10.17 tarball dropped in under the same recipe passes every # count above and fails here. check_eq $S/version-strings '1.10.18' "$SZ_VERSIONS" check_eq $S/version-banner-js 1 "$SZ_BANNER_FULL" check_eq $S/version-banner-min 1 "$SZ_BANNER_MIN" check_eq $S/release-date '2014-02-05' "$SZ_DATE" # ------------------------------------------------------------- the source map # See the header. Three cases, and the third is the one that matters: the URL # names a dist/ subdirectory, and payload-dirs above proved there is none. check_eq $S/sourcemap-min-ref 1 "$SZ_SMAP_MIN" check_eq $S/sourcemap-js-none 0 "$SZ_SMAP_JS" check_eq $S/sourcemap-url 'dist/sizzle.min.map' "$SZ_SMAP_URL" # ----------------------------------------------------------------- packaging # THE EXPECTATION IS DERIVED, NOT WRITTEN DOWN. This recipe installs nothing to # ${docdir}, so no -doc package is produced at all and that directory must be # absent while the payload above is present. Saying it that way round is the # point: it is the case that fails if the javascript ever lands in -doc instead, # which is exactly the packaging mistake this port had to check for. if [ -d /usr/share/doc/libjs-sizzle ]; then bst_notok $S/doc-not-installed \ "/usr/share/doc/libjs-sizzle exists; the -doc package is not in IMAGE_INSTALL" else bst_ok $S/doc-not-installed fi # --------------------------------------------------- the engine, on the target # Placed HERE rather than at the top on purpose. `require` ends the file, so a # duk that ever left this image would cost every case below it and none above: # the payload verification still runs and reports, and the skip says why the # rest did not. The alternative -- requiring duk up front -- would turn a # missing interpreter into a suite that says nothing at all about the payload. require duk $S/duk-present # Each script is staged as PFX + stub + payload + newline + probe and run once. # # THE SEPARATOR NEWLINE IS DEFENSIVE, AND THE COMMENT THAT USED TO SIT HERE # OVERCLAIMED IT. sizzle.min.js has no final newline and its last line is a // # comment (the source-map reference), so a plain `cat stub payload probe` runs # the probe's FIRST PHYSICAL LINE into that comment. Mutation-tested both ways: # removing the `echo` costs nothing today, and removing it AND making the # probe's first line executable code still costs nothing -- because only that # one line is absorbed and the probe's line 1 is a comment. So this is not # guarding an observed failure. What it does buy is that the staging stops # depending on a property of the probe FILE that nothing else checks; the `echo` # is one builtin. text-final-newline above is the case that pins the payload # shape this reasoning rests on. # # The staging is also why the payload is read with `cat` as an ARGUMENT rather # than by redirecting stdin from it: a top-level `< /usr/share/...` on the # virtiofs root returns errno 513 and kills the suite silently # (brintos/linux#73, and the packager README's table). probe_one() { # $1 = shell prefix, $2 = payload basename, $3 = case suffix { printf 'var PFX="%s";\n' "$1" cat "$D/libjs-sizzle-stub.js" "$P/$2" echo cat "$D/libjs-sizzle-probe.js" } > "$W.$3.js" duk "$W.$3.js" > "$W.$3.out" 2> "$W.$3.err" . "$W.$3.out" } probe_cases() { # $1 = shell prefix, $2 = case suffix # Indirection by eval, with EVERY right-hand side double-quoted inside the # eval'd text. Several of these values carry spaces -- the two error messages # and the probe selector -- and an unquoted assignment there would word-split # the message and try to run its second word as a command. eval " v_probe=\"\$${1}PROBE\"; v_type=\"\$${1}TYPE\" v_arity=\"\$${1}ARITY\"; v_api=\"\$${1}API\" v_miss=\"\$${1}API_MISSING\"; v_comp=\"\$${1}COMPILE\" v_rt=\"\$${1}COMPILE_ROUNDTRIP\"; v_sel=\"\$${1}COMPILE_SEL\" v_np=\"\$${1}NPSEUDOS\"; v_ps=\"\$${1}PSEUDOS\" v_m=\"\$${1}MATCH\"; v_rel=\"\$${1}RELATIVE\" v_cl=\"\$${1}CACHELEN\"; v_ftag=\"\$${1}F_TAG\" v_fcls=\"\$${1}F_CLASS\"; v_fstar=\"\$${1}F_STAR\" v_mc=\"\$${1}M_CHILD\"; v_ma=\"\$${1}M_ATTR\" v_mctx=\"\$${1}M_CTX\"; v_mb=\"\$${1}M_BOOL\" v_e1=\"\$${1}ERR_PSEUDO\"; v_e2=\"\$${1}ERR_SYNTAX\" v_xml=\"\$${1}ISXML\"; v_gt=\"\$${1}GETTEXT\" " # THE PROBE RAN TO THE END. api-missing below asserts an empty value, and an # empty value is what a duk that threw -- or never parsed the file at all -- # hands back for every variable at once. Asserted first, same reason as # scan-ran. check_eq $S/$2-probe-ran 'complete' "$v_probe" # The module loaded and exposed itself on the stub window. Sizzle's EXPOSE # block prefers AMD then CommonJS then the global, and neither define nor # module exists in duk at global scope, so the global is the branch taken. check_eq $S/$2-sizzle-type 'function' "$v_type" check_eq $S/$2-sizzle-arity 4 "$v_arity" check_eq $S/$2-api '12/12' "$v_api" check_eq $S/$2-api-missing '' "$v_miss" # The compiler, run for real. compile() tokenizes the selector and builds the # matcher tree; the compiled function carries the selector it came from, so # the round-trip is a statement about the tokenizer's output rather than about # a string the probe already had. check_eq $S/$2-compile 'function' "$v_comp" check_eq $S/$2-compile-roundtrip 'true' "$v_rt" check_eq $S/$2-compile-selector \ 'div > p.foo, #bar[href^="http"] ~ span:not(.x)' "$v_sel" # The engine's tables, by exact content. A truncated payload loses the names # past the cut, and a different Sizzle release changes the set: :scope and # :is arrive in later versions, and this one has neither. check_eq $S/$2-pseudos-count 32 "$v_np" check_eq $S/$2-pseudos \ 'button,checkbox,checked,contains,disabled,empty,enabled,eq,even,file,first,focus,gt,has,header,image,input,lang,last,lt,not,nth,odd,parent,password,radio,reset,root,selected,submit,target,text' \ "$v_ps" check_eq $S/$2-match-keys 'ATTR,CHILD,CLASS,ID,PSEUDO,TAG,bool,needsContext' "$v_m" check_eq $S/$2-relative '+,>,SP,~' "$v_rel" check_eq $S/$2-cachelength 50 "$v_cl" # The matcher factories, called and then USED against plain objects. This is # the engine discriminating, not merely parsing: TAG case-folds, CLASS is # word-bounded so "foobar" must not match "foo", and "*" matches anything. check_eq $S/$2-filter-tag 'true,true,false' "$v_ftag" check_eq $S/$2-filter-class 'true,false,false' "$v_fcls" check_eq $S/$2-filter-star 'true' "$v_fstar" # The tokenizer's own regexes, on the two hardest productions. check_eq $S/$2-token-child 'nth|child|2n+1' "$v_mc" check_eq $S/$2-token-attr 'href|^=|http' "$v_ma" check_eq $S/$2-needs-context 'true,false' "$v_mctx" check_eq $S/$2-bool-attr 'true,false' "$v_mb" # The error paths, verbatim. Sizzle.error is the one public entry point # specified by its message rather than by its return value, and both messages # are what a consumer catching them keys on. check_eq $S/$2-err-pseudo \ 'Syntax error, unrecognized expression: unsupported pseudo: __nope__' "$v_e1" check_eq $S/$2-err-syntax \ 'Syntax error, unrecognized expression: <<<' "$v_e2" # Two helpers that take plain objects rather than nodes. isXML keys on the # documentElement's nodeName being exactly "HTML", so both directions are # asserted -- a one-sided check passes on a function that always answers true. check_eq $S/$2-isxml 'false,true' "$v_xml" check_eq $S/$2-gettext 'hello' "$v_gt" } probe_one SZA_ sizzle.js js probe_one SZB_ sizzle.min.js min probe_cases SZA_ js probe_cases SZB_ min # The two builds agree on the API surface. Neither per-file block can make this # statement: a payload where only sizzle.min.js was swapped for another release # passes all 23 cases above for each file and fails here. check_eq $S/min-agrees-with-full \ "$SZA_API|$SZA_NPSEUDOS|$SZA_PSEUDOS|$SZA_MATCH|$SZA_RELATIVE|$SZA_ERR_PSEUDO" \ "$SZB_API|$SZB_NPSEUDOS|$SZB_PSEUDOS|$SZB_MATCH|$SZB_RELATIVE|$SZB_ERR_PSEUDO" rm -f "$W.inv" "$W.sums" "$W.f1" \ "$W.js.js" "$W.js.out" "$W.js.err" "$W.min.js" "$W.min.out" "$W.min.err"