Chapter 6. Pipeline assertions
You can use assertions to test that your pipeline is processing
documents the way you expect. In order to use assertions you must enable them
with the --assertions
option.
Assertion types
An assertion can either be a Schematron schema or an XProc pipeline.
Schematron assertions
A Schematron assertion is a s:schema
element containing the
Schematron to use for the assertion.
Schematron operates on XML documents. If you make an assertion on a
document that contains a map or array (or simple atomic value), it will be
converted to XML (as per p:cast-content-type
) before the assertions
are applied. Assertions cannot be applied to binary document.
XProc pipeline assertions
A pipeline assertion is a p:declare-step
element
containing the pipeline to use for the assertion. The pipeline must
have a single input port named source. It may have any
number of output ports, but documents sent to them are ignored. No
options are passed to an assertion pipeline.
An assertion pipeline passes if it finishes without raising an error.
To raise an assertion, cause an error, for example with p:error
.
The warning or error message used in the assertion is the string value of the error document, if there is one. For example:
|<p:error code="cx:failed-assertion">
| <p:with-input><doc>Something went wrong</doc></p:with-input>
|</p:error>
will generate an assertion message something like the following:
Assert cx:failed-assertion on step-id/port: Something went wrong
There’s no equivalent of Schematron reports for assertion pipelines.
Assertion placement
There are two ways to use assertions: you can place them directly in
p:input
, p:output
, or p:with-input
elements, or
you can refer to them with a cx:assertions
extension
attribute.
In either case, each document is tested against the specified schema(s).
Directly embedded assertions
To use an assertion directly, simply place the assertion in
a p:pipeinfo
element in the input or output. For example:
1 |<p:declare-step xmlns:p="http://www.w3.org/ns/xproc"
| xmlns:c="http://www.w3.org/ns/xproc-step"
| xmlns:cx="http://xmlcalabash.com/ns/extensions"
| xmlns:s="http://purl.oclc.org/dsdl/schematron"
5 | xmlns:xs="http://www.w3.org/2001/XMLSchema"
| exclude-inline-prefixes="#all" version="3.0">
|<p:input port="source">
| <p:pipeinfo>
| <s:schema queryBinding="xslt2">
10 | <s:ns prefix="ex" uri="https://xmlcalabash.com/ns/examples"/>
| <s:pattern>
| <s:rule context="/">
| <s:report test="*/@xml:id">The source document has a root id.</s:report>
| <s:report test="not(*/@xml:id)">The source document does not have a root id.</s:report>
15 | <s:assert test="ex:book">The source is not a book.</s:assert>
| </s:rule>
| </s:pattern>
| </s:schema>
| </p:pipeinfo>
20 |</p:input>
|<p:output port="result">
| <p:pipeinfo>
| <s:schema queryBinding="xslt2">
| <s:pattern>
25 | <s:rule context="/">
| <s:assert test="string(/*/@unique-id) != ''">The output does not have a unique id.</s:assert>
| </s:rule>
| </s:pattern>
| </s:schema>
30 | </p:pipeinfo>
|</p:output>
|
|<p:add-attribute attribute-name="unique-id" attribute-value=""/>
|
35 |<p:uuid match="/*/@unique-id"/>
|
|</p:declare-step>
If you enable assertion warnings and run that pipeline with this input document:
|<chap xmlns="https://xmlcalabash.com/ns/examples">
|<title>Second Chapter</title>
|<p>…</p>
|</chap>
You’ll see:
> xmlcalabash --assertions:warn -i:source=default-ch2.xml
At / on p:declare-step/source in file:/…/examples/xml/default-ch2.xml:
Report not(*/@xml:id): The source document does not have a root id.
At / on p:declare-step/source in file:/…/examples/xml/default-ch2.xml:
Warning ex:book: The source is not a book.
=== result :: 1 :: file:/…/examples/xml/default-ch2.xml ===
<chap xmlns="https://xmlcalabash.com/ns/examples"
unique-id="16e550c1-96ff-4e88-98be-d85a15183a65">
<title>Second Chapter</title>
<p>…</p>
</chap>
=====================================================================================================
If you enable assertion errors, the pipeline will fail. If you ignore assertions, you’ll just get the output without any schema results.
Assertions by reference
There are two reasons to use assertions by reference: reuse and testing the output of atomic steps:
It may be convenient to make the same assertions on several outputs, so it’s useful to refer to a schema rather than embedding it directly every time.
Atomic steps don’t have
p:output
(or any sort of “with-output”) children in which you can place the assertions, so there has to be another method to reference them.
To use them by reference, place the schemas in a p:pipeinfo
within the
p:declare-step
(or p:library
) that contains the pipeline. Give
each schema an xml:id
attribute.
You can refer to the schemas from a cx:assertions
attribute on a step. The attribute is a map from port names to a list of IDs.
Here is the earlier assertions pipeline rewritten in this way:
1 |<p:declare-step xmlns:p="http://www.w3.org/ns/xproc"
| xmlns:c="http://www.w3.org/ns/xproc-step"
| xmlns:cx="http://xmlcalabash.com/ns/extensions"
| xmlns:s="http://purl.oclc.org/dsdl/schematron"
5 | xmlns:xs="http://www.w3.org/2001/XMLSchema"
| exclude-inline-prefixes="#all" version="3.0"
| cx:assertions="map { 'source': ('assert-input') }">
|<p:input port="source"/>
|<p:output port="result"/>
10 |
|<p:add-attribute attribute-name="unique-id" attribute-value=""/>
|
|<p:uuid match="/*/@unique-id"
| cx:assertions="map { 'result': 'assert-output' }"/>
15 |
|<!-- ============================================================ -->
|
|<p:pipeinfo>
| <s:schema queryBinding="xslt2" xml:id="assert-input">
20 | <s:ns prefix="ex" uri="https://xmlcalabash.com/ns/examples"/>
| <s:pattern>
| <s:rule context="/">
| <s:report test="*/@xml:id">The source document has a root id.</s:report>
| <s:report test="not(*/@xml:id)">The source document does not have a root id.</s:report>
25 | <s:assert test="ex:book">The source is not a book.</s:assert>
| </s:rule>
| </s:pattern>
| </s:schema>
|
30 | <s:schema queryBinding="xslt2" xml:id="assert-output">
| <s:pattern>
| <s:rule context="/">
| <s:assert test="string(/*/@unique-id) != ''">The output does not have a unique id.</s:assert>
| </s:rule>
35 | </s:pattern>
| </s:schema>
|</p:pipeinfo>
|
|</p:declare-step>
As a convience for the case where you want to reuse schemas where
you have an input or output element, you can put the
cx:assertions
attribute directly on the input or
output element. In this case, the assertions must be a list of strings.
Here’s a final example that adds another test.
1 |<p:declare-step xmlns:p="http://www.w3.org/ns/xproc"
| xmlns:c="http://www.w3.org/ns/xproc-step"
| xmlns:cx="http://xmlcalabash.com/ns/extensions"
| xmlns:s="http://purl.oclc.org/dsdl/schematron"
5 | xmlns:xs="http://www.w3.org/2001/XMLSchema"
| exclude-inline-prefixes="#all" version="3.0">
|<p:input port="source" cx:assertions="'assert-input'"/>
|<p:output port="result"/>
|
10 |<p:add-attribute attribute-name="unique-id" attribute-value=""/>
|
|<p:uuid match="/*/@unique-id"
| cx:assertions="map { 'result': 'assert-output' }">
| <p:with-input cx:assertions="'assert-attribute'"/>
15 |</p:uuid>
|
|<!-- ============================================================ -->
|
|<p:pipeinfo>
20 | <s:schema queryBinding="xslt2" xml:id="assert-input">
| <s:ns prefix="ex" uri="https://xmlcalabash.com/ns/examples"/>
| <s:pattern>
| <s:rule context="/">
| <s:report test="*/@xml:id">The source document has a root id.</s:report>
25 | <s:report test="not(*/@xml:id)">The source document does not have a root id.</s:report>
| <s:assert test="ex:book">The source is not a book.</s:assert>
| </s:rule>
| </s:pattern>
| </s:schema>
30 |
| <s:schema queryBinding="xslt2" xml:id="assert-output">
| <s:pattern>
| <s:rule context="/">
| <s:assert test="string(/*/@unique-id) != ''">The output does not have a unique id.</s:assert>
35 | </s:rule>
| </s:pattern>
| </s:schema>
|
| <s:schema queryBinding="xslt2" xml:id="assert-attribute">
40 | <s:pattern>
| <s:rule context="/">
| <s:assert test="*/@unique-id">The input does not have a unique-id attribute.</s:assert>
| </s:rule>
| </s:pattern>
45 | </s:schema>
|</p:pipeinfo>
|
|</p:declare-step>
You can mix-and-match the embedded and referenced assertions.
Reusing p:pipeinfo
elements
To support reuse, you can load p:pipeinfo
elements from external
documents in your pipeline. An empty p:pipeinfo
element with a
cx:href
attribute:
|<p:pipeinfo cx:href="assertions.xml"/>
will load the p:pipeinfo
element from the external document and
behave exactly as if it had been embedded in the pipeline.