. "$BST_LIB" # BST-TIMEOUT: 2700 # NINE python3 starts, and they are the whole cost. Every # # assertion below either reads the filesystem/dpkg with # # shell builtins or greps the output of one of those nine. # # # # Three are interpreter-side -- a bare `import', a second # # one whose STDERR is the assertion (`import-quiet'), and # # the probe, which takes ONE start for a hundred and seven # # measurements. The other six are the SHIPPED SCRIPT, once # # per CLI case, and they cannot be folded into the probe # # because the whole point of them is that /usr/bin/js-beautify # # is a separate executable reached through its own shebang. # # # # The work inside each is string parsing over fixtures # # under 120 bytes -- microseconds beside an interpreter # # start on this arm, which is tens of seconds before the # # first line of the program runs. # # # # 2700 is 300s per start, five times the ~60s a python3 # # start has measured at here, and the same order as the # # 400s/start 99-python3-aniso8601 and 99-python3-parse set # # for their three. # # # # A suite-timeout red is a batch-wide cost, so if the nine # # 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-jsbeautifier 1.15.1 (meta-openembedded/meta-python # recipes-devtools/python/python3-jsbeautifier_1.15.1.bb, `inherit pypi # setuptools3', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # The inverse of a minifier. It re-indents and re-spaces JavaScript that has # been minified or badly formatted, as a library (`jsbeautifier.beautify') and # as a shipped script (`/usr/bin/js-beautify'). Its configuration surface is # what makes it more than a pretty-printer: indent size and character, brace # style, wrap width, and how many existing blank lines to keep. # # IT IS JAVASCRIPT ONLY. `cssbeautifier' and `html' are separate distributions # on PyPI and neither is in this sdist -- `pkg-only-js' below asserts that # directly, because "beautifies JS, CSS and HTML" is what beautifier.io says # about the PROJECT and a reader can carry it into an expectation about this # PACKAGE. There is one importable here and it is `jsbeautifier'. # # ============================================================================ # `six' IS A HARD RUNTIME DEPENDENCY THAT NO STATIC SCANNER CAN SEE, AND THE # RECIPE DID NOT DECLARE IT. # ============================================================================ # # This is what the bbappend exists for and what `pkg-depends-six' and # `pulled-nonstdlib' assert from the two opposite directions -- packaging and # runtime. # # The stock recipe declares python3-core, python3-shell and python3-stringold. # Upstream's setup.py declares `install_requires=["six>=1.13.0", # "editorconfig>=0.12.2"]' and the recipe carries neither across. six is not # optional and not a Python-2 leftover: # # core/inputscanner.py:30 self.__six = __import__("six") # javascript/acorn.py:17 six = __import__("six") # # The first is in `InputScanner.__init__', which EVERY beautify constructs; the # second is at module scope in the identifier-classification tables. Both are # spelled `__import__("six")' rather than `import six' -- upstream's own comment # says why ("given how we did the version import it is the only way to do this # without having setup.py fail on a missing six dependency") -- which is exactly # the mechanism by which the dependency stayed undeclared. # # So a rootfs that took this .deb without the bbappend installs cleanly, imports # cleanly, and raises ModuleNotFoundError inside a constructor on the first # beautify. `pulled-nonstdlib' is the case that would have caught it: it is the # ONE suite in this family whose expectation is a name rather than `'. # # ============================================================================ # ONE FLAG OF THE CLI DOES NOT WORK HERE, AND THAT IS THE WHOLE OF THE GAP. # ============================================================================ # # `editorconfig' is the other undeclared install_require, and it is reached in # exactly one place: `cli.set_file_editorconfig_opts', whose `from editorconfig # import ...' is FUNCTION-LOCAL and is called only from # `integrate_editorconfig_options', which returns immediately unless # `--editorconfig' was passed. # # meta-openembedded carries no python3-editorconfig recipe, so there is nothing # to declare for and the flag raises. Everything else -- the library, the # script, every other option -- is unaffected, and the suite says so as a pair: # `cli-editorconfig-hook' is about the tree that shipped and # `editorconfig-raises' is about the platform that is missing the module. # `cli-editorconfig-fails' asserts the user-visible consequence through the # script itself. # # A future image that grew python3-editorconfig turns `editorconfig-raises' # red. That is the correct signal, not a regression: the gap closed. # # ============================================================================ # EVERY EXPECTED OUTPUT BELOW IS A MEASUREMENT, NOT A TRANSCRIPTION. # ============================================================================ # # The literals were taken from the tree unpacked from THE BUILT .deb, which # `diff -rq' reports byte-identical to the sdist the recipe's SRC_URI sha256 # pins. They are escaped -- `\n' for a newline, `\t' for a tab -- because the # runner parses its input line by line and a beautifier's whole output is # newlines. That is not only transport: what this package is FOR is where it # put the newlines and what it put in front of them, so an assertion that # collapsed whitespace would test everything about the output except the part # under test. # # ============================================================================ # EACH OPTION IS ASSERTED TWICE: AS AN OUTPUT AND AS A DIRECTION. # ============================================================================ # # A literal says what the output IS; it goes red on any upstream change to the # fixture's exact spacing, including one that left the option working. So every # shape-changing option also carries a relation -- `indent-size8-deeper', # `brace-expand-own-line', `wrap20-shorter', `blankrun-*' -- which says what the # option DID and survives a re-spacing that a literal would not. # # THREE FIXTURES WERE REPLACED AFTER MEASURING THEM, and the reason is worth # recording because each looked correct: # # - `wrap_line_length=20' over a 16-character chain is a no-op. The fixture is # now 58 characters (`longest-wrap20' is 19, `longest-wrap0' is 59). # - `brace_style=end-expand' over a `switch' produced output BYTE-IDENTICAL to # the default: end-expand moves the brace that CLOSES a block, so it is # invisible without an `else' or a `catch'. The fixture is now `if/else'. # - `eol="\r\n"' over a single-line fixture produced no CR at all, there being # no line to end. The fixture is now the nested one. # # All three would have passed as written and asserted nothing. # # ============================================================================ # IDEMPOTENCE IS ASSERTED UNDER OPTIONS, NOT ONLY UNDER THE DEFAULT. # ============================================================================ # # Beautifying beautified output is a no-op -- for all six fixtures, and # separately for the three configurations that change the shape most # (`idem-indent8', `idem-tabs', `idem-expand'). Idempotence under the default # says nothing about a caller who set an option, and an option that is applied # on the first pass but re-derived from the text on the second is exactly the # bug this catches. P=python3-jsbeautifier # THE CANARY, which asserts nothing about python3-jsbeautifier. # # 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 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. It sits ahead of the `require' guards 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. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/jsbeautifier DI=$SITE/jsbeautifier-1.15.1.dist-info PKGLIST=/var/lib/dpkg/info/python3-jsbeautifier.list CLI=/usr/bin/js-beautify PROBE=$BST_DATA/python3-jsbeautifier-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-jsbeautifier.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import jsbeautifier' 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. setup.py names eight packages explicitly, so a # package tree lands in site-packages. This is the case that fails a release # which flattened to one module, loudly and here. check $P/package-dir test -d "$PKGDIR" check $P/no-module-py test ! -f "$SITE/jsbeautifier.py" check $P/init-file test -s "$PKGDIR/__init__.py" # The four subpackages, named because the header's findings are about them: # `core' holds InputScanner (the six import on every beautify), `javascript' # holds acorn (the six import at module scope) and the beautifier proper, # `unpackers' is the obfuscation front end that brings subprocess and base64 # into the closure, and `cli' holds the editorconfig hook. check $P/core-dir test -d "$PKGDIR/core" check $P/javascript-dir test -d "$PKGDIR/javascript" check $P/unpackers-dir test -d "$PKGDIR/unpackers" check $P/cli-dir test -d "$PKGDIR/cli" check $P/inputscanner-py test -s "$PKGDIR/core/inputscanner.py" check $P/acorn-py test -s "$PKGDIR/javascript/acorn.py" # The precompiled bytecode, which is what `import-quiet' below turns on. check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" # THE SHIPPED SCRIPT. setup.py declares one console_scripts entry point # (`js-beautify = jsbeautifier:main'), so unlike a pure library this package # does put something on PATH -- and the CLI group at the foot of this file is # about it. check $P/cli-present test -x "$CLI" check_out $P/cli-shebang "#!/usr/bin/env python3" head -n 1 "$CLI" # JAVASCRIPT ONLY -- see the header. `cssbeautifier' and the HTML beautifier are # separate PyPI distributions and neither is in this sdist. Asserted so the # project's own "JS, CSS and HTML" description cannot be read as a claim about # this package. check $P/no-cssbeautifier test ! -e "$SITE/cssbeautifier" check $P/no-css-cli test ! -e /usr/bin/css-beautify check $P/no-html-cli test ! -e /usr/bin/html-beautify # NO COMPILED EXTENSION ANYWHERE THE PACKAGE PUT ANYTHING. On this image a .so # could not be loaded at all -- python3 is ONE STATIC BINARY with an empty # lib-dynload and no dynamic loader (BRINTOS-1476) -- so this is the case that # fails a version bump which grew one, at install rather than at `import'. # # 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, ITS METADATA, OR THE ONE # SCRIPT. 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. # # A dpkg .list names DIRECTORIES too, and without a trailing slash, so a # name-only filter would report every directory in the package as an unexpected # shape; each entry is therefore tested for being a regular file first. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; "$CLI") continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # THE ONE IMPORTABLE THIS PACKAGE INSTALLS. Everything under site-packages that # the package owns is either the `jsbeautifier' tree or its dist-info -- which # is the packaging-side statement of `no-cssbeautifier' above. _other="" while IFS= read -r _f; do case "$_f" in "$PKGDIR"|"$PKGDIR"/*) continue ;; "$DI"|"$DI"/*) continue ;; "$SITE"/?*) _other="$_other $_f" ;; esac done < "$W/pkgf" check_eq $P/pkg-only-js "" "$_other" # ----------------------------------------------------------- the dist-info --- # The installed metadata. `__version__.py' carries `__version__' and setup.py # imports it (`from jsbeautifier.__version__ import __version__'), so the two # cannot disagree at build time -- which makes `version-agrees' below a # statement about the INSTALL rather than about upstream: a tree landing beside # a dist-info from some other release is what it catches. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 1.15.1" cat "$DI/METADATA" check_out $P/dist-info-name "Name: jsbeautifier" cat "$DI/METADATA" # The one-line description, which is upstream's own and is also the recipe's # SUMMARY. check_out $P/dist-info-summary "JavaScript unobfuscator and beautifier." cat "$DI/METADATA" # top_level.txt names the ONE importable this package installs. It is what an # `import jsbeautifier' has to match, and a wheel that renamed the tree would # change it here first. check_out $P/dist-info-top-level "jsbeautifier" cat "$DI/top_level.txt" # PURE PYTHON, AS THE WHEEL ITSELF DECLARES IT. `Root-Is-Purelib: true' is the # packaging-side statement of `pkg-no-so' -- 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" # THE ENTRY POINT, from the metadata rather than from PATH. `cli-present' says # a file landed in /usr/bin; this says the package ASKED for it, so a script # that arrived from somewhere else is distinguishable from this one. check_out $P/dist-info-entry-point "js-beautify = jsbeautifier:main" 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-jsbeautifier 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-jsbeautifier$/,/^$/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" # THE RUNTIME DEPENDENCIES, as dpkg recorded them -- and this is the group the # bbappend exists for. Three of the six are the recipe's own; the other three # are the port. Each is asserted BY NAME, because a Depends line that lost one # would still install on THIS image (all six are here for other reasons) and # break on the first rootfs that took the .deb alone. 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-shell "python3-shell" cat "$W/dep" check_out $P/pkg-depends-stringold "python3-stringold" cat "$W/dep" # THE ONE THAT MATTERS. See the header: without it the package installs, imports # and then raises inside InputScanner.__init__ on the first beautify. check_out $P/pkg-depends-six "python3-six" cat "$W/dep" # The unpacker path's two: base64 (myobfuscate) and subprocess->fcntl # (evalbased). Neither is on the default beautify, which is why they were # missed and why they are named. check_out $P/pkg-depends-netclient "python3-netclient" cat "$W/dep" check_out $P/pkg-depends-fcntl "python3-fcntl" cat "$W/dep" # NO -ptest PACKAGE EXISTS FOR THIS RECIPE: it does not `inherit ptest' at all. # The case is here so a version bump that grows one is noticed -- at which point # the upstream tests, which currently ship in the RUNTIME package, would have # somewhere else to go. check_eq $P/no-ptest-package "" \ "$(sed -n '/^Package: python3-jsbeautifier-ptest$/p' /var/lib/dpkg/status 2>/dev/null)" # --------------------------------------------------------- the interpreter --- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If jsbeautifier 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 jsbeautifier' # ...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 jsbeautifier has import-time output of its own: no logging # configuration, no warning, no print. check_eq $P/import-quiet "" \ "$(python3 -c 'import jsbeautifier' 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 # reporting got:'' and naming a symptom each, with the actual traceback nowhere. # The bound is generous because a python3 start on this arm is tens of seconds # before any work begins. 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 `out_wrap20' # distinguishable from `out_wrap0', and `idem_indent8' from `idem_ifelse' -- # the pairs where one name is a prefix of another. 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 a comment. # # The cases below still print their reds: withdraw, do not hide. This case is # what says what those reds measure. 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 # ...and it ran to the END. Every key is emitted in file order, so a probe that # died two thirds of the way through would leave the earlier cases green and the # later ones reading got:'' -- which looks like a partial library rather than a # partial run. This is the case that tells those apart. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "107" "$(pv emitted_count)" # The tree 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 jsbeautifier. check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" # THE VERSION, FROM BOTH PLACES, AND THEN AGAINST EACH OTHER. check_eq $P/version-attr "1.15.1" "$(pv version_attr)" check_eq $P/dist-version "1.15.1" "$(pv dist_version)" check_eq $P/version-agrees "$(pv version_attr)" "$(pv dist_version)" # ------------------------------------------------------------------ the API --- # THE SIX PUBLIC NAMES, each reachable from the package root: the two entry # points, the options factory, the two classes, and `main' -- which is what the # console_scripts entry point resolves to, so its absence would be the CLI's # absence one import earlier than /usr/bin/js-beautify could say so. check_eq $P/api-beautify "yes" "$(pv has_beautify)" check_eq $P/api-beautify-file "yes" "$(pv has_beautify_file)" check_eq $P/api-default-options "yes" "$(pv has_default_options)" check_eq $P/api-Beautifier "yes" "$(pv has_Beautifier)" check_eq $P/api-BeautifierOptions "yes" "$(pv has_BeautifierOptions)" check_eq $P/api-main "yes" "$(pv has_main)" # -------------------------------------------------------------- the defaults --- # EVERY BEHAVIOURAL CASE BELOW IS A DEFAULT PLUS ONE OVERRIDE, so a release that # moved a default would otherwise surface as a spray of unexplained output reds. # Here it is named, field by field. check_eq $P/opt-indent-size "4" "$(pv opt_indent_size)" check_eq $P/opt-indent-char " " "$(pv opt_indent_char)" check_eq $P/opt-indent-with-tabs "False" "$(pv opt_indent_with_tabs)" check_eq $P/opt-brace-style "collapse" "$(pv opt_brace_style)" check_eq $P/opt-preserve-newlines "True" "$(pv opt_preserve_newlines)" # 32786, NOT 32768. It is upstream's literal and it is off by 18 from the power # of two a reader assumes; asserted exactly so a release that "fixed" the typo # is reported rather than silently changing where blank lines get capped. check_eq $P/opt-max-preserve-newlines "32786" "$(pv opt_max_preserve_newlines)" check_eq $P/opt-wrap-line-length "0" "$(pv opt_wrap_line_length)" check_eq $P/opt-end-with-newline "False" "$(pv opt_end_with_newline)" check_eq $P/opt-eol "auto" "$(pv opt_eol)" check_eq $P/opt-eval-code "False" "$(pv opt_eval_code)" check_eq $P/opt-break-chained-methods "False" "$(pv opt_break_chained_methods)" check_eq $P/opt-keep-array-indentation "False" "$(pv opt_keep_array_indentation)" check_eq $P/opt-operator-position "before-newline" "$(pv opt_operator_position)" check_eq $P/opt-indent-level "0" "$(pv opt_indent_level)" # ------------------------------------------------------------ the fixtures --- # The inputs, echoed back. If a fixture ever fails to reach the probe intact -- # a quoting accident in this file, an encoding coercion in the guest -- every # output case below reds against an input nobody can see. These are what make # those reds readable. check_eq $P/fixture-nested "function a(){function b(){return{c:1,d:[1,2]}}return b()}" "$(pv fixture_nested)" check_eq $P/fixture-switch "switch(x){case 1:y=1;break;case 2:y=2;break;default:y=3}" "$(pv fixture_switch)" check_eq $P/fixture-mixed 'if (a) {\n\tb();\n c();\n}' "$(pv fixture_mixed)" check_eq $P/fixture-keepnl 'var a=1;\n\n\n\n\nvar b=2;' "$(pv fixture_keepnl)" # ---------------------------------------------------- beautification, default --- # THE DELIVERABLE. Four-space indent, one statement per line, a space after a # colon and around an operator, and the block structure recovered from a source # that had none. # # `out-nested' is the headline: nested function declarations, an object literal # and an array literal, each at its own depth. check_eq $P/out-nested \ 'function a() {\n function b() {\n return {\n c: 1,\n d: [1, 2]\n }\n }\n return b()\n}' \ "$(pv out_nested)" # A `switch' indents its `case' labels one level and their bodies two -- a rule # of its own, not a consequence of the brace. check_eq $P/out-switch \ 'switch (x) {\n case 1:\n y = 1;\n break;\n case 2:\n y = 2;\n break;\n default:\n y = 3\n}' \ "$(pv out_switch)" # A chain is NOT broken by default -- `out-breakchained' below is the same input # with the option set, and the pair is what says the default is a decision. check_eq $P/out-chain 'a.b(1).c(2).d(3);' "$(pv out_chain)" # Ternaries, including a nested one, get spaces around `?' and `:' and stay on # one line each. check_eq $P/out-tern 'var x = a ? b : c;\nvar y = p ? q ? r : s : t;' "$(pv out_tern)" # THE MIXED-WHITESPACE FIXTURE IS THE ONE THE BEAUTIFIER MUST NOT PRESERVE. Its # input carries a tab on one line and eight spaces on the next; the output # carries neither, because indentation is RECOMPUTED rather than reflowed. check_eq $P/out-mixed 'if (a) {\n b();\n c();\n}' "$(pv out_mixed)" check_eq $P/mixed-input-has-tab "yes" "$(pv mixed_input_has_tab)" check_eq $P/mixed-output-has-tab "no" "$(pv mixed_output_has_tab)" check_eq $P/mixed-output-uniform "yes" "$(pv mixed_output_uniform)" # An if/else, which is the fixture the three brace styles differ on. check_eq $P/out-ifelse 'if (a) {\n b()\n} else {\n c()\n}' "$(pv out_ifelse)" # ------------------------------------------------------------- indent_size --- check_eq $P/out-indent8 \ 'function a() {\n function b() {\n return {\n c: 1,\n d: [1, 2]\n }\n }\n return b()\n}' \ "$(pv out_indent8)" check_eq $P/out-indent1 \ 'function a() {\n function b() {\n return {\n c: 1,\n d: [1, 2]\n }\n }\n return b()\n}' \ "$(pv out_indent1)" # ...and the direction, which survives an upstream re-spacing that the two # literals above would not. check_eq $P/indent-default-spaces "4" "$(pv indent_default_spaces)" check_eq $P/indent-size8-spaces "8" "$(pv indent_size8_spaces)" check_eq $P/indent-size8-deeper "yes" "$(pv indent_size8_deeper)" # -------------------------------------------------------- indent_with_tabs --- check_eq $P/out-tabs \ 'function a() {\n\tfunction b() {\n\t\treturn {\n\t\t\tc: 1,\n\t\t\td: [1, 2]\n\t\t}\n\t}\n\treturn b()\n}' \ "$(pv out_tabs)" # One tab per level, and NO leading spaces at all -- the second is the half a # caller gets wrong, because an implementation that emitted a tab and then # padded to the indent width would pass the first. check_eq $P/indent-tabs-tabs "1" "$(pv indent_tabs_tabs)" check_eq $P/indent-tabs-no-leading-space "yes" "$(pv indent_tabs_no_leading_space)" # ------------------------------------------------------------- brace_style --- # `collapse' (the default) keeps the opening brace on the statement's line; # `expand' gives it its own; `end-expand' differs from collapse only at the # CLOSING brace, which is why it needs the if/else fixture. check_eq $P/out-brace-expand \ 'function a()\n{\n function b()\n {\n return {\n c: 1,\n d: [1, 2]\n }\n }\n return b()\n}' \ "$(pv out_brace_expand)" # ...and `collapse' explicitly is byte-identical to the default, which is what # says the default IS collapse rather than merely resembling it. check_eq $P/out-brace-collapse "$(pv out_nested)" "$(pv out_brace_collapse)" check_eq $P/out-ifelse-expand 'if (a)\n{\n b()\n}\nelse\n{\n c()\n}' "$(pv out_ifelse_expand)" check_eq $P/out-ifelse-endexpand 'if (a) {\n b()\n}\nelse {\n c()\n}' "$(pv out_ifelse_endexpand)" check_eq $P/brace-expand-own-line "yes" "$(pv brace_expand_own_line)" check_eq $P/brace-collapse-no-own-line "yes" "$(pv brace_collapse_no_own_line)" check_eq $P/endexpand-close-own-line "yes" "$(pv endexpand_close_own_line)" check_eq $P/collapse-no-close-own-line "yes" "$(pv collapse_no_close_own_line)" # ------------------------------------- preserve_newlines / max_preserve_newlines --- # The input carries FOUR blank lines between two statements. The default keeps # them; `preserve_newlines=False' removes them all; `max_preserve_newlines' # caps them -- and the cap counts NEWLINES, not blank lines, so a cap of 1 # leaves zero blank lines and a cap of 2 leaves one. That off-by-one is the # whole reason both values are asserted rather than just one. check_eq $P/out-keepnl 'var a = 1;\n\n\n\n\nvar b = 2;' "$(pv out_keepnl)" check_eq $P/out-nopreserve 'var a = 1;\nvar b = 2;' "$(pv out_nopreserve)" check_eq $P/out-maxpreserve1 'var a = 1;\nvar b = 2;' "$(pv out_maxpreserve1)" check_eq $P/out-maxpreserve2 'var a = 1;\n\nvar b = 2;' "$(pv out_maxpreserve2)" check_eq $P/blankrun-default "4" "$(pv blankrun_default)" check_eq $P/blankrun-nopreserve "0" "$(pv blankrun_nopreserve)" check_eq $P/blankrun-max1 "0" "$(pv blankrun_max1)" check_eq $P/blankrun-max2 "1" "$(pv blankrun_max2)" # -------------------------------------------------------- wrap_line_length --- # 0 is the default and means "do not wrap", so the pair says both that the # option acts and that its default does not. The fixture is 58 characters # precisely so that it does. check_eq $P/out-wrap20 \ 'obj.methodOne(1)\n .methodTwo(2)\n .methodThree(3)\n .methodFour(4);' \ "$(pv out_wrap20)" check_eq $P/out-wrap0 'obj.methodOne(1).methodTwo(2).methodThree(3).methodFour(4);' "$(pv out_wrap0)" check_eq $P/longest-wrap20 "19" "$(pv longest_wrap20)" check_eq $P/longest-wrap0 "59" "$(pv longest_wrap0)" check_eq $P/wrap20-shorter "yes" "$(pv wrap20_shorter)" # ---------------------------------------------- break_chained_methods / eol --- check_eq $P/out-breakchained 'a.b(1)\n .c(2)\n .d(3);' "$(pv out_breakchained)" # `eol' asserted on a MULTI-LINE fixture -- see the header; on a single-line one # it produced no CR at all and asserted nothing. check_eq $P/out-eol-crlf \ 'function a() {\r\n function b() {\r\n return {\r\n c: 1,\r\n d: [1, 2]\r\n }\r\n }\r\n return b()\r\n}' \ "$(pv out_eol_crlf)" check_eq $P/eol-default-has-cr "no" "$(pv eol_default_has_cr)" check_eq $P/eol-crlf-has-cr "yes" "$(pv eol_crlf_has_cr)" # `end_with_newline' adds the trailing newline the default omits. check_eq $P/out-endnl 'a.b(1).c(2).d(3);\n' "$(pv out_endnl)" # ------------------------------------------------------------- idempotence --- # Beautifying beautified output is a no-op -- for every fixture, and separately # for the three configurations that change the shape most. See the header for # why the second half is not implied by the first. check_eq $P/idem-nested "yes" "$(pv idem_nested)" check_eq $P/idem-switch "yes" "$(pv idem_switch)" check_eq $P/idem-chain "yes" "$(pv idem_chain)" check_eq $P/idem-tern "yes" "$(pv idem_tern)" check_eq $P/idem-mixed "yes" "$(pv idem_mixed)" check_eq $P/idem-keepnl "yes" "$(pv idem_keepnl)" check_eq $P/idem-indent8 "yes" "$(pv idem_indent8)" check_eq $P/idem-tabs "yes" "$(pv idem_tabs)" check_eq $P/idem-expand "yes" "$(pv idem_expand)" # ------------------------------------------------------------------ the edges --- # Empty and whitespace-only input, which a caller reaches by beautifying an # empty file. Both come back empty rather than raising. check_eq $P/out-empty "" "$(pv out_empty)" check_eq $P/out-blank "" "$(pv out_blank)" # `beautify(None)' DOES NOT RAISE -- InputScanner substitutes the empty string # for a None input, so the answer is ''. Asserted because a caller reading the # signature would expect the opposite, and because `beautify(1)' next door DOES # raise, so the pair says the tolerance is specific to None rather than general. check_eq $P/raise-none "" "$(pv raise_none)" check_eq $P/raise-int "TypeError" "$(pv raise_int)" # ---------------------------------------------------------------- beautify_file --- # The other public entry point, and the one the CLI uses. It must agree with # `beautify()' over the same bytes, and it must raise rather than return empty # on a path that is not there. check_eq $P/out-file "$(pv out_nested)" "$(pv out_file)" check_eq $P/file-matches-string "yes" "$(pv file_matches_string)" check_eq $P/file-missing-raises "FileNotFoundError" "$(pv file_missing_raises)" # ------------------------------------------------------------------ unpackers --- # The obfuscation front end. It is NOT on the default path -- `beautify()' # imports it only when `eval_code' is set -- and it is where subprocess and # base64 enter the closure, which is what two of the three bbappend RDEPENDS # entries are for. Named so a release that moved or dropped one is reported # here rather than at some caller's first `--eval' run. check_eq $P/unpackers-count "4" "$(pv unpackers_count)" check_eq $P/unpackers-names \ "jsbeautifier.unpackers.javascriptobfuscator,jsbeautifier.unpackers.myobfuscate,jsbeautifier.unpackers.packer,jsbeautifier.unpackers.urlencode" \ "$(pv unpackers_names)" check_eq $P/unpackers-error-class "UnpackingError" "$(pv unpackers_error_class)" # ------------------------------------------------------------------ the closure --- # THE CASE THE bbappend EXISTS FOR, from the runtime side. `pkg-depends-six' # above says the .deb declares it; this says the code actually reaches it. # # This is the ONE suite in this family whose expectation is a name rather than # `': six is imported through `__import__("six")' in # `InputScanner.__init__' and at module scope in `javascript/acorn.py', so every # beautify above went through it. A SECOND name appearing here is a new # undeclared dependency, and this is where it says so. check_eq $P/pulled-nonstdlib "six" "$(pv pulled_nonstdlib)" check_eq $P/needs-six "yes" "$(pv needs_six)" # ...and editorconfig is NOT reached, which is the other half: the import that # would pull it sits behind a CLI flag, so nothing the library does touches it. check_eq $P/needs-editorconfig "no" "$(pv needs_editorconfig)" # -------------------------------------------------------- the editorconfig gap --- # See the header. The pair separates the tree from the platform: the hook # shipped, and the module it wants is not on this image. check_eq $P/cli-editorconfig-hook "yes" "$(pv cli_has_editorconfig_hook)" check_eq $P/root-hides-editorconfig-hook "yes" "$(pv root_hides_editorconfig_hook)" # A future image carrying python3-editorconfig turns this red. That is the # correct signal and not a regression: the gap closed, and the header and the # bbappend are then stale. check_eq $P/editorconfig-raises "ModuleNotFoundError" "$(pv editorconfig_raises)" # ------------------------------------------------------------------ the CLI --- # THE SHIPPED SCRIPT, REACHED THROUGH ITS OWN SHEBANG. None of this can be # folded into the probe: the point of these cases is that /usr/bin/js-beautify # is a separate executable that the console_scripts entry point produced, and a # probe calling `jsbeautifier.main()' in-process would pass on an image where # the script never installed. # # The fixture is written under $W -- the same tmpfs the probe transcript uses -- # so nothing here redirects stdin from the virtiofs root (brintos/linux#73). printf 'function a(){function b(){return{c:1,d:[1,2]}}return b()}' > "$W/in.js" check $P/cli-fixture-written test -s "$W/in.js" # The version the script reports, which is the third place 1.15.1 is asserted # from -- after the module attribute and the dist-info -- and the only one that # goes through the entry point. check_out $P/cli-version "1.15.1" js-beautify --version # OVER A FILE ARGUMENT. Byte-for-byte the library's answer, which is what says # the CLI is a front end rather than a second implementation. check_eq $P/cli-file \ 'function a() { function b() { return { c: 1, d: [1, 2] } } return b() }' \ "$(js-beautify "$W/in.js" 2>&1)" # OVER STDIN, which the script spells `-'. The pipe is also what keeps this off # the top-level-redirect path. check_eq $P/cli-stdin \ "$(js-beautify "$W/in.js" 2>&1)" \ "$(cat "$W/in.js" | js-beautify - 2>&1)" # AN OPTION REACHES THE LIBRARY THROUGH THE SCRIPT. `-s 8' is `indent_size=8', # and its output is the one `out-indent8' already asserted from the library -- # so this case is about the plumbing, not about the beautifier. check_out $P/cli-indent8 " function b() {" js-beautify -s 8 "$W/in.js" # ...and the same for the tab flag. check_out $P/cli-tabs "$(printf '\tfunction b() {')" js-beautify -t "$W/in.js" # `--editorconfig' IS THE ONE FLAG THAT DOES NOT WORK HERE -- see the header. # The assertion is that it fails AND that the failure names the missing module, # rather than that it fails somehow: a flag that broke for an unrelated reason # would otherwise pass this case. # # The command runs inside $( ), so the fork happens before any redirect and the # non-zero status cannot end the suite. _ec=$(js-beautify --editorconfig "$W/in.js" 2>&1 >/dev/null || true) case "$_ec" in *editorconfig*) _ecr=names-editorconfig ;; '') _ecr='' ;; *) _ecr=$_ec ;; esac check_eq $P/cli-editorconfig-fails "names-editorconfig" "$_ecr" rm -rf "$W"