# BST-TIMEOUT: 600 # measured 97s on target (89 cases, each a fresh exec of a
# # 19 MB static wasm image); the runner default of 60 trips it
#
# xmlstarlet (XMLStarlet 1.6.1) — the XML command line toolkit. The binary is
# named `xml`, not `xmlstarlet`.
#
# Every expectation below was scored against a NATIVE x86_64 control built from
# the same tarball, with the same meta-oe patches, against the SAME libxml2
# 2.12.10 and libxslt 1.1.43 this image links — so a red here is a platform
# difference rather than a wrong expectation. Where upstream's answer is
# surprising (the exit codes for a missing file, a bad stylesheet, a bad XPath)
# the suite asserts the OBSERVED answer and says so, so the case goes red when
# xmlstarlet changes rather than when a reader's intuition differs.
. "$BST_LIB"
require xml xmlstarlet/present
t=${TMPDIR:-/tmp}/bst-xmlstarlet.$$
mkdir -p "$t" 2>/dev/null || { bst_notok xmlstarlet/tmpdir "cannot mkdir $t"; return 2>/dev/null || exit 0; }
cat > "$t/b.xml" <<'EOF'
Dune9.99
Germinal7.50
EOF
printf '\n' > "$t/bad.xml"
# --- the banner ---------------------------------------------------------------
# Its first line is the package version, which is EMPTY unless the recipe passes
# VERSION= to make: configure.ac takes the version from `git describe` and the
# tarball is not a git repository. This case is the regression guard for the
# bbappend line that fixes it — see xmlstarlet_%.bbappend.
check_out xmlstarlet/version-self "1.6.1" xml --version
check_out xmlstarlet/version-xml2 "libxml2 2.12.10" xml --version
check_out xmlstarlet/version-xslt "libxslt 1.1.43" xml --version
check_out xmlstarlet/usage "XMLStarlet Toolkit" xml --help
# --- el: element structure ----------------------------------------------------
check_out xmlstarlet/el-path "catalog/book/title" xml el "$t/b.xml"
check_out xmlstarlet/el-uniq "catalog/book/price" xml el -u "$t/b.xml"
check_eq xmlstarlet/el-lines 7 "$(xml el "$t/b.xml" | wc -l | tr -d ' ')"
check_eq xmlstarlet/el-uniq-lines 4 "$(xml el -u "$t/b.xml" | wc -l | tr -d ' ')"
check_out xmlstarlet/el-attr "catalog/book/@id" xml el -a "$t/b.xml"
# --- sel: XPath ---------------------------------------------------------------
check_out xmlstarlet/sel-value "Germinal" xml sel -t -v '//book[@id="b2"]/title' -n "$t/b.xml"
check_out xmlstarlet/sel-count "2" xml sel -t -v 'count(//book)' -n "$t/b.xml"
check_out xmlstarlet/sel-attr "b1" xml sel -t -v '//book[1]/@id' -n "$t/b.xml"
check_out xmlstarlet/sel-sum "17.49" xml sel -t -v 'sum(//price)' -n "$t/b.xml"
check_out xmlstarlet/sel-copy "
Dune" xml sel -t -c '//book[1]/title' -n "$t/b.xml"
check_out xmlstarlet/sel-text "Dune" xml sel -T -t -v '//title' -n "$t/b.xml"
check_eq xmlstarlet/sel-match "b1 Dune" "$(xml sel -t -m '//book' -v '@id' -o ' ' -v 'title' -n "$t/b.xml" | head -1)"
check_eq xmlstarlet/sel-if "Germinal" "$(xml sel -t -m '//book' -i '@lang="fr"' -v 'title' -n "$t/b.xml")"
check_eq xmlstarlet/sel-else "DEARCHEAP" "$(xml sel -t -m '//book' -i 'price>8' -o 'DEAR' --else -o 'CHEAP' -n "$t/b.xml")"
check_eq xmlstarlet/sel-sort "Dune" "$(xml sel -t -m '//book' -s A:T:U 'title' -v 'title' -n "$t/b.xml" | head -1)"
# string / number functions run inside libxslt's XPath engine
check_out xmlstarlet/sel-substr "Dun" xml sel -t -v 'substring(//title[1],1,3)' -n "$t/b.xml"
check_out xmlstarlet/sel-concat "Dune-en" xml sel -t -v 'concat(//title[1],"-",//book[1]/@lang)' -n "$t/b.xml"
check_out xmlstarlet/sel-translate "DUNE" xml sel -t -v 'translate(//title[1],"une","UNE")' -n "$t/b.xml"
check_out xmlstarlet/sel-round "10" xml sel -t -v 'round(//price[1])' -n "$t/b.xml"
check_out xmlstarlet/sel-last "Germinal" xml sel -t -v '//book[last()]/title' -n "$t/b.xml"
# a document read from stdin rather than a path
check_eq xmlstarlet/sel-stdin "2" "$(cat "$t/b.xml" | xml sel -t -v 'count(//book)' -n -)"
# an XPath that selects nothing is not an error — upstream exits 0 with no output
out=$(xml sel -t -v '//nothing' -n "$t/b.xml" 2>&1); rc=$?
if [ "$rc" -eq 0 ] && [ -z "$out" ]; then bst_ok xmlstarlet/sel-empty
else bst_notok xmlstarlet/sel-empty "rc=$rc $(_bst_1line "$out")"; fi
# --- sel: namespaces ----------------------------------------------------------
cat > "$t/ns.xml" <<'EOF'
deep
EOF
check_out xmlstarlet/ns-elem "deep" xml sel -N k=http://example.org/k -t -v '//k:item' -n "$t/ns.xml"
check_out xmlstarlet/ns-attr "7" xml sel -N k=http://example.org/k -t -v '//k:item/@k:n' -n "$t/ns.xml"
# --- ed: editing --------------------------------------------------------------
check_out xmlstarlet/ed-update "1.23" xml ed -u '//book[@id="b1"]/price' -v 1.23 "$t/b.xml"
check_out xmlstarlet/ed-ins-attr 'year="1965"' xml ed -i '//book[1]' -t attr -n year -v 1965 "$t/b.xml"
check_out xmlstarlet/ed-subnode "hi" xml ed -s '/catalog' -t elem -n note -v hi "$t/b.xml"
check_out xmlstarlet/ed-rename "- |\n|g' | head -1 | grep -o '' | wc -l | tr -d ' ')"
# -L rewrites the file in place: the only case in this suite that WRITES to the
# filesystem, so it doubles as a small write probe of the guest's /tmp.
cp "$t/b.xml" "$t/e.xml"
check xmlstarlet/ed-inplace-rc xml ed -L -u '//book[@id="b1"]/title' -v 'DUNE' "$t/e.xml"
check_eq xmlstarlet/ed-inplace-file "1" "$(grep -c 'DUNE' "$t/e.xml")"
check_eq xmlstarlet/ed-inplace-other "1" "$(grep -c 'Germinal' "$t/e.xml")"
# --- tr: XSLT -----------------------------------------------------------------
cat > "$t/s.xsl" <<'EOF'
|
EOF
check_eq xmlstarlet/tr-text "Dune|Germinal|" "$(xml tr "$t/s.xsl" "$t/b.xml")"
cat > "$t/p.xsl" <<'EOF'
nobody
hello
EOF
check_eq xmlstarlet/tr-param-default "hello nobody" "$(xml tr "$t/p.xsl" "$t/b.xml")"
check_eq xmlstarlet/tr-param-override "hello world" "$(xml tr "$t/p.xsl" -s who=world "$t/b.xml")"
# libexslt is linked into this binary as well as libxslt; without it these two
# extension namespaces resolve to nothing and the transform silently empties.
cat > "$t/e.xsl" <<'EOF'
|
EOF
check_eq xmlstarlet/tr-exslt "xxx|9.99" "$(xml tr "$t/e.xsl" "$t/b.xml")"
# an XSLT template that builds a document rather than text
cat > "$t/x.xsl" <<'EOF'
EOF
check_out xmlstarlet/tr-doc "Germinal" xml tr "$t/x.xsl" "$t/b.xml"
# --- val: the four validation modes -------------------------------------------
printf '\n' > "$t/w.xml"
check_out xmlstarlet/val-wf-ok "valid" xml val "$t/b.xml"
check_rc xmlstarlet/val-wf-bad 1 xml val "$t/bad.xml"
out=$(xml val --err "$t/bad.xml" 2>&1); rc=$?
case "$out" in *"Opening and ending tag mismatch"*) hit=1 ;; *) hit=0 ;; esac
if [ "$rc" -eq 1 ] && [ "$hit" -eq 1 ]; then bst_ok xmlstarlet/val-err-detail
else bst_notok xmlstarlet/val-err-detail "rc=$rc hit=$hit $(_bst_1line "$out")"; fi
cat > "$t/c.dtd" <<'EOF'
EOF
check_out xmlstarlet/val-dtd-ok "valid" xml val -d "$t/c.dtd" "$t/b.xml"
check_rc xmlstarlet/val-dtd-bad 1 xml val -d "$t/c.dtd" "$t/w.xml"
cat > "$t/c.xsd" <<'EOF'
EOF
check_out xmlstarlet/val-xsd-ok "valid" xml val -s "$t/c.xsd" "$t/b.xml"
check_rc xmlstarlet/val-xsd-bad 1 xml val -s "$t/c.xsd" "$t/w.xml"
cat > "$t/c.rng" <<'EOF'
EOF
check_out xmlstarlet/val-rng-ok "valid" xml val -r "$t/c.rng" "$t/b.xml"
check_rc xmlstarlet/val-rng-bad 1 xml val -r "$t/c.rng" "$t/w.xml"
# --- fo: formatting -----------------------------------------------------------
check_eq xmlstarlet/fo-indent ' ' "$(xml fo -s 4 "$t/b.xml" | sed -n 3p)"
check_eq xmlstarlet/fo-omit "" "$(xml fo -o "$t/b.xml" | head -1)"
check_out xmlstarlet/fo-decl '' xml fo "$t/b.xml"
check_rc xmlstarlet/fo-bad 2 xml fo "$t/bad.xml"
# -R recovers a malformed document rather than refusing it
out=$(xml fo -R "$t/bad.xml" 2>/dev/null); rc=$?
case "$out" in *""*) hit=1 ;; *) hit=0 ;; esac
if [ "$rc" -eq 0 ] && [ "$hit" -eq 1 ]; then bst_ok xmlstarlet/fo-recover
else bst_notok xmlstarlet/fo-recover "rc=$rc hit=$hit $(_bst_1line "$out")"; fi
# --- pyx / depyx --------------------------------------------------------------
check_out xmlstarlet/pyx-elem "(catalog" xml pyx "$t/b.xml"
check_out xmlstarlet/pyx-attr "Aid b1" xml pyx "$t/b.xml"
check_eq xmlstarlet/pyx-roundtrip "2" "$(xml pyx "$t/b.xml" | xml depyx | xml sel -t -v 'count(//book)' -n -)"
# --- c14n ---------------------------------------------------------------------
check_out xmlstarlet/c14n "Dune" xml c14n "$t/b.xml"
check_out xmlstarlet/c14n-exc "Dune" xml c14n --exc-without-comments "$t/b.xml"
# canonicalisation drops the XML declaration — that is the point of it
out=$(xml c14n "$t/b.xml" 2>&1); rc=$?
case "$out" in "')"
check_eq xmlstarlet/unesc '' "$(xml unesc '<a & b>')"
check_eq xmlstarlet/esc-roundtrip '' "$(xml unesc "$(xml esc '')")"
# --- ls: the filesystem surface -----------------------------------------------
# `xml ls` walks a directory with opendir/readdir/lstat and renders it as XML, so
# it is this port's one real syscall probe on the guest's filesystem.
mkdir -p "$t/d" && printf 'hi\n' > "$t/d/f.txt"
check_out xmlstarlet/ls-root "" xml ls "$t/d"
check_out xmlstarlet/ls-name 'n="f.txt"' xml ls "$t/d"
check_out xmlstarlet/ls-size 's="3"' xml ls "$t/d"
check_eq xmlstarlet/ls-parses "1" "$(xml ls "$t/d" | xml sel -t -v 'count(//f)' -n -)"
# --- encodings ----------------------------------------------------------------
# libxml2 handles UTF-8, UTF-16, ASCII and ISO-8859-1 with BUILT-IN converters and
# reaches iconv only for anything else — which matters here, because glibc's
# iconv_open dlopens a gconv module and this image has no dynamic loader. These
# cases stay inside the built-in set deliberately; the target's answer for an
# iconv-only encoding is recorded in the ticket rather than asserted here.
printf '\ncaf\351\n' > "$t/l1.xml"
printf '\ncaf\303\251\n' > "$t/u8.xml"
# both decode to the same UTF-8 output: 0xC3 0xA9
utf8_cafe=$(printf 'caf\303\251')
check_eq xmlstarlet/enc-latin1-in "$utf8_cafe" "$(xml sel -t -v '//t' -n "$t/l1.xml")"
check_eq xmlstarlet/enc-utf8-in "$utf8_cafe" "$(xml sel -t -v '//t' -n "$t/u8.xml")"
check_out xmlstarlet/enc-latin1-decl 'encoding="ISO-8859-1"' xml fo "$t/l1.xml"
# re-encoding UTF-8 out to ISO-8859-1 exercises the built-in encoder in reverse
check_eq xmlstarlet/enc-latin1-out "2" "$(xml fo -e ISO-8859-1 "$t/u8.xml" | wc -l | tr -d ' ')"
check_out xmlstarlet/enc-latin1-out-decl 'encoding="ISO-8859-1"' xml fo -e ISO-8859-1 "$t/u8.xml"
# --- exit codes, as upstream actually defines them ----------------------------
# Each of these was measured on the native control. They are asserted because
# they are the contract a shell script around xmlstarlet depends on, and because
# two of them are surprising: a missing FILE is 3, not 1 or 2.
check_rc xmlstarlet/rc-missing-file 3 xml sel -t -v '//x' -n "$t/nope.xml"
check_rc xmlstarlet/rc-el-missing 3 xml el "$t/nope.xml"
check_rc xmlstarlet/rc-ed-missing 3 xml ed -u '//title' -v x "$t/nope.xml"
check_rc xmlstarlet/rc-bad-xpath 4 xml sel -t -v '//[' -n "$t/b.xml"
check_rc xmlstarlet/rc-pyx-malformed 4 xml pyx "$t/bad.xml"
check_rc xmlstarlet/rc-missing-xsl 4 xml tr "$t/nope.xsl" "$t/b.xml"
check_rc xmlstarlet/rc-unknown-cmd 2 xml bogus
check_rc xmlstarlet/rc-no-args 2 xml
printf '\n' > "$t/bs.xsl"
check_rc xmlstarlet/rc-bad-xsl 5 xml tr "$t/bs.xsl" "$t/b.xml"
# a document that is well-formed but absent from the filesystem is "invalid",
# not an error, to `val` — a different answer from every other subcommand's 3
check_rc xmlstarlet/rc-val-missing 1 xml val "$t/nope.xml"
# --- scale --------------------------------------------------------------------
# 2000 elements / ~44 kB, shipped as a payload rather than generated on target
# (a 2000-iteration ash loop costs more than the case is worth). This is the only
# case that exercises the parser at a size the fixtures above cannot.
if [ -f "$BST_DATA/xmlstarlet-big.xml" ]; then
big="$BST_DATA/xmlstarlet-big.xml"
check_eq xmlstarlet/big-count "2000" "$(xml sel -t -v 'count(//n)' -n "$big")"
check_eq xmlstarlet/big-last "v1999" "$(xml sel -t -v '//n[@id="1999"]' -n "$big")"
check_eq xmlstarlet/big-pyx "2000" "$(xml pyx "$big" | xml depyx | xml sel -t -v 'count(//n)' -n -)"
check xmlstarlet/big-val xml val "$big"
check_eq xmlstarlet/big-edit "1" "$(xml ed -u '//n[@id="0"]' -v EDITED "$big" | grep -c '>EDITED<')"
else
bst_notok xmlstarlet/big-payload "missing $BST_DATA/xmlstarlet-big.xml"
fi
rm -rf "$t"