# BST-TIMEOUT: 300 . "$BST_LIB" # rapidjson 1.1.0+git (SRCREV 0ccdbf36) -- Tencent's SAX/DOM JSON library, the # one chosen when allocation control and speed matter more than API comfort. # It is HEADER-ONLY: the port ships 37 headers, RapidJSON.pc and the # find_package(RapidJSON) cmake package, and nothing else. No archive, no shared # object, no CLI. # # WHY THIS IMAGE CARRIES FIVE JSON PARSERS, AND WHAT MAKES THIS ONE NOT A # DUPLICATE. cjson, json-c and libfastjson (all at 81) are C libraries with a # malloc-per-node DOM; jsoncpp is C++ but DOM-only and allocates freely; # nlohmann-json (82) is header-only C++ with an STL-container interface. What # none of them has is rapidjson's combination of a PULL/PUSH SAX reader that # never materialises the document, IN-SITU parsing that decodes strings back # over the caller's own buffer, and a CALLER-SUPPLIED POOL ALLOCATOR -- the # three things a consumer picks rapidjson for on a memory-bounded target. The # probe's sax/*, insitu-* and pool-* cases are the whole of that difference and # are the reason the overlap is a deliberate answer rather than an accident. # # WHAT MAKES A HEADER-ONLY PORT DIFFERENT TO TEST, AND WHY MOST OF THE WEIGHT IS # IN THE PROBE. For a compiled port, the shipped archive is itself evidence that # this toolchain read the sources and emitted code for them. A header-only port # ships bytes that no compiler on this arm need ever have looked at, so "the # files are present" is not merely thin coverage -- it is compatible with # headers that do not compile at all. The recipe therefore builds # rapidjson-probe from ${S}/include during the port, which makes do_compile the # first consumer, and the probe then RUNS here. The two halves answer different # questions and neither substitutes for the other: # # * the shell half (sections 1-3) asserts the PAYLOAD -- that 37 headers # arrived, that the tree's own #include graph closes inside itself, and that # the .pc and the cmake package agree with the header about the version; # * the probe (section 4) asserts the LIBRARY -- parse, serialise, refuse. # # THE ERROR CHANNEL IS THE THING MOST WORTH TESTING. rapidjson never throws: # RAPIDJSON_PARSE_ERROR_NORETURN is a customisation point a caller MAY point at # a throw, and this port does not, so ParseResult -- a code and a byte offset -- # is the entire error contract. With no second channel, a parser that quietly # accepted a malformed document, or that collapsed every failure to one code, # is indistinguishable from a working one to any caller that only asks "did it # parse". The probe's bad-* group therefore pins fourteen refusals across ten # DIFFERENT codes AND the offset of each, with a valid document as the negative # control -- every one of those offsets measured against an x86-64 build of the # same headers rather than predicted. # # Style follows 74-pugixml and 82-nlohmann-json: files are read ONCE into shell # variables and every text assertion is a fork-free `case` glob against them. # The runner's per-suite bound is a budget in process spawns, not in cases, and # ~150 check_out spawns would cost more than the assertions. inc=/usr/include/rapidjson pc=/usr/lib/pkgconfig/RapidJSON.pc cm=/usr/lib/cmake/RapidJSON PT=/usr/libexec/rapidjson-tests nl=' ' # hasfile -- ok iff is a non-empty regular file. # has -- ok iff contains the literal . # lacks -- the inverse; used where ABSENCE is the claim. # hasline -- ok iff has as a WHOLE line (a # substring test would let internal/regex.h # satisfy a lookup for regex.h). hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } lacks() { case "$3" in *"$2"*) bst_notok "$1" "present but should not be: $2" ;; *) bst_ok "$1" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "missing: $2" ;; esac; } # --------------------------------------------------------------------------- # 1. The shipped header tree. # # One find for the whole inventory; every presence case below is a glob against # it rather than its own stat. files=$(find "$inc" -name '*.h' 2>/dev/null | sed "s|^$inc/||" | sort) nfiles=$(printf '%s\n' "$files" | grep -c .) # 37 headers is the full 1.1.0 install. The floor guards the closure check # below, which an empty find would otherwise pass vacuously. if [ "$nfiles" -ge 37 ]; then bst_ok rapidjson/tree-count else bst_notok rapidjson/tree-count "installed=$nfiles want>=37"; fi # The headers a consumer names directly. document.h is the DOM, reader.h/writer.h # the SAX halves, prettywriter.h the indenting serialiser, pointer.h RFC 6901, # schema.h JSON Schema, and fwd.h the forward declarations that let a consumer's # own header mention the types without pulling the parser in. for f in rapidjson.h document.h reader.h writer.h prettywriter.h pointer.h \ schema.h fwd.h allocators.h encodings.h stream.h stringbuffer.h \ memorybuffer.h memorystream.h encodedstream.h cursorstreamwrapper.h \ filereadstream.h filewritestream.h istreamwrapper.h ostreamwrapper.h; do hasline "rapidjson/hdr-${f%.h}" "$f" "$files" done # error/: the two headers that carry the ONLY error channel this library has. # en.h in particular is a separate packaging risk -- a caller that reports # failures to a user needs it, and losing it would not break a single parse. hasline rapidjson/hdr-error "error/error.h" "$files" hasline rapidjson/hdr-error-en "error/en.h" "$files" # internal/: the implementation the public headers are assembled from. It is not # private in any packaging sense -- document.h includes it -- so a file missing # here breaks the build of a consumer who only ever wrote #include # . dtoa/itoa/diyfp/biginteger/strtod are rapidjson's OWN # number conversion, which is why the probe can compare serialised bytes exactly: # nothing here defers to the libc. for f in internal/biginteger.h internal/clzll.h internal/diyfp.h internal/dtoa.h \ internal/ieee754.h internal/itoa.h internal/meta.h internal/pow10.h \ internal/regex.h internal/stack.h internal/strfunc.h internal/strtod.h \ internal/swap.h; do b=${f##*/} hasline "rapidjson/hdr-${b%.h}" "$f" "$files" done # msinttypes/: the MSVC shim. It ships on every arm and is dead code here, which # is worth pinning rather than leaving as an unexplained pair of files. hasline rapidjson/hdr-msinttypes-stdint "msinttypes/stdint.h" "$files" hasline rapidjson/hdr-msinttypes-inttypes "msinttypes/inttypes.h" "$files" # THE COMPLETENESS CHECK, and the single most valuable case in the shell half: # every rapidjson include the installed tree makes must resolve INSIDE the # installed tree, so the port is self-contained and a consumer's first #include # compiles. The reference list is read off the target rather than written here, # so a header added or renamed upstream is covered automatically, and this is # the only case that would catch a FILES glob or a packaging split that dropped # a file nobody happened to name above. # # rapidjson includes RELATIVELY -- "stream.h", "internal/stack.h", # "../rapidjson.h" -- which is why this cannot be the one-line lookup # 82-nlohmann-json uses: each path has to be resolved against the DIRECTORY OF # THE HEADER THAT MAKES IT before it can be looked up. Two spellings also # appear, and both are real: # # "internal/stack.h" -- relative to the including header # "rapidjson/reader.h" -- the consumer-facing form, resolved against the # include-path root (/usr/include), so the prefix is # stripped and the lookup starts at the tree top # # One grep, then pure parameter expansion: the resolution loop runs ~76 times # and forks NOTHING, because the per-suite bound is a budget in process spawns. # NO --include HERE, DELIBERATELY: busybox grep does not implement it and # answers `unrecognized option`, which this loop would read as an empty tree and # report as a vacuous pass. The installed tree is all *.h, so the filter buys # nothing anyway. (Caught by running this suite under `busybox ash` before it # shipped -- the README asks for exactly that, and it is the only reason this is # not a green suite that measures nothing on target.) incs=$(grep -rn -e '#include[[:space:]]*"[^"]*"' "$inc" 2>/dev/null | sed "s|^$inc/||") nrefs=0; nres=0; miss= IFS="$nl" for line in $incs; do f=${line%%:*}; rest=${line#*:}; rest=${rest#*:} p=${rest#*\"}; p=${p%%\"*} # rapidjson.h and reader.h carry `#include "rapidjson/..."` inside DOC # COMMENTS showing a consumer what to write; it names no file and is skipped. case "$p" in *...*) continue ;; esac case "$p" in rapidjson/*) p=${p#rapidjson/}; d= ;; *) d=${f%/*}; [ "$d" = "$f" ] && d= ;; esac while :; do case "$p" in ../*) p=${p#../}; d=${d%/*}; [ "$d" = "${d#*/}" ] && d= ;; *) break ;; esac done if [ -n "$d" ]; then t="$d/$p"; else t="$p"; fi nrefs=$((nrefs+1)) case "$nl$files$nl" in *"$nl$t$nl"*) nres=$((nres+1)) ;; *) miss="$miss $t" ;; esac done unset IFS # The floor keeps an empty or failed grep from passing this vacuously -- 76 # quoted includes were measured on the 1.1.0 tree, 2026-08-20. if [ "$nrefs" -ge 70 ] && [ -z "$miss" ]; then bst_ok rapidjson/include-closure else bst_notok rapidjson/include-closure "refs=$nrefs resolved=$nres unresolved:$miss"; fi # --------------------------------------------------------------------------- # 2. Identity and configuration, read off the header. # # Read once; every assertion below is a glob over the same 25 kB. r=$(cat "$inc/rapidjson.h" 2>/dev/null) hasfile rapidjson/rapidjson-h-present "$inc/rapidjson.h" has rapidjson/version-major "#define RAPIDJSON_MAJOR_VERSION 1" "$r" has rapidjson/version-minor "#define RAPIDJSON_MINOR_VERSION 1" "$r" has rapidjson/version-patch "#define RAPIDJSON_PATCH_VERSION 0" "$r" maj=${r#*#define RAPIDJSON_MAJOR_VERSION }; maj=${maj%%[!0-9]*} min=${r#*#define RAPIDJSON_MINOR_VERSION }; min=${min%%[!0-9]*} pat=${r#*#define RAPIDJSON_PATCH_VERSION }; pat=${pat%%[!0-9]*} hver="$maj.$min.$pat" check_eq rapidjson/version-triple "1.1.0" "$hver" # The customisation points a consumer is expected to override, each of which # changes the library's contract. They must all be PRESENT as overridable # #ifndef blocks -- a header that hardcoded any of them would silently ignore a # consumer's -D. has rapidjson/cfg-assert "#ifndef RAPIDJSON_ASSERT" "$r" has rapidjson/cfg-has-stdstring "#ifndef RAPIDJSON_HAS_STDSTRING" "$r" has rapidjson/cfg-endian "#ifndef RAPIDJSON_ENDIAN" "$r" has rapidjson/cfg-sizetype "typedef unsigned SizeType" "$r" has rapidjson/cfg-align "#ifndef RAPIDJSON_ALIGN" "$r" # THE PORT'S PREMISE, asserted as a property of the shipped headers rather than # of the build. rapidjson does not throw: the only `throw` in the tree is inside # the documentation comment in reader.h that shows a caller how to point # RAPIDJSON_PARSE_ERROR_NORETURN at one. That is why this port needs no # exceptions-off switch, unlike pugixml (-DPUGIXML_NO_EXCEPTIONS) and jsoncpp # (-DJSON_USE_EXCEPTION=0) -- and if a future version started throwing, THIS is # the case that says so, on an arm that has no unwinder to carry it. # The tree contains the word `throw` exactly THREE times, and all three are # inside documentation comments -- rapidjson.h:619 and reader.h:78 describing # the customisation point, and reader.h:84 the `throw ParseException(...)` line # of the \code example showing a caller how to use it. Read and verified # individually, 2026-08-20. The count is pinned rather than asserted to be zero # because zero is not the truth and a case that states an untruth is one nobody # can maintain: if a future version adds a REAL throw the count moves and this # goes red for a human to look at, which is the whole job here. # # The authoritative evidence is not this case at all -- it is that the probe is # COMPILED with -fno-exceptions and carries an #error that refuses the # translation unit if exceptions are ever back on. This one covers the headers # the probe does not happen to instantiate. throws=$(grep -rho 'throw ' "$inc" 2>/dev/null | grep -c .) check_eq rapidjson/throw-sites-all-documentation "3" "$throws" # is named in exactly ONE file, reader.h, and there too only inside # the same \code example -- it is what a caller would have to include to define # ParseException, not something the library pulls in. Pinned by FILE rather than # by count of zero, for the same reason as above: the truth is one, and a case # asserting zero would be red from the day it was written. excepts=$(grep -rl '#include ' "$inc" 2>/dev/null | sed "s|^$inc/||") check_eq rapidjson/stdexcept-doc-only "reader.h" "$excepts" # The error enum -- the codes the probe's bad-* group pins by value. Their ORDER # is API (they are compared and stored as integers by callers), so the first and # last are asserted by name to catch a reordering that would silently renumber # every code a consumer has recorded. e=$(cat "$inc/error/error.h" 2>/dev/null) has rapidjson/err-enum "enum ParseErrorCode" "$e" has rapidjson/err-none-is-zero "kParseErrorNone = 0" "$e" has rapidjson/err-parseresult "struct ParseResult {" "$e" has rapidjson/err-iserror "bool IsError()" "$e" has rapidjson/err-offset "size_t Offset()" "$e" for c in kParseErrorDocumentEmpty kParseErrorDocumentRootNotSingular \ kParseErrorValueInvalid kParseErrorObjectMissName \ kParseErrorObjectMissColon kParseErrorObjectMissCommaOrCurlyBracket \ kParseErrorArrayMissCommaOrSquareBracket \ kParseErrorStringUnicodeEscapeInvalidHex \ kParseErrorStringUnicodeSurrogateInvalid kParseErrorStringEscapeInvalid \ kParseErrorStringMissQuotationMark kParseErrorStringInvalidEncoding \ kParseErrorNumberTooBig kParseErrorNumberMissFraction \ kParseErrorNumberMissExponent kParseErrorTermination \ kParseErrorUnspecificSyntaxError; do has "rapidjson/err-$c" "$c" "$e" done has rapidjson/err-en-table "GetParseError_En" "$(cat "$inc/error/en.h" 2>/dev/null)" # The parse-mode flags. Each is a distinct behaviour the probe exercises at run # time; asserted here as well because a flag REMOVED upstream would turn a # consumer's template argument into a compile error rather than a red case. d=$(cat "$inc/reader.h" 2>/dev/null) for f in kParseNoFlags kParseInsituFlag kParseValidateEncodingFlag \ kParseIterativeFlag kParseStopWhenDoneFlag kParseFullPrecisionFlag \ kParseCommentsFlag kParseNumbersAsStringsFlag \ kParseTrailingCommasFlag kParseNanAndInfFlag; do has "rapidjson/flag-$f" "$f" "$d" done has rapidjson/sax-basehandler "struct BaseReaderHandler" "$d" has rapidjson/sax-reader "class GenericReader" "$d" # THE CUSTOMISATION POINT THAT DEFINES THIS PORT'S ERROR CONTRACT. reader.h -- # not rapidjson.h -- declares RAPIDJSON_PARSE_ERROR_NORETURN behind an #ifndef, # and its default body sets the ParseResult and returns. A caller MAY redefine # it to throw; this port does not, which is why ParseResult is the whole of the # error channel and why the probe's bad-* group is the centre of this suite. has rapidjson/cfg-noreturn "#ifndef RAPIDJSON_PARSE_ERROR_NORETURN" "$d" has rapidjson/cfg-noreturn-default "#define RAPIDJSON_PARSE_ERROR_NORETURN" "$d" # The DOM's public surface, one defining declaration each. doc=$(cat "$inc/document.h" 2>/dev/null) has rapidjson/dom-genericvalue "class GenericValue" "$doc" has rapidjson/dom-genericdocument "class GenericDocument" "$doc" has rapidjson/dom-value-typedef "typedef GenericValue > Value" "$doc" has rapidjson/dom-document-typedef "typedef GenericDocument > Document" "$doc" has rapidjson/dom-parseinsitu "ParseInsitu(" "$doc" has rapidjson/dom-getparseerror "GetParseError()" "$doc" has rapidjson/dom-geterroroffset "GetErrorOffset()" "$doc" # RemoveMember vs EraseMember -- the pair whose difference the probe measures. has rapidjson/dom-removemember "RemoveMember(" "$doc" has rapidjson/dom-erasemember "EraseMember(" "$doc" # The allocators, which are the reason to choose this library on a bounded # target: a pool the caller owns, and the crt fallback. a=$(cat "$inc/allocators.h" 2>/dev/null) has rapidjson/alloc-crt "class CrtAllocator" "$a" has rapidjson/alloc-pool "class MemoryPoolAllocator" "$a" # schema.h ships rapidjson's OWN regex engine rather than std::regex, which is # what makes JSON Schema's `pattern` keyword usable on a static C++ arm at all. s=$(cat "$inc/schema.h" 2>/dev/null) has rapidjson/schema-doc "class GenericSchemaDocument" "$s" has rapidjson/schema-validator "class GenericSchemaValidator" "$s" has rapidjson/schema-internal-regex "RAPIDJSON_SCHEMA_USE_INTERNALREGEX" "$s" has rapidjson/schema-regex-header "internal/regex.h" "$s" # --------------------------------------------------------------------------- # 3. The two integration routes. # # Header-only means the whole answer either route can give is a -I flag, so what # is being tested is that each states the RIGHT one -- an absolute /usr/include # for pkg-config, and a path relative to the cmake file's own location for # find_package. hasfile rapidjson/pc-present "$pc" p=$(cat "$pc" 2>/dev/null) has rapidjson/pc-name "Name: RapidJSON" "$p" has rapidjson/pc-description "Description: A fast JSON parser" "$p" has rapidjson/pc-cflags "Cflags: -I\${includedir}" "$p" has rapidjson/pc-includedir "includedir=/usr/include" "$p" # @PLACEHOLDER@ left unsubstituted is the failure mode of a .pc.in that cmake # configured with the wrong variable set, and it is invisible until a consumer # builds. lacks rapidjson/pc-no-placeholder "@" "$p" pver=${p#*Version: }; pver=${pver%%[!0-9.]*} check_eq rapidjson/pc-version-agrees "$hver" "$pver" hasfile rapidjson/cmake-config "$cm/RapidJSONConfig.cmake" hasfile rapidjson/cmake-version "$cm/RapidJSONConfigVersion.cmake" cc=$(cat "$cm/RapidJSONConfig.cmake" 2>/dev/null) cv=$(cat "$cm/RapidJSONConfigVersion.cmake" 2>/dev/null) # The imported target is INTERFACE with no IMPORTED_LOCATION, because there is # no library file to point at. A target that acquired one would mean something # got compiled and the port is not what this suite describes. has rapidjson/cmake-interface-target "add_library(rapidjson INTERFACE IMPORTED)" "$cc" has rapidjson/cmake-include-dirs "INTERFACE_INCLUDE_DIRECTORIES" "$cc" lacks rapidjson/cmake-header-only "IMPORTED_LOCATION" "$cc" cver=${cv#*PACKAGE_VERSION \"}; cver=${cver%%\"*} check_eq rapidjson/cmake-version-agrees "$hver" "$cver" # THE ONE THAT IS NOT A FORMALITY. Upstream installs RapidJSONConfig.cmake TWICE # into this same directory, from two install() rules six lines apart # (CMakeLists.txt:218 and :222): the install-tree variant, whose include path is # relative to the cmake file, and then the BUILD-TREE variant, which names the # build host's source directory. Which survives is settled by cmake's own # file-time comparison, and the install-tree one wins because it is configured # second -- so the shipped file is correct. These three cases are what make that # a checked fact rather than a lucky one: if the ordering ever flips, the image # would ship a cmake package pointing a consumer at # /home//build/.../git/include, which does not exist on the target and # fails at THEIR configure step, not ours. has rapidjson/cmake-include-relative "\${RapidJSON_CMAKE_DIR}/../../../include" "$cc" has rapidjson/cmake-source-dir-empty 'set( RapidJSON_SOURCE_DIR "")' "$cc" lacks rapidjson/cmake-no-buildpath "/home/" "$cc" # Header-only guard: nothing is compiled, so nothing may ship in libdir. A stray # archive here would mean the recipe built a library on an arm that has no # consumer for one. noglob_ok() { # ok iff nothing matches the glob already expanded into "$@" _id=$1; shift if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi } set -- /usr/lib/librapidjson*; noglob_ok rapidjson/no-library "$@" set -- /usr/lib/libRapidJSON*; noglob_ok rapidjson/no-library-caps "$@" # --------------------------------------------------------------------------- # 4. The probe -- the library actually running. # # Built by the rapidjson recipe from the very headers that ship, in the same # build, so it cannot drift from what it tests. Installed 0755 in libexecdir and # needing no staging: it opens the sample read-only and writes nothing, so the # read-only rootfs at browser runtime is not in its way. `require' rather than a # hard failure -- an image that takes rapidjson-dev without rapidjson-tests # legitimately has nothing to run here, and that is a coverage prompt (add the # package), not a broken port. require "$PT/rapidjson-probe" rapidjson/probe-present hasfile rapidjson/sample-present "$PT/rapidjson-sample.json" # Run ONCE and keep both the output and the status: on this runtime the wasm # module's start-up dominates the cost of the whole suite. out=$("$PT/rapidjson-probe" "$PT" 2>&1); prc=$? bst_ok rapidjson/probe-ran # identity and the arm-dependent configuration has rapidjson/run-version-major "RJ-version-major OK" "$out" has rapidjson/run-version-minor "RJ-version-minor OK" "$out" has rapidjson/run-version-patch "RJ-version-patch OK" "$out" has rapidjson/run-version-string "RJ-version-string OK" "$out" has rapidjson/run-endian-little "RJ-endian-little OK" "$out" has rapidjson/run-sizetype-width "RJ-sizetype-width OK" "$out" has rapidjson/run-pointer-packing "RJ-pointer-packing-fits-arm OK" "$out" has rapidjson/run-no-stdstring "RJ-no-stdstring OK" "$out" # the bundled document, parsed and walked has rapidjson/run-sample-load "RJ-sample-load OK" "$out" has rapidjson/run-dom-parse "RJ-dom-parse OK" "$out" has rapidjson/run-dom-root-object "RJ-dom-root-object OK" "$out" has rapidjson/run-dom-root-members "RJ-dom-root-members OK" "$out" has rapidjson/run-dom-name "RJ-dom-name OK" "$out" has rapidjson/run-dom-enabled "RJ-dom-enabled OK" "$out" has rapidjson/run-dom-absent-null "RJ-dom-absent-null OK" "$out" has rapidjson/run-dom-has-member "RJ-dom-has-member OK" "$out" has rapidjson/run-dom-no-member "RJ-dom-no-member OK" "$out" has rapidjson/run-dom-find-member-end "RJ-dom-find-member-end OK" "$out" # THE INTEGER WIDTH LADDER. Which of Is{Int,Uint,Int64,Uint64} answers true is a # property of the VALUE, not of the target's word size, so these must hold # identically on wasm32 and x86-64 -- and rapidjson stores an int64 in a union # whose layout this arm has never exercised before this port. has rapidjson/run-dom-int "RJ-dom-int OK" "$out" has rapidjson/run-dom-int-is-uint "RJ-dom-int-is-uint OK" "$out" has rapidjson/run-dom-negative "RJ-dom-negative OK" "$out" has rapidjson/run-dom-negative-not-uint "RJ-dom-negative-not-uint OK" "$out" has rapidjson/run-dom-uint-over-int "RJ-dom-uint-over-int OK" "$out" has rapidjson/run-dom-uint-over-int-not-int "RJ-dom-uint-over-int-not-int OK" "$out" has rapidjson/run-dom-int64 "RJ-dom-int64 OK" "$out" has rapidjson/run-dom-int64-not-int "RJ-dom-int64-not-int OK" "$out" has rapidjson/run-dom-uint64 "RJ-dom-uint64 OK" "$out" has rapidjson/run-dom-uint64-not-int64 "RJ-dom-uint64-not-int64 OK" "$out" has rapidjson/run-dom-zero "RJ-dom-zero OK" "$out" # doubles, including the negative zero whose sign a naive parse-then-print loses has rapidjson/run-dom-half "RJ-dom-half OK" "$out" has rapidjson/run-dom-pi "RJ-dom-pi OK" "$out" has rapidjson/run-dom-small "RJ-dom-small OK" "$out" has rapidjson/run-dom-large "RJ-dom-large OK" "$out" has rapidjson/run-dom-neg-zero "RJ-dom-neg-zero OK" "$out" has rapidjson/run-dom-int-as-double "RJ-dom-int-as-double OK" "$out" # strings: the \u decoder, asserted against the document rather than a constant has rapidjson/run-dom-plain "RJ-dom-plain OK" "$out" has rapidjson/run-dom-escapes "RJ-dom-escapes OK" "$out" has rapidjson/run-dom-escapes-len "RJ-dom-escapes-len OK" "$out" has rapidjson/run-dom-escaped-bmp "RJ-dom-escaped-bmp OK" "$out" has rapidjson/run-dom-escaped-equals-raw "RJ-dom-escaped-equals-raw OK" "$out" has rapidjson/run-dom-surrogate "RJ-dom-surrogate OK" "$out" has rapidjson/run-dom-surrogate-len "RJ-dom-surrogate-len OK" "$out" has rapidjson/run-dom-utf8 "RJ-dom-utf8 OK" "$out" has rapidjson/run-dom-empty-str-len "RJ-dom-empty-str-len OK" "$out" # arrays, iteration and nesting has rapidjson/run-dom-list-size "RJ-dom-list-size OK" "$out" has rapidjson/run-dom-list-1 "RJ-dom-list-1 OK" "$out" has rapidjson/run-dom-list-5 "RJ-dom-list-5 OK" "$out" has rapidjson/run-dom-list-6-1 "RJ-dom-list-6-1 OK" "$out" has rapidjson/run-dom-list-iterate "RJ-dom-list-iterate OK" "$out" has rapidjson/run-dom-member-iterate "RJ-dom-member-iterate OK" "$out" has rapidjson/run-dom-empty-object "RJ-dom-empty-object OK" "$out" has rapidjson/run-dom-empty-array "RJ-dom-empty-array OK" "$out" has rapidjson/run-dom-deep "RJ-dom-deep OK" "$out" # THE BYTE-EXACT SERIALIZATION. rapidjson implements its own Grisu2 dtoa and its # own itoa rather than calling the libc, so the exact bytes a number serialises # to are the library's own arithmetic -- and are therefore the one thing in this # port that a wasm32 miscompilation could get subtly, silently wrong. Every # other numeric case above would still pass with a value that is merely close. has rapidjson/run-write-accept "RJ-write-accept OK" "$out" has rapidjson/run-write-complete "RJ-write-complete OK" "$out" has rapidjson/run-write-compact-bytes "RJ-write-compact-bytes OK" "$out" has rapidjson/run-write-compact-len "RJ-write-compact-len OK" "$out" has rapidjson/run-write-roundtrip-bytes "RJ-write-roundtrip-bytes OK" "$out" has rapidjson/run-write-pretty-accept "RJ-write-pretty-accept OK" "$out" has rapidjson/run-write-pretty-head "RJ-write-pretty-head OK" "$out" has rapidjson/run-write-pretty-tail "RJ-write-pretty-tail OK" "$out" has rapidjson/run-write-pretty-roundtrip "RJ-write-pretty-roundtrip OK" "$out" has rapidjson/run-write-hand-bytes "RJ-write-hand-bytes OK" "$out" has rapidjson/run-write-incomplete "RJ-write-incomplete-detected OK" "$out" # SAX, whose event tally is cross-checked against the DOM's own counts has rapidjson/run-sax-parse "RJ-sax-parse OK" "$out" has rapidjson/run-sax-obj-balanced "RJ-sax-obj-balanced OK" "$out" has rapidjson/run-sax-arr-balanced "RJ-sax-arr-balanced OK" "$out" has rapidjson/run-sax-objects "RJ-sax-objects OK" "$out" has rapidjson/run-sax-arrays "RJ-sax-arrays OK" "$out" has rapidjson/run-sax-first-key "RJ-sax-first-key OK" "$out" has rapidjson/run-sax-root-members "RJ-sax-root-members OK" "$out" has rapidjson/run-sax-nulls "RJ-sax-nulls OK" "$out" has rapidjson/run-sax-bools "RJ-sax-bools OK" "$out" has rapidjson/run-sax-ints "RJ-sax-ints OK" "$out" has rapidjson/run-sax-uints "RJ-sax-uints OK" "$out" has rapidjson/run-sax-int64s "RJ-sax-int64s OK" "$out" has rapidjson/run-sax-uint64s "RJ-sax-uint64s OK" "$out" has rapidjson/run-sax-doubles "RJ-sax-doubles OK" "$out" has rapidjson/run-sax-strings "RJ-sax-strings OK" "$out" has rapidjson/run-sax-keys "RJ-sax-keys OK" "$out" has rapidjson/run-sax-insitu-parse "RJ-sax-insitu-parse OK" "$out" has rapidjson/run-sax-insitu-keys "RJ-sax-insitu-keys OK" "$out" has rapidjson/run-sax-terminate-error "RJ-sax-terminate-error OK" "$out" has rapidjson/run-sax-terminate-code "RJ-sax-terminate-code OK" "$out" # THE ERROR CHANNEL. Fourteen malformed documents across ten distinct codes, # each with its byte offset, plus a valid document as the negative control -- # without which every case here would still pass against a parser that reported # an error unconditionally. for c in empty blank root-not-singular miss-colon miss-curly miss-square \ miss-name value-invalid miss-quote escape-invalid bad-hex \ lone-surrogate miss-fraction miss-exponent; do has "rapidjson/run-bad-$c-refused" "RJ-bad-$c-refused OK" "$out" has "rapidjson/run-bad-$c-code" "RJ-bad-$c-code OK" "$out" has "rapidjson/run-bad-$c-offset" "RJ-bad-$c-offset OK" "$out" has "rapidjson/run-bad-$c-doc-agrees" "RJ-bad-$c-doc-agrees OK" "$out" done has rapidjson/run-err-en-none "RJ-err-en-none OK" "$out" has rapidjson/run-err-en-empty "RJ-err-en-empty OK" "$out" has rapidjson/run-err-en-colon "RJ-err-en-colon OK" "$out" has rapidjson/run-bad-control-clean "RJ-bad-control-clean OK" "$out" # The modes rapidjson is chosen for. has rapidjson/run-insitu-parse "RJ-insitu-parse OK" "$out" has rapidjson/run-insitu-members "RJ-insitu-members OK" "$out" has rapidjson/run-insitu-surrogate "RJ-insitu-surrogate OK" "$out" has rapidjson/run-insitu-points-into-buffer "RJ-insitu-points-into-buffer OK" "$out" has rapidjson/run-iterative-parse "RJ-iterative-parse OK" "$out" has rapidjson/run-iterative-deep "RJ-iterative-deep OK" "$out" has rapidjson/run-deep-recursive "RJ-deep-recursive OK" "$out" has rapidjson/run-deep-iterative "RJ-deep-iterative OK" "$out" # THE TWO NUMBER PARSERS, AND THE ONE MEASURED DIFFERENCE BETWEEN THE ARMS. # rapidjson's default is a fast path upstream documents as accurate to within # 3 ULP; kParseFullPrecisionFlag selects an exact one. Measured 2026-08-20, the # fast path lands on a DIFFERENT double on the two architectures for the same # input: "pi": 3.1415926535897931 gives 400921FB54442D18 on x86-64 and # 400921FB54442D17, one ULP below, here. That is inside the documented tolerance # -- not a defect, and not a miscompilation -- but it is why nothing below pins # the fast path's exact bits or the bytes it serialises to: such a case would be # asserting a property of the machine it was written on, and would ship a suite # with a permanent expected red. # # What IS asserted is the contract, which holds on any conforming arm: the exact # parser lands on the true nearest double, the fast parser is within 3 ULP of # it, and whatever either produced round-trips to the identical bit pattern. A # genuinely broken number parser on a new arm does not miss by one ULP -- it # misses by orders of magnitude, and precision-*-fast-within-3ulp catches that. has rapidjson/run-fullprec-parse "RJ-fullprec-parse OK" "$out" has rapidjson/run-fullprec-value "RJ-fullprec-value OK" "$out" has rapidjson/run-fullprec-sample "RJ-fullprec-sample-parse OK" "$out" has rapidjson/run-fullprec-small-exact "RJ-fullprec-small-exact OK" "$out" has rapidjson/run-fullprec-small-bits "RJ-fullprec-small-bits OK" "$out" has rapidjson/run-fullprec-doc-bytes "RJ-fullprec-doc-bytes OK" "$out" has rapidjson/run-precision-both-parse "RJ-precision-both-parse OK" "$out" for k in half pi small large negZero; do has "rapidjson/run-precision-$k-exact-bits" "RJ-precision-$k-exact-bits OK" "$out" has "rapidjson/run-precision-$k-within-3ulp" "RJ-precision-$k-fast-within-3ulp OK" "$out" has "rapidjson/run-precision-$k-fast-rt" "RJ-precision-$k-fast-roundtrip OK" "$out" has "rapidjson/run-precision-$k-exact-rt" "RJ-precision-$k-exact-roundtrip OK" "$out" done # the negative controls: two values both parsers must get EXACTLY, because they # are representable in binary with no rounding. A non-zero delta here would mean # the fast path is broken in a way the 3 ULP tolerance is too loose to see. has rapidjson/run-precision-half-exact "RJ-precision-half-fast-is-exact OK" "$out" has rapidjson/run-precision-3.5-exact "RJ-precision-list-3.5-fast-is-exact OK" "$out" # The doubles whose bit patterns ARE arm-independent -- exactly representable in # binary, so no parser has any freedom about them. has rapidjson/run-bits-half "RJ-bits-half OK" "$out" has rapidjson/run-bits-large "RJ-bits-large OK" "$out" has rapidjson/run-bits-neg-zero "RJ-bits-neg-zero OK" "$out" has rapidjson/run-bits-list-3.5 "RJ-bits-list-3.5 OK" "$out" # The relaxed-syntax extensions, each asserted OFF by default and ON under its # flag. A parser that took comments unconditionally would be accepting documents # the JSON grammar refuses. has rapidjson/run-comments-off "RJ-comments-refused-by-default OK" "$out" has rapidjson/run-comments-on "RJ-comments-accepted-with-flag OK" "$out" has rapidjson/run-trailing-off "RJ-trailing-comma-refused-by-default OK" "$out" has rapidjson/run-trailing-on "RJ-trailing-comma-accepted-with-flag OK" "$out" has rapidjson/run-nan-off "RJ-nan-refused-by-default OK" "$out" has rapidjson/run-nan-on "RJ-nan-accepted-with-flag OK" "$out" has rapidjson/run-nan-is-nan "RJ-nan-is-nan OK" "$out" has rapidjson/run-inf-is-inf "RJ-inf-is-inf OK" "$out" has rapidjson/run-stop-when-done "RJ-stop-when-done OK" "$out" has rapidjson/run-stop-when-done-value "RJ-stop-when-done-value OK" "$out" # The caller-supplied pool allocator, and the producer side. has rapidjson/run-pool-parse "RJ-pool-parse OK" "$out" has rapidjson/run-pool-members "RJ-pool-members OK" "$out" has rapidjson/run-pool-used "RJ-pool-used OK" "$out" has rapidjson/run-pool-capacity "RJ-pool-capacity OK" "$out" has rapidjson/run-build-member-count "RJ-build-member-count OK" "$out" has rapidjson/run-build-array "RJ-build-array OK" "$out" has rapidjson/run-build-copied "RJ-build-copied OK" "$out" # REMOVEMEMBER DOES NOT PRESERVE ORDER -- it moves the last member into the # hole, which is what makes it O(1) and what makes EraseMember exist. Both are # pinned side by side because it is the most surprising thing about rapidjson's # object model: a consumer that removes a key from a config file and writes it # back gets a reordered document, and discovers it in a diff rather than here. has rapidjson/run-build-removemember-reorders "RJ-build-removemember-reorders OK" "$out" has rapidjson/run-build-erasemember-keeps-order "RJ-build-erasemember-keeps-order OK" "$out" has rapidjson/run-copy-deep "RJ-copy-deep OK" "$out" has rapidjson/run-copy-size "RJ-copy-size OK" "$out" # RFC 6901 pointers, including the ~0/~1 escaping a split-on-slash walker drops. has rapidjson/run-ptr-get "RJ-ptr-get OK" "$out" has rapidjson/run-ptr-array-index "RJ-ptr-array-index OK" "$out" has rapidjson/run-ptr-deep "RJ-ptr-deep OK" "$out" has rapidjson/run-ptr-missing "RJ-ptr-missing OK" "$out" has rapidjson/run-ptr-root "RJ-ptr-root OK" "$out" has rapidjson/run-ptr-default "RJ-ptr-default OK" "$out" has rapidjson/run-ptr-invalid-detected "RJ-ptr-invalid-detected OK" "$out" has rapidjson/run-ptr-invalid-code "RJ-ptr-invalid-code OK" "$out" has rapidjson/run-ptr-escape-slash "RJ-ptr-escape-slash OK" "$out" has rapidjson/run-ptr-escape-tilde "RJ-ptr-escape-tilde OK" "$out" has rapidjson/run-ptr-set-created "RJ-ptr-set-created OK" "$out" has rapidjson/run-ptr-set-erased "RJ-ptr-set-erased OK" "$out" # JSON Schema, and with it rapidjson's own regex engine -- the part that would # go quietly wrong if miscompiled, since a regex matching everything looks # exactly like a validator that works. Hence a positive control beside every # rejection. has rapidjson/run-schema-parse "RJ-schema-parse OK" "$out" has rapidjson/run-schema-valid "RJ-schema-valid OK" "$out" has rapidjson/run-schema-type "RJ-schema-type-rejected OK" "$out" has rapidjson/run-schema-type-kw "RJ-schema-type-keyword OK" "$out" has rapidjson/run-schema-range "RJ-schema-range-rejected OK" "$out" has rapidjson/run-schema-range-kw "RJ-schema-range-keyword OK" "$out" has rapidjson/run-schema-pattern "RJ-schema-pattern-rejected OK" "$out" has rapidjson/run-schema-pattern-kw "RJ-schema-pattern-keyword OK" "$out" has rapidjson/run-schema-pattern-ok "RJ-schema-pattern-accepted OK" "$out" has rapidjson/run-schema-required "RJ-schema-required-rejected OK" "$out" has rapidjson/run-schema-required-kw "RJ-schema-required-keyword OK" "$out" has rapidjson/run-schema-minitems "RJ-schema-minitems-rejected OK" "$out" has rapidjson/run-schema-minitems-kw "RJ-schema-minitems-keyword OK" "$out" has rapidjson/run-schema-pointer "RJ-schema-invalid-pointer OK" "$out" # FileReadStream, driven with a 256-byte buffer over a 925-byte document so the # refill path is genuinely taken. has rapidjson/run-stream-parse "RJ-stream-parse OK" "$out" has rapidjson/run-stream-members "RJ-stream-members OK" "$out" has rapidjson/run-stream-same-bytes "RJ-stream-same-bytes OK" "$out" # THE PROBE'S OWN DIAGNOSIS, WHICH IS OTHERWISE LOST. Every case above is a # `has` against $out and reports only "not found: RJ-x OK" -- but the probe # prints `RJ-x FAIL: want ... got ...` beside each failure, with expected values # and byte offsets, and that text never reaches the console because $out is a # command substitution. A reader of the run then sees five reds and no reason # for any of them, and has to rebuild and re-boot to learn what the numbers # were. Re-emit each FAIL line as its own case carrying the detail: nothing is # added on a green run, and a red one explains itself the first time. IFS="$nl" for l in $out; do case "$l" in RJ-*\ FAIL:*) c=${l#RJ-}; c=${c%% *} bst_notok "rapidjson/probe-$c" "$(_bst_1line "${l#*FAIL: }")" ;; esac done unset IFS # THE COUNT, which is the guard the per-case assertions cannot be. Every `has` # above can only see what the probe printed, so a probe that silently stopped # emitting half its cases would satisfy all of them and still reach # ALL-RAPIDJSON-OK -- main() prints that whenever its failure counter is zero, # and a group skipped without failing leaves the counter zero. Pinning the # number is what notices. A case ADDED to the probe reddens this one, which is # the intent: update the number in the same change. cases=$(printf '%s\n' "$out" | grep -c '^RJ-') check_eq rapidjson/probe-case-count "272" "$cases" # The probe's own verdict, which also catches a case this list forgot to name: # it counts its OWN failures, so a case added to the probe and not here still # turns the suite red rather than passing unnoticed. has rapidjson/run-all-ok "ALL-RAPIDJSON-OK" "$out" if [ "$prc" = 0 ]; then bst_ok rapidjson/run-exit-status else bst_notok rapidjson/run-exit-status "probe exited $prc: $(_bst_1line "$out")" fi # The last line of the suite, and a coverage assertion rather than a formality: # a suite that dies mid-file loses its remaining cases SILENTLY -- the runner # counts the lines it saw and reports them as a complete green -- so the only # way a reader can tell a full run from a truncated one is a marker that only a # full run emits. Its absence is what a regression here looks like; see the pass # criterion in docs/brintos-test-plan.md. bst_ok rapidjson/suite-complete