. "$BST_LIB" # BST-TIMEOUT: 1200 # ONE python3 start, and that is the whole cost. Every # # assertion below either reads the filesystem/dpkg with # # shell builtins or greps the single probe run's output; the # # probe itself parses a 142-byte document and resolves about # # forty pointers over it, which is nothing beside the # # interpreter load. It matches 99-python3-elementpath's and # # 99-python3-xmltodict's 1200 for the same reason -- the # # start dominates and the work after it is comparable. # # # # ONE EXTRA START, NOT ZERO: the shipped `jsonpointer' # # script is driven once below, and a script run IS another # # full interpreter load. 1200 still covers two starts with # # room; the two suites named above cover one each. # # # # 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-jsonpointer 2.4 (poky meta/recipes-devtools/python/python3-jsonpointer_2.4.bb, # `inherit pypi ptest setuptools3', PyPI sdist) on the wasm32-hwjs glibc image -- # RFC 6901 JSON POINTERS. `resolve_pointer(doc, "/a/b/0")' addresses one node of # a parsed JSON document, and `set_pointer' writes through the same address. # # ============================================================================ # WHAT THIS PORT ADDS, AND WHY IT IS NOT `doc["a"]["b"][0]'. # ============================================================================ # # A reader will reasonably ask what a pointer library does that ordinary # subscripting does not, since resolving `/a/b/0' by hand is one expression. The # answer is that a pointer is DATA: it arrives as a string, from a JSON Patch # document, a JSON Reference, or a config file, and the code that resolves it # does not know the path at the time it is written. That is why this package is # pulled in so widely -- it is the addressing half of JSON Patch and JSON # Reference, and both of those carry pointers as payload. # # It follows that the interesting behaviour is entirely in the STRING GRAMMAR -- # the escapes, the empty key, the array index, and what happens when the address # names nothing -- and that is what the cases below are about. Asserting only # `/foo/0' -> "bar" would be asserting the part subscripting already does. # # ============================================================================ # THE VECTORS ARE RFC 6901 §5's OWN, NOT INVENTED ONES. # ============================================================================ # # §5 publishes a document and the twelve pointers that address it, each with the # value it must yield. The probe evaluates that table and the cases below assert # each result against the literal the RFC prints. This matters more than the # usual preference for realistic fixtures: a fixture written by the porter tends # to be written to match what the implementation did, so it can only catch a # REGRESSION. A red against §5 is a divergence from the STANDARD, which is a # statement worth making about a package whose entire content is one RFC. # # Four of those twelve are the ones an implementation actually gets wrong, and # they are worth naming because they look like typos: # "" addresses the WHOLE DOCUMENT. Not "/", which is a different pointer. # "/" addresses the EMPTY-STRING KEY. The document really does define one. # "/ " addresses the key that is a single SPACE. # "/c%d" addresses `c%d' literally -- `%' is not percent-encoding here. # # ============================================================================ # THE ESCAPE ORDER IS THE ONE RULE WITH A WRONG ANSWER THAT LOOKS RIGHT. # ============================================================================ # # §4 requires `~1' to be undone FIRST and `~0' SECOND. Reverse them and `~01' # becomes `~1' and then `/' -- so the token addresses a DIFFERENT member, and # every other vector still passes. §5's own table cannot catch this, because its # document has no key that would distinguish the two. The `order-*' cases below # therefore drive a second, purpose-built document holding both `~1' and `/' as # keys: a correct implementation returns "tilde-one" for `/~01', and one that # reversed the order returns "slash". This is the one case here that was written # to catch a specific wrong implementation rather than to restate the RFC. # # ============================================================================ # WHY IT PORTS AT ALL: PURE PYTHON, AND ONE MODULE. # ============================================================================ # # This image's python3 is ONE STATIC BINARY with an EMPTY lib-dynload and no # dynamic loader (BRINTOS-1476; 99-python3 asserts both directly). A module # carrying a compiled extension could not be imported here -- not "would be # slow", could not be imported. # # jsonpointer ships NO C: no .c, .pyx or .so in the sdist, and setup.py declares # `py_modules' with no ext_modules at all. It sits on nothing that would need one # either -- its only import outside the stdlib is none, and `json' itself is # python3-json, a sub-package of the ported python3 (BRINTOS-1476), which is this # recipe's whole RDEPENDS. So nothing about this port waits on another package. # `pkg-no-so' below is a REGRESSION TRIPWIRE on a version bump rather than a # statement about today. # # IT IS A SINGLE MODULE, NOT A PACKAGE -- the opposite shape from # 99-python3-elementpath's directory of seven subpackages. setup.py declares # `py_modules = ('jsonpointer',)', so what lands in site-packages is the FILE # jsonpointer.py. `no-package-dir' asserts the inverse of elementpath's # `no-flat-module' for the same reason: a release that grew a package directory # would still import and still pass every behavioural case, while the shape this # image ships had silently changed. # # ============================================================================ # THE SCRIPT IS A `scripts=' ENTRY, NOT A console_scripts ENTRY POINT. # ============================================================================ # # setup.py ships `bin/jsonpointer' through `scripts=[...]', which means setuptools # installs the FILE and rewrites its `#!/usr/bin/env python' shebang, rather than # GENERATING a launcher as it would for a console_scripts entry point. The two # differ in exactly the way that matters on this image: a generated launcher # would name the interpreter setuptools was told to target, while a rewritten # shebang is whatever the build wrote into it -- and `python' unqualified does not # exist on this rootfs, only `python3'. # # So `script-shebang' is not padding. It is the case that would catch the port # shipping an executable that cannot start, and NO behavioural case would catch # it: every assertion about the module would pass with the script broken. The # script is driven once for real, over a fixture, so the packaged entry point is # covered rather than merely present. # # ============================================================================ # WHAT IS NOT COVERED. # ============================================================================ # # The recipe `inherit ptest' and ships upstream's tests.py, but ptest is NOT # installed on this image and this suite does not run it. The cases here are a # separate statement -- upstream's suite is a doctest+unittest run whose failure # mode is upstream's, while these assert the RFC and the packaging. # # The URI-FRAGMENT form of §6 (`#/foo/0', with %-decoding) is not exercised: this # package resolves the plain string form, and `pct-literal' below asserts that # the two readings are NOT conflated, which is the part that could go wrong here. # # ============================================================================ # WHERE THE ASSERTED VALUES COME FROM. # ============================================================================ # # Every literal below was measured on x86-64 against the sdist the recipe's # SRC_URI sha256 pins (585cee82...d88, verified by digest before measuring), and # again against the bytes of the built python3-jsonpointer_2.4-r0_wasm32.deb. # The control interpreter is CPython 3.10 and the image ships 3.12; that gap is # stated here and in the test plan rather than papered over. The on-target result # is not yet measured -- the batch verify is this port's first run on wasm32-hwjs. P=python3-jsonpointer # THE CANARY, which asserts nothing about python3-jsonpointer. # # 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 MOD=$SITE/jsonpointer.py DI=$SITE/jsonpointer-2.4.dist-info SCRIPT=/usr/bin/jsonpointer PKGLIST=/var/lib/dpkg/info/python3-jsonpointer.list PROBE=$BST_DATA/python3-jsonpointer-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-jsonpointer.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import jsonpointer' 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 is distinguishable from one that # imported out of what this image installed. check $P/site-packages-dir test -d "$SITE" check $P/module-file test -f "$MOD" # ...AND IT IS A FILE, NOT A DIRECTORY. The inverse of 99-python3-elementpath's # `no-flat-module': a release that grew a package would still import and would # still pass many cases below, while the shape this image ships had changed. See # the header. check_eq $P/no-package-dir "absent" \ "$(if [ -d "$SITE/jsonpointer" ]; then echo present; else echo absent; fi)" # NO COMPILED EXTENSION ANYWHERE THE PACKAGE PUT ANYTHING. See the header: on # this image a .so could not be loaded at all, so this is the case that fails a # version bump which grew one -- loudly, here, instead of at `import' later. # # 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 README -- 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 ;; "$SCRIPT") continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ----------------------------------------------------------- the dist-info --- # The installed metadata, which is the second, independent statement of the # version -- `$P/dist-version' below reads it through importlib.metadata, and the # directory name carries it a third time in $DI itself. A package whose module # landed and whose metadata did not would import fine and serve every behavioural # case while reporting no installed version at all. 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.4" cat "$DI/METADATA" # top_level.txt names the ONE importable this package installs. It is what an # `import jsonpointer' has to match, and a wheel that renamed the module would # change it here first. check_out $P/dist-info-top-level "jsonpointer" cat "$DI/top_level.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-jsonpointer 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 a -ptest or -dev # stanza of the same family. sed -n '/^Package: python3-jsonpointer$/,/^$/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 DEPENDENCY THE RECIPE DECLARES, as dpkg recorded it. The recipe's # RDEPENDS is `python3-json' alone, a sub-package of the ported python3 # (BRINTOS-1476), which is why this port waits on nothing. python3-core comes # with the packaging. 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-json "python3-json" cat "$W/dep" # ---------------------------------------------------------------- the script -- # The shipped entry point, which is a `scripts=' file with a REWRITTEN SHEBANG # rather than a generated console_scripts launcher. See the header: this is the # failure no behavioural case would catch, because every assertion about the # module passes with the script unable to start. check $P/script-present test -f "$SCRIPT" check $P/script-executable test -x "$SCRIPT" # The shebang must name an interpreter THIS ROOTFS HAS. `python' unqualified does # not exist here; only `python3' does. The assertion is on the interpreter's # NAME rather than the whole line, because the form is the build's to choose -- # measured on the built .deb it is `#!/usr/bin/env python3', but a direct # `#!/usr/bin/python3' would be equally correct and this port has no business # pinning which. What matters is the name, and that it resolves on this image. head -n 1 "$SCRIPT" > "$W/sb" 2>/dev/null || : > "$W/sb" check_out $P/script-shebang "python3" cat "$W/sb" # ...and the interpreter that shebang names is really reachable, which is the # half the line above cannot state on its own. # # `command -v', NOT `test -x /usr/bin/python3'. The measured shebang goes through # `env', which searches PATH -- so an image with python3 somewhere else on PATH # is correct and a `/usr/bin' case would red on it, while an image whose PATH # lost python3 would PASS a `/usr/bin' case and still ship a script that cannot # start. PATH is what `env' consults, so PATH is what is asserted. check $P/script-interp-exists command -v python3 # ---------------------------------------------------------- the interpreter --- # THE IMPORT, ON ITS OWN, BEFORE THE PROBE. If jsonpointer 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 jsonpointer' # ...and `json' separately, because jsonpointer is useless without the parser # whose output it addresses, and a missing python3-json presents AS a broken # jsonpointer. This case is what tells the two apart. check $P/json-import python3 -c 'import json' # ---------------------------------------------------------------- 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 `version' distinguishable # from `dist_version', and `set_result' from `set_returns_same'. 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. 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 # THE VERSION THE RECIPE PINS, twice and independently: the module's own # `__version__' (which setup.py parses out of jsonpointer.py to set the package # version) and importlib.metadata over the installed dist-info. The two # disagreeing means a mixed install -- a dist-info from one version beside a # module from another. check_eq $P/version "2.4" "$(pv version)" check_eq $P/dist-version "2.4" "$(pv dist_version)" # The module answered out of the file 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 jsonpointer. check_eq $P/module-answered "$MOD" "$(pv module_file)" # THE PUBLIC NAMES, one case each. This module declares NO __all__, so unlike # 99-python3-elementpath there is no whole-surface string to assert -- dir() # would report this module's own imports (`re', `copy', `tee', the Python-2 # shims) and a case over it would be pinning implementation detail. See the # probe's header. for _n in resolve_pointer set_pointer JsonPointer JsonPointerException \ EndOfList escape unescape; do check_eq $P/api-$_n "yes" "$(pv has_$_n)" done # The exception's BASE, which is what a caller's `except' clause names. A release # re-basing it on ValueError or KeyError would leave every case below passing # while breaking every caller that catches it -- including `miss-type' here, # which catches the class by name and would not notice. check_eq $P/exc-base "Exception" "$(pv exc_base)" # ------------------------------------------------------------- the fixture --- # The RFC 6901 §5 document the probe drove, pinned by length and digest, so every # vector below is known to be about the document the RFC prints rather than about # an edited probe. The digest is the load-bearing one: the four vectors that # depend on exotic keys would red individually if the fixture drifted, and this # case is what names the common cause. check_eq $P/fixture-len "142" "$(pv fixture_len)" check_eq $P/fixture-sha "e36aaea0853a2374" "$(pv fixture_sha)" # The ten keys §5 defines, as a JSON array -- not a `|'-joined list, because one # of the keys IS `g|h'. See the probe. check_eq $P/fixture-keys \ '["", " ", "a/b", "c%d", "e^f", "foo", "g|h", "i\\j", "k\"l", "m~n"]' \ "$(pv doc_keys)" # ------------------------------------------------- RFC 6901 §5, ALL TWELVE --- # The standard's own table. Each expectation is the literal §5 prints for that # pointer, rendered as canonical JSON (sorted keys, no incidental whitespace) so # that the whole-document case is an assertion about CONTENT rather than about # dict insertion order. # # THE WHOLE DOCUMENT, addressed by the EMPTY pointer. Not "/" -- that is the next # case and it is a different member. An implementation treating "" as "/" passes # neither. check_eq $P/rfc-empty-whole-doc \ '{"":0," ":7,"a/b":1,"c%d":2,"e^f":3,"foo":["bar","baz"],"g|h":4,"i\\j":5,"k\"l":6,"m~n":8}' \ "$(pv rfc_00)" # An object member, and an ARRAY INDEX into it. `/foo/0' is the only vector in # the table that indexes a list, so it is the one that says a numeric token is # not treated as a dict key. check_eq $P/rfc-foo '["bar","baz"]' "$(pv rfc_01)" check_eq $P/rfc-foo-0 '"bar"' "$(pv rfc_02)" # THE EMPTY-STRING KEY. `/' is not the root: it is one token long and that token # is the empty string, which this document really does define. The pairing with # the case above is the point -- "" and "/" address different things. check_eq $P/rfc-empty-key '0' "$(pv rfc_03)" # `~1' IS `/'. Without the escape this pointer would be the two tokens `a' and # `b'; with it, it is the one key `a/b'. check_eq $P/rfc-tilde1 '1' "$(pv rfc_04)" # `%' IS NOT PERCENT-ENCODING. The plain string form of a pointer is not a URI # fragment (§6 is the form where decoding applies), so `c%d' is three ordinary # characters. `pct-literal' below makes the same point where it is decidable. check_eq $P/rfc-percent '2' "$(pv rfc_05)" # Three keys carrying characters that are special to something -- `^' to a regex, # `|' to a shell, `\' to almost everything -- and to a JSON pointer none of them # are anything at all. They are in the RFC's table for exactly that reason. check_eq $P/rfc-caret '3' "$(pv rfc_06)" check_eq $P/rfc-pipe '4' "$(pv rfc_07)" check_eq $P/rfc-backslash '5' "$(pv rfc_08)" check_eq $P/rfc-quote '6' "$(pv rfc_09)" # A SPACE is an ordinary key character, and `/ ' is a pointer to it. This is the # vector most likely to be lost to an editor stripping trailing whitespace, which # is why the fixture is pinned by digest above. check_eq $P/rfc-space '7' "$(pv rfc_10)" # `~0' IS `~'. The other half of the escape pair, and the input to the order rule # asserted next. check_eq $P/rfc-tilde0 '8' "$(pv rfc_11)" # The whole-document pointer returns the document ITSELF, not a copy -- # `rfc-empty-whole-doc' asserts the content, this asserts the identity, which is # what makes a `set' through a sub-path observable through the original object. check_eq $P/rfc-root-identity "yes" "$(pv root_is_doc)" # ----------------------------------------------- the escape ORDER, §4 --------- # THE ONE RULE WITH A WRONG ANSWER THAT LOOKS RIGHT, and the one thing §5's table # cannot catch: its document holds no key that distinguishes the two orders. See # the header. `/~01' MUST address the literal key `~1'; an implementation that # undid `~0' first would turn it into `/' and return "slash" here while passing # every vector above. check_eq $P/order-tilde01 '"tilde-one"' "$(pv order_tilde01)" # ...and the control: `/~1' really does address `/' in the same document, so the # case above cannot be passing because the `/' key is simply unreachable. check_eq $P/order-tilde1 '"slash"' "$(pv order_tilde1)" # The same rule stated on the primitive, where it is one call rather than a # lookup. unescape('~01') is '~1' -- NOT '/'. check_eq $P/unescape-order "~1" "$(pv unescape_tilde01)" # escape() is the inverse, and the round trip is what says the two agree. The # third is the one that matters: the key `~1' must escape to `~01', which is # precisely the token `order-tilde01' resolves. check_eq $P/escape-slash "a~1b" "$(pv escape_slash)" check_eq $P/escape-tilde "m~0n" "$(pv escape_tilde)" check_eq $P/escape-tilde-one "~01" "$(pv escape_tilde_one)" # ------------------------------------------- `%' NOT DECODED, decidably ------- # `rfc-percent' shows `%' surviving; these show it is not DECODED. The document # holds both `a%20b' and `a b', so the two readings return different values and # an implementation that decoded would return 2 for the first. check_eq $P/pct-literal '1' "$(pv pct_literal)" check_eq $P/pct-space '2' "$(pv pct_space)" # ------------------------------------------------ missing members, defaults --- # A pointer naming a member that is not there RAISES; the same pointer with a # default RETURNS the default. Both halves, because a build in which the default # swallowed a real error would pass the second on its own. check_eq $P/miss-raised "yes" "$(pv miss_raised)" check_eq $P/miss-type "JsonPointerException" "$(pv miss_type)" # The sentinel is a string no part of the document holds: a default of None or 0 # would be indistinguishable from a value the document really carries. check_eq $P/miss-default '"DEFAULT"' "$(pv miss_default)" # ...and a default does NOT mask a well-formed hit. Without this case, an # implementation that returned the default unconditionally would pass the one # above. check_eq $P/hit-with-default '"bar"' "$(pv hit_with_default)" # THE MESSAGES, for the three errors whose message is a CONSTANT. The # member-not-found message interpolates the document's repr -- `member 'nope' not # found in {...}' -- so it is deliberately NOT asserted: a case over it would pin # dict ordering and the whole fixture's repr into an assertion about a lookup. # These three carry no repr. check_eq $P/err-index-oob "index '9' is out of bounds" "$(pv err_index_oob)" check_eq $P/err-index-notint "'bar' is not a valid sequence index" "$(pv err_index_notint)" # A pointer that does not start with `/' is MALFORMED, and is refused when the # JsonPointer is CONSTRUCTED rather than when it is resolved. Both failures # surface as JsonPointerException, so the message is what separates a bad pointer # from a pointer that found nothing. check_eq $P/err-no-leading-slash "Location must start with /" "$(pv err_no_leading_slash)" # ------------------------------------------------------- `-', end of list ----- # §4's end-of-list token, which exists for JSON Patch's `add' and therefore does # NOT raise -- it returns a marker. That is the surprising half: a reader would # expect the out-of-bounds error `err-index-oob' above shows, and this case is # what records that `-' is not that. check_eq $P/dash-end-of-list "EndOfList" "$(pv dash_type)" # --------------------------------------------- the pointer object, round trip -- # get_parts() returns UNESCAPED parts and .path re-emits the ESCAPED token # string. The parts include one member with a `/' and one with a `~', which are # the only two characters the round trip can lose. check_eq $P/parts "a/b|m~n|0" "$(pv parts)" check_eq $P/path "/a~1b/m~0n/0" "$(pv path)" check_eq $P/str "/a~1b/m~0n/0" "$(pv str)" check_eq $P/from-parts "/a~1b/m~0n/0" "$(pv from_parts)" # ...and the rebuilt pointer resolves to the same member the literal one does, # which is the round trip asserted as BEHAVIOUR rather than as a string. A # from_parts that escaped correctly into an unusable pointer would pass the case # above and red here. check_eq $P/from-parts-resolves '1' "$(pv from_parts_resolves)" # ------------------------------------------------------------------- set ------ # The addressing half of JSON Patch is only useful if it can WRITE. What goes # wrong is not the write but the COLLATERAL -- a rebuild that replaces the # containing object and drops its siblings -- so the fixture carries a sibling # key and a second array element the pointer does not name, and the WHOLE # document is asserted rather than just the written value. check_eq $P/set-result \ '{"foo":{"arr":[{"p":55},{"q":9}],"keep":"untouched"},"top":"also-untouched"}' \ "$(pv set_result)" # inplace=True is the default, so the ORIGINAL object must show the write. This # is what makes set_pointer usable through a reference somebody else holds, and # it is a different statement from the returned value. check_eq $P/set-inplace \ '{"foo":{"arr":[{"p":55},{"q":9}],"keep":"untouched"},"top":"also-untouched"}' \ "$(pv set_inplace)" check_eq $P/set-returns-same "yes" "$(pv set_returns_same)" # inplace=False is the other half, and the case that matters is the ORIGINAL # being untouched -- a copy that shared its inner dicts would pass the result # case and red here. check_eq $P/set-copy-result '{"a":{"b":99},"keep":2}' "$(pv set_copy_result)" check_eq $P/set-copy-original '{"a":{"b":1},"keep":2}' "$(pv set_copy_original)" # ------------------------------------------------------- the shipped script --- # The packaged entry point driven ONCE, for real, over a fixture -- so the port # covers the script rather than merely asserting it is on disk. See the header: # this is a `scripts=' file whose shebang was rewritten, and a broken one would # leave every case above passing. # # The fixture is written to tmpfs, not read from the rootfs, for the dup2(2) # reason above (brintos/linux#73). printf '{"foo": ["bar", "baz"], "a/b": 1}\n' > "$W/doc.json" check_out $P/script-resolve '"bar"' "$SCRIPT" /foo/0 "$W/doc.json" # ...and one ESCAPED pointer through the script, which is what says the CLI # parses the same grammar the module does rather than splitting on `/' itself. check_out $P/script-escaped '1' "$SCRIPT" /a~1b "$W/doc.json" # The script's own version flag, which reads the module's __version__ -- a third, # independent statement of the version, through the entry point. check_out $P/script-version "jsonpointer 2.4" "$SCRIPT" --version # The probe ran to the end. Every case above greps a key, so a probe that died # halfway would leave the remaining keys absent and each case would red with # got:'' -- this case is the one that says the cause was a truncated run. check_eq $P/probe-complete "yes" "$(pv probe_complete)" rm -rf "$W"