. "$BST_LIB" # MD4C 0.5.2 is a SAX-like Markdown parser in C: md_parse() walks the document # and calls back into the caller for every block, span and run of text, handing # each one back as a (pointer, length) slice INTO the caller's own buffer. It # ships here as two static archives (libmd4c.a, libmd4c-html.a), their headers # and pkg-config files, and the md2html(1) CLI that links them. # # It is the SECOND Markdown implementation on this image, beside cmark (76), and # the two are deliberately numbered together. They are not redundant: cmark is # the CommonMark reference implementation and renders a document tree, while # MD4C never builds a tree at all -- it is a streaming parser whose entire output # is indirect calls through a caller-supplied struct of seven function pointers. # That difference is the reason this suite is shaped the way it is. # # --------------------------------------------------------------------------- # WHY THIS SUITE RUNS A PROBE AS WELL AS THE CLI # # md2html exercises the library end to end and is asserted directly below, but # it only ever passes MD4C *its own* renderer. Two properties that matter on this # arm are invisible from the CLI: # # 1. THE SLICES. Every text run MD4C reports is a pointer into the input plus # a length that MD4C computed. BRINTOS-217 is the precedent: oniguruma # compiled and packaged perfectly on this arm and then returned silently # wrong offsets to every caller. A wrong slice here is not a crash -- it is # quietly wrong output -- and md2html would render it without complaint. # 2. THE CALLBACKS. MD_PARSER is seven stored function pointers. On this # coroutinized arm those are reached through a table: the build log shows # `FPCAST-DYN-MARSHAL fn=md_parse site_sig=Fvii arms=3`, i.e. md_parse's # indirect call sites are dynamically marshalled with an open-world # fallback that aborts LOUDLY on an unresolved target. Whether every arm # dispatches, and whether values come back OUT of them, is exactly what a # caller-supplied renderer measures and md2html does not. # # So data/md4c-probe is cross-linked with wasmcc-glibc against THE VERY ARCHIVES # THAT SHIP (/usr/lib/libmd4c.a, /usr/lib/libmd4c-html.a), staged to $TMPDIR and # run here. Its source is src/md4c_probe.c. EVERY EXPECTED VALUE IN IT IS THE # VALUE A NATIVE x86_64 BUILD OF THE IDENTICAL SOURCE PRODUCES: the probe scored # 186/186 natively before this suite was written, and getting it there corrected # 18 wrong expectations of mine -- so a red below is a platform difference, not a # guess of mine. The probe compares internally and prints its own verdict, and on # a failure prints a DIAG line carrying what it actually got. # # --------------------------------------------------------------------------- # TWO THINGS THE NATIVE CONTROL SETTLED, RECORDED HERE BECAUSE THEY LOOK LIKE # TARGET BUGS AND ARE NOT # # a) MD4C DOES NOT HONOUR ITS OWN ABORT CONTRACT. md4c.h promises that any # rendering callback may abort the parse by returning non-zero and that # md_parse() then returns that value. Measured, on x86_64: only leave_block # keeps the promise. enter_block and enter_span stop the walk but md_parse # still returns 0, and the text callback's return is ignored outright. That # is upstream's defect; the probe pins all four to what they ACTUALLY do so # a change in either direction is caught, and leave_block is the case that # carries the platform signal (a value surviving an indirect call). # # b) SOME SLICES LEGITIMATELY POINT OUTSIDE THE INPUT. MD4C emits line # terminators from the static literal _T("\n") (md4c.c:4472, 4486, 4738) and # the NULLCHAR placeholder from _T(""), so a size-1 "\n"/"\0" is not a slice # of the caller's buffer. The probe counts those separately rather than # exempting a whole text type, which makes the range check SHARPER: every # other byte MD4C reports must lie inside the input. # # --------------------------------------------------------------------------- # Unlike libconfuse (73) the main package is NOT empty here -- md2html gives it # real payload -- so all three of md4c, md4c-dev and md4c-staticdev are named in # the image. # # --------------------------------------------------------------------------- # SHARED LIBRARIES (BRINTOS-881 phase 3; BRINTOS-1415) # # This suite used to say "the distro is static-only, so there is no libmd4c.so # and none is expected". Under BRINTOS_PACKAGE_SHARED that is no longer true: # md4c ships BOTH halves, and md2html binds the shared ones at load. # # md4c is the reference case for BRINTOS-1415 because it is a CHAIN -- # md2html -> libmd4c-html.so -> libmd4c.so -- and the MIDDLE module carries no # needed-list of its own. 78-needed-graph-multi already covers a main naming two # package libraries; nothing covered a library naming another one, which is a # different question: the loader has to resolve libmd4c-html's undefined md_parse # from a module it placed, with nothing in libmd4c-html recording where to look. # # The assertions below establish only the SHAPE. What proves the chain at run # time is the ~25 md4c/cli-* cases further down: md2html cannot render a heading # without md_html() out of libmd4c-html.so, which cannot parse without md_parse() # out of libmd4c.so. Those cases pass identically on a static image, which is why # the shape assertions ship with them -- together they discriminate, separately # neither does. # # Skipped, not failed, where the flip is off: both shapes are legitimate builds. require md2html md4c/present tmp=${TMPDIR:-/tmp}/bst-md4c.$$ mkdir -p "$tmp" 2>/dev/null # --------------------------------------------------------------- payload shape # md4c is cmake, so the flip takes the static half away completely -- measured at # recipe scope with BRINTOS_PACKAGE_SHARED = "1": md4c, md4c-dev, md4c-doc and # md4c-dbg are generated, md4c-staticdev is not, and no .a ships. Both shapes are # asserted; neither is waived. if ls /usr/lib/libmd4c.so.* >/dev/null 2>&1; then check md4c/so-lib-parser test -f /usr/lib/libmd4c.so.0.5.2 check md4c/so-lib-html test -f /usr/lib/libmd4c-html.so.0.5.2 else check md4c/lib-parser test -f /usr/lib/libmd4c.a check md4c/lib-html test -f /usr/lib/libmd4c-html.a fi check md4c/hdr-parser test -f /usr/include/md4c.h # --- shared halves, and the chain's shape (BRINTOS-1415) --------------------- if ! ls /usr/lib/libmd4c.so.* >/dev/null 2>&1; then bst_skip md4c/so-chain "no libmd4c.so.* -- built with BRINTOS_PACKAGE_SHARED off" else bst_ok md4c/so-parser check md4c/so-html test -e /usr/lib/libmd4c-html.so.0 _md=$(command -v md2html) # The main must NAME both, or it merged them and the cli-* cases below prove # nothing about linkage. if grep -aq "wasm\.linux\.needed" "$_md" 2>/dev/null; then bst_ok md4c/so-main-dynamic else bst_notok md4c/so-main-dynamic "md2html carries no wasm.linux.needed -- statically merged"; fi for _l in libmd4c-html libmd4c; do if grep -aq "$_l\.so" "$_md" 2>/dev/null; then bst_ok md4c/so-main-needs-${_l#libmd4c} else bst_notok md4c/so-main-needs-${_l#libmd4c} "md2html does not name $_l.so"; fi done # THE BRINTOS-1415 DISCRIMINATOR. The middle module must carry NO needed list # of its own: if it did, the loader would be following a recorded entry and # the chain would prove nothing beyond what 78-needed-graph-multi shows. _mid=$(ls /usr/lib/libmd4c-html.so.*.* 2>/dev/null | head -1) if [ -n "$_mid" ] && grep -aq "wasm\.linux\.needed" "$_mid" 2>/dev/null; then bst_notok md4c/so-middle-unrecorded "libmd4c-html.so carries a needed list -- the chain is recorded, not resolved" else bst_ok md4c/so-middle-unrecorded fi fi check md4c/hdr-html test -f /usr/include/md4c-html.h check md4c/pc-parser test -f /usr/lib/pkgconfig/md4c.pc check md4c/pc-html test -f /usr/lib/pkgconfig/md4c-html.pc check md4c/cmake-config test -f /usr/lib/cmake/md4c/md4cConfig.cmake check md4c/bin-present test -f /usr/bin/md2html check md4c/bin-executable test -x /usr/bin/md2html # The dev symlink follows the SHAPE, and both shapes are correct builds # (BRINTOS-1619 allow-listed md4c, so the conversion is now an intended outcome # rather than a lost BUILD_SHARED_LIBS=OFF). Keyed off the versioned library the # so-chain block above already keys off, so the two can never disagree. if ls /usr/lib/libmd4c.so.* >/dev/null 2>&1; then check md4c/dev-symlink test -e /usr/lib/libmd4c.so else check_rc md4c/no-shared 1 test -e /usr/lib/libmd4c.so fi # The archives must carry the object files, not just exist. libmd4c-html.a is the # one that carries entity.c -- the ~2100-entry HTML entity table the renderer # searches -- so its absence would be invisible until an &entity; came out wrong. # Read the member names straight out of the ar header rather than unpacking: this # image ships no binutils ar (the first boot scored these rc=127), and a member # name short enough for ar's 16-byte name field is stored as plain text, so a # binary-mode grep sees it. # ar member names exist only in an archive -- a side module has no such header -- # so these three assert the archive shape and are skipped, not failed, on the # converted one. entity.c's ~2100-entry table is still reachable there: it is # asserted by the © case in the CLI section below, which is what the member # name was standing in for. if [ -e /usr/lib/libmd4c.a ]; then check_out md4c/ar-parser "md4c.c.o" grep -a -o 'md4c\.c\.o' /usr/lib/libmd4c.a check_out md4c/ar-html "md4c-html.c.o" grep -a -o 'md4c-html\.c\.o' /usr/lib/libmd4c-html.a check_out md4c/ar-entity "entity.c.o" grep -a -o 'entity\.c\.o' /usr/lib/libmd4c-html.a else bst_skip md4c/ar-members "no /usr/lib/libmd4c.a -- md4c converted, so there is no ar header to read" fi # pkg-config text must describe the TARGET layout, not a build host's. Only # prefix is written absolute; libdir stays the unexpanded ${exec_prefix}/lib, so # the RESOLVED value is what has to be asserted -- ask pkg-config to expand it # rather than grepping the file for a literal that is not in it. check_out md4c/pc-prefix "prefix=/usr" cat /usr/lib/pkgconfig/md4c.pc check_out md4c/pc-libdir "/usr/lib" pkg-config --variable=libdir md4c check_out md4c/pc-lname "-lmd4c" cat /usr/lib/pkgconfig/md4c.pc check_out md4c/pc-html-lname "-lmd4c-html" cat /usr/lib/pkgconfig/md4c-html.pc # --------------------------------------------------------------- md2html(1) check_out md4c/cli-version "0.5.2" md2html --version check_out md4c/cli-help "--github" md2html --help # Core rendering, from a file and from stdin. These are CommonMark, and they are # the same bytes the native control emits. printf '# Title\n\nHello *world*.\n' > "$tmp/a.md" check_out md4c/cli-h1 "

Title

" md2html "$tmp/a.md" check_out md4c/cli-em "

Hello world.

" md2html "$tmp/a.md" out=$(printf '**b**\n' | md2html 2>&1) check_eq md4c/cli-stdin "

b

" "$out" # -o writes a file rather than stdout. printf 'x\n' > "$tmp/b.md" md2html -o "$tmp/b.html" "$tmp/b.md" 2>/dev/null check md4c/cli-o-created test -s "$tmp/b.html" check_eq md4c/cli-o-content "

x

" "$(cat "$tmp/b.html" 2>/dev/null)" # --full-html wraps the body; --xhtml self-closes void elements. check_out md4c/cli-full-doctype "" md2html --full-html "$tmp/b.md" check_out md4c/cli-full-body "" md2html --full-html "$tmp/b.md" check_out md4c/cli-html-title "MyTitle" md2html --full-html --html-title=MyTitle "$tmp/b.md" printf -- '---\n' > "$tmp/hr.md" check_eq md4c/cli-hr "
" "$(md2html "$tmp/hr.md" 2>&1)" check_eq md4c/cli-hr-xhtml "
" "$(md2html --xhtml "$tmp/hr.md" 2>&1)" # Extension flags: each must be OFF by default and ON when asked for. This pair # shape is what catches a flag that is wired to the wrong MD_FLAG_ bit. printf '|a|\n|-|\n|b|\n' > "$tmp/t.md" check_rc md4c/cli-table-off 1 sh -c 'md2html '"$tmp"'/t.md | grep -q ""' check_out md4c/cli-table-on "
" md2html --ftables "$tmp/t.md" printf '~~s~~\n' > "$tmp/s.md" check_eq md4c/cli-strike-off "

~~s~~

" "$(md2html "$tmp/s.md" 2>&1)" check_eq md4c/cli-strike-on "

s

" "$(md2html --fstrikethrough "$tmp/s.md" 2>&1)" printf 'www.e.com\n' > "$tmp/w.md" check_rc md4c/cli-www-off 1 sh -c 'md2html '"$tmp"'/w.md | grep -q " "$tmp/k.md" check_out md4c/cli-tasklists 'type="checkbox"' md2html --ftasklists "$tmp/k.md" printf '$e$\n' > "$tmp/m.md" check_out md4c/cli-latex '' md2html --flatex-math "$tmp/m.md" printf '[[w]]\n' > "$tmp/wl.md" check_out md4c/cli-wikilinks '` filtering. Asserted in both # directions so the difference between the two ports stays deliberate. printf '
x
\n' > "$tmp/h.md" check_eq md4c/cli-raw-html-kept "
x
" "$(md2html "$tmp/h.md" 2>&1)" check_rc md4c/cli-raw-html-dropped 1 sh -c 'md2html --fno-html '"$tmp"'/h.md | grep -q "
"' # Entities resolve through entity.c's table; --fverbatim-entities turns that off. printf '©\n' > "$tmp/e.md" check_out md4c/cli-entity "$(printf '\302\251')" md2html "$tmp/e.md" check_out md4c/cli-entity-verbatim "©" md2html --fverbatim-entities "$tmp/e.md" # UTF-8 must survive byte-for-byte: md4c.c is compiled -DMD4C_USE_UTF8 and its # decoder walks the input a byte at a time, which is the pointer arithmetic most # likely to differ on this arm. printf 'caf\303\251 \346\227\245\346\234\254\n' > "$tmp/u.md" check_out md4c/cli-utf8 "$(printf 'caf\303\251 \346\227\245\346\234\254')" md2html "$tmp/u.md" # A missing input file is an error, not a silent empty render. check_rc md4c/cli-missing-file 1 md2html "$tmp/does-not-exist.md" # A large document: exercises the block-array realloc path (md4c.c moves its # block array as it grows, then fixes up ->current_block) rather than any single # construct. 2000 headings in, 2000

out. i=0 : > "$tmp/big.md" while [ "$i" -lt 2000 ]; do printf '# H%s\n\n' "$i" >> "$tmp/big.md"; i=$((i+1)); done n=$(md2html "$tmp/big.md" 2>/dev/null | grep -c '^

') check_eq md4c/cli-big-headings 2000 "$n" # ------------------------------------------------------- the consumer probe # The recipe installs everything under data/ with `install -m 0644`, so the # payload is NOT executable where it lands: stage it to $TMPDIR and chmod +x, # the same way every sibling probe suite does. The deployed rootfs is read-only # at browser runtime, which is the other reason it is copied before it runs. # # Do NOT guard this on [ -x "$BST_DATA/md4c-probe" ]. That is false on a # correctly-built image, and worse, `test -x` on a mode-0644 file aborts the # whole script here with `sh: getgroups: Function not implemented` # (brintos/linux#19) -- which truncated this suite at exactly this line and # still let it report a green pass=46 fail=0 skip=0 with every runtime # assertion below silently unrun. if ! cp "$BST_DATA/md4c-probe" "$tmp/md4c-probe" 2>/dev/null \ || ! chmod +x "$tmp/md4c-probe" 2>/dev/null; then bst_skip md4c/probe "md4c-probe payload missing or not stageable" else pout=$("$tmp/md4c-probe" 2>&1); prc=$? # The probe's own tally. 186/186 is the native control's score, so anything # less names a real difference on this arm -- and the FAIL/DIAG lines in the # console dump say which case and what it got. psum=$(printf '%s\n' "$pout" | sed -n 's/^MD4C-SUMMARY //p') check_eq md4c/probe-summary "186/186" "$psum" check_eq md4c/probe-exit "0" "$prc" # A handful of the probe's groups are re-asserted individually, so a red run # localises to a mechanism from the summary line alone rather than needing # the whole dump read. nfail=$(printf '%s\n' "$pout" | grep -c '^MD4C FAIL' || true) check_eq md4c/probe-no-failures "0" "$nfail" for g in blk spn flg det off utf ent html abt; do gf=$(printf '%s\n' "$pout" | grep -c "^MD4C FAIL $g" || true) check_eq "md4c/probe-group-$g" "0" "$gf" done # The two mechanisms this port exists to prove, called out by name: # the slices stay inside the caller's buffer (BRINTOS-217), and # a value survives the round trip out of an indirect call. check_out md4c/probe-slices-in-range "MD4C ok off-mixed-in-range" \ printf '%s\n' "$pout" check_out md4c/probe-indirect-return "MD4C ok abt-leave-block-propagates" \ printf '%s\n' "$pout" fi rm -rf "$tmp"