diff --git a/.circleci/config.yml b/.circleci/config.yml index 55b58ef8..57569fb2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,8 +18,8 @@ jobs: - run: name: Install testing prerequisites command: | - # sudo apt-get update - # sudo apt-get install -y xsltproc + sudo apt-get update + sudo apt-get install -y xsltproc mkdir /tmp/saxon/ mkdir /tmp/saxon/lib/ @@ -46,6 +46,9 @@ jobs: - run: name: Run LoC-specific test suite command: "$XSPEC test/ConvSpec-DLC.xspec" + - run: + name: Compare libxslt and Saxon output + command: SAXON_CP=${SAXON_CP:-/tmp/saxon/saxon-he-12.4.jar} ./test/compare-processors.sh - store_artifacts: path: test/xspec/marc2bibframe2-result.html destination: xspec-tests/marc2bibframe2-result.html diff --git a/README.md b/README.md index 9bfde94e..06d6375e 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,22 @@ LoC-specific conversions, run only the ConvSpec-DLC.xspec test suite: xspec.sh test/ConvSpec-DLC.xspec +### Testing against libxslt + +The XSpec suite runs under Saxon. A construct that Saxon evaluates correctly +and libxslt does not will pass it and still produce wrong output for the many +downstream users on libxslt -- lxml, Nokogiri, PHP's XSL extension, +XML::LibXSLT and `xsltproc` itself. To catch that, `test/compare-processors.sh` +converts the same records with both processors and requires the results to +match: + + SAXON_CP=/tmp/saxon/saxon-he-12.4.jar ./test/compare-processors.sh + +Both results are rendered through `test/normalize.xsl` first, by the same +processor, so indentation, attribute order and where namespace declarations +land are not treated as differences. With no arguments the script checks the +records named in its `DEFAULT_FILES`; pass paths to check others. + ## Active record conversion Active conversion of records - resolving URIs for elements of the diff --git a/test/ConvSpec-Process6-Series.xspec b/test/ConvSpec-Process6-Series.xspec index 3e4496fe..c7fd2a54 100644 --- a/test/ConvSpec-Process6-Series.xspec +++ b/test/ConvSpec-Process6-Series.xspec @@ -27,6 +27,15 @@ --> + + + + + + + + + diff --git a/test/compare-processors.sh b/test/compare-processors.sh new file mode 100755 index 00000000..ba9fb312 --- /dev/null +++ b/test/compare-processors.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# +# Convert every test record with both Saxon and libxslt and require the two +# results to agree. +# +# The XSpec suite runs under Saxon only, so a construct that Saxon evaluates +# correctly and libxslt does not passes CI and still produces wrong output for +# the many downstream users on libxslt -- lxml (Python), Nokogiri (Ruby), PHP's +# XSL extension, XML::LibXSLT (Perl) and xsltproc itself. +# +# Both results are rendered through test/normalize.xsl before comparison, by +# the same processor, so that indentation, attribute order and the placement of +# namespace declarations do not count as differences. +# +# Usage: +# SAXON_CP=/path/to/saxon-he-12.4.jar ./test/compare-processors.sh [file...] +# +# With no arguments it checks the records listed in DEFAULT_FILES below. Pass +# paths to check anything else -- pointing it at all of test/data is a quick +# way to see where else the two processors part company. + +set -u + +cd "$(dirname "$0")/.." + +: "${SAXON_CP:?set SAXON_CP to the Saxon jar, e.g. /tmp/saxon/saxon-he-12.4.jar}" + +command -v xsltproc >/dev/null || { echo "xsltproc not found"; exit 2; } + +STYLESHEET=xsl/marc2bibframe2.xsl +NORMALIZE=test/normalize.xsl + +# The transform stamps the current time into the work's admin metadata, which +# would differ between the two runs. +DATESTAMP=2020-01-01T00:00:00 + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +# The records this check covers. Deliberately a subset for now: it starts with +# the series data, and should grow as other processor divergences are found and +# resolved. +DEFAULT_FILES=(test/data/ConvSpec-Process6-Series/*.xml) + +if [ "$#" -gt 0 ]; then + files=("$@") +else + files=("${DEFAULT_FILES[@]}") +fi + +failed=0 +checked=0 + +for file in "${files[@]}"; do + checked=$((checked + 1)) + + if ! xsltproc --stringparam pGenerationDatestamp "$DATESTAMP" \ + "$STYLESHEET" "$file" > "$work/libxslt.xml" 2> "$work/libxslt.err"; then + echo "FAIL $file" + echo " libxslt could not convert it:" + sed 's/^/ /' "$work/libxslt.err" + failed=$((failed + 1)) + continue + fi + + if [ ! -s "$work/libxslt.xml" ]; then + echo "FAIL $file" + echo " libxslt produced no output" + sed 's/^/ /' "$work/libxslt.err" + failed=$((failed + 1)) + continue + fi + + java -cp "$SAXON_CP" net.sf.saxon.Transform \ + -s:"$file" -xsl:"$STYLESHEET" "pGenerationDatestamp=$DATESTAMP" \ + -o:"$work/saxon.xml" 2> "$work/saxon.err" || { + echo "FAIL $file" + echo " Saxon could not convert it:" + sed 's/^/ /' "$work/saxon.err" + failed=$((failed + 1)) + continue + } + + xsltproc "$NORMALIZE" "$work/libxslt.xml" > "$work/libxslt.txt" + xsltproc "$NORMALIZE" "$work/saxon.xml" > "$work/saxon.txt" + + if ! diff -q "$work/libxslt.txt" "$work/saxon.txt" > /dev/null; then + echo "FAIL $file" + echo " libxslt and Saxon disagree (< libxslt, > Saxon), first 20 lines:" + diff "$work/libxslt.txt" "$work/saxon.txt" | head -20 | sed 's/^/ /' + failed=$((failed + 1)) + continue + fi + + echo "ok $file" +done + +echo +if [ "$failed" -gt 0 ]; then + echo "$failed of $checked records differ between processors" + exit 1 +fi +echo "$checked records convert identically under libxslt and Saxon" diff --git a/test/data/ConvSpec-Process6-Series/dup-relations.xml b/test/data/ConvSpec-Process6-Series/dup-relations.xml new file mode 100644 index 00000000..ab4c621c --- /dev/null +++ b/test/data/ConvSpec-Process6-Series/dup-relations.xml @@ -0,0 +1,22 @@ + + + 1 + + Lund studies in geography + + + + 2 + + Lund studies in geography ; + no. 32 + + + + 3 + + Lund studies in geography ; + 1234-5678 + + + diff --git a/test/normalize.xsl b/test/normalize.xsl new file mode 100644 index 00000000..958f8aae --- /dev/null +++ b/test/normalize.xsl @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xsl/ConvSpec-Process6-Series.xsl b/xsl/ConvSpec-Process6-Series.xsl index 7c5ee209..57d05ed7 100644 --- a/xsl/ConvSpec-Process6-Series.xsl +++ b/xsl/ConvSpec-Process6-Series.xsl @@ -63,7 +63,7 @@ - +