<?xml version="1.0" encoding="utf-8"?><article xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0-subset Balisage-1.5">
    <title>XSpec for XProc, and XProc for XSLT</title>
    <subtitle>Testing Your XProc Steps Using XSpec 4</subtitle>
    <info>
    <confgroup>
      <conftitle>Balisage: The Markup Conference 2026</conftitle>
      <confdates>August 3-7, 2026</confdates>
   </confgroup>
        <abstract>
      <para>XProc 3 is an actively developed pipeline language that
        excels at processing documents. XSpec is an automated testing
        framework that supports testing code written in XSLT, XQuery,
        and Schematron—and now XProc as well. This paper demonstrates
        a test suite for XProc using the XSpec vocabulary. We’ll also
        see a little of how the XSLT and XProc processors work
        together, as the test execution process jumps from XSLT to
        XProc and back. A feature of XML Calabash (and upcoming in
        MorganaXProc-III) enables XSpec to invoke the behavior of an
        XProc step from an XPath expression. You can use that feature
        yourself, in your XSpec test suites or in other
        projects.</para>
        </abstract>
        <author>
            <personname>
                <firstname>Amanda</firstname>
                <surname>Galtman</surname>
            </personname>
            <personblurb>
                <para>Amanda Galtman maintains XSpec, is writing a
          book on XSpec to be published by XML Press, and also writes
          about XSpec at <link xlink:href="https://medium.com/@xspectacles" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/>.</para>
            </personblurb>
            <email>galtmana@proton.me</email>
        </author>
<legalnotice><para>Copyright © 2026 by the author.</para></legalnotice>
        <keywordset role="author">
      <keyword>XSpec</keyword>
      <keyword>XProc</keyword>
      <keyword>Software testing</keyword>
    </keywordset>
    </info>
    <section>
        <title>Introduction</title>
    <para>XProc is a language that excels at performing operation
      sequences, known as pipelines, to process documents. A pipeline is
      composed of smaller steps, and a pipeline is itself a step that
      other pipelines can use. Active support for XProc version 3
      includes two processors, a collection of resources at <link xlink:href="https://xproc.org/" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/>, a public mailing list, a
      series of virtual user group meetings, and a published book [<xref linkend="S"/>]. XProc
      3 is well positioned for use in applications that have one or more
      of the following characteristics: large-scale, widely used,
      enduring, important. Software applications having one or more of
      those characteristics tend to be good candidates for automated
      testing and behavior-driven development techniques. And yet, in
      2025, I had multiple, unrelated conversations with XProc users who
      brought up the shortage of tools for testing their XProc
      code.</para>
    <para>XSpec version 4 narrows the testing gap for XProc 3. XSpec
        [<xref linkend="XS"/>] is an open-source software product for testing several
      languages in the XML space: XSLT, XQuery, Schematron, and now
      XProc. Support for testing XProc is new in XSpec 4.0, released
      in June 2026.<footnote>
        <para>I’m especially grateful to the following people for
          their contributions to the XSpec support for testing XProc
          steps: Norm Tovey-Walsh, Adrian Bird, Achim Berndzen,
          Wendell Piez, Christophe Marchand, and Sheila
          Thomson.</para>
      </footnote></para>
    <para>In this paper, I’ll show the XML vocabulary you use to write
      an XSpec test suite for a custom XProc step or pipeline. I’ll
      explain some non-obvious semantics related to execution of the
      test suite, as well as some limitations of the support for testing
      XProc steps. After that, I’ll dive into an implementation aspect
      that demonstrates the versatility of custom XProc steps beyond the
      typical chaining into pipelines. If you’ve found it useful that
      XProc can call XSLT and wished that it could work the other way,
      too, you’ll be pleased to know that it can.</para>
    </section>
  <section xml:id="section_kkg_rjz_43c">
    <title>Testing background</title>
    <para>Imagine that you’ve created an XProc step with type
        <code>my:count-words</code> (for some namespace binding of the
      prefix <code>my</code>) that counts words in one or more
      documents, and you want to test this step. I’ll refer to it as a
        <emphasis>test target</emphasis>. At a high level, when you
      test the <code>my:count-words</code> step, you’re doing two
      things:</para>
    <orderedlist>
      <listitem>
        <para>Running (that is, calling) the step</para>
      </listitem>
      <listitem>
        <para>Verifying its results</para>
      </listitem>
    </orderedlist>
    <para>You decide what input documents or option values to use when
      running the <code>my:count-words</code> step. You also decide
      what aspects of the results are useful to verify. You express
      each verification in a way that accommodates a
        <quote>pass</quote> or <quote>fail</quote> status.</para>
    <para>For instance, suppose you want to verify that exactly one
      document appears on the <code>result</code> output port of the
      step and that the document equals a particular one you expect. If
      exactly one document appears on the <code>result</code> output
      port but the content of the document doesn’t match your
      expectation, then the first verification passes and the second one
      fails.</para>
    <para>When you create a set of specific instructions for the call
      and the verification, you have a <emphasis>test case</emphasis>.
      For any given test target, you might create multiple test cases,
      such as one test case that calls the step with one input document,
      and another that calls the step with two input documents.</para>
  </section>
  <section xml:id="section_zjj_c4z_43c">
    <title>Ingredients of an XSpec file</title>
    <para>As a software product, XSpec has two main pieces: an XML
      vocabulary for writing a test file that expresses one or more test
      cases, and the software that can carry out the specific
      instructions in your test cases and report the pass/fail status of
      each verification. When you create an XSpec file, you’re using the
      vocabulary. When you run your test file to see which verifications
      passed or failed, you’re using the XSpec software (which in turn
      calls other software, such as Saxon).</para>
    <para>At the highest structural levels of an XSpec test file,
      testing XProc code is analogous to testing code in the other
      languages that XSpec supports. For starters, the outermost element
      of an XSpec file is <code>&lt;x:description&gt;</code>, where the
      prefix <code>x</code> is bound to the XSpec namespace. The
        <code>&lt;x:description&gt;</code> element uses an attribute named
        <code>xproc</code> to point to the XProc step file or library
      file that contains (directly or through imports) your test
      target(s). The <code>xproc</code> attribute is analogous to
      attributes named <code>stylesheet</code> for testing XSLT,
        <code>query-at</code> for testing XQuery, and
        <code>schematron</code> for testing a Schematron schema.</para>
    <para>If you have one test case, then within
        <code>&lt;x:description&gt;</code>, you use the
        <code>&lt;x:scenario&gt;</code> element to contain elements that
      express how you want to call the test target and what you want to
      verify. If you have multiple test cases, you use multiple
        <code>&lt;x:scenario&gt;</code> elements. To simplify explanations
      in this paper, we’ll assume the file has no nesting of scenarios,
      so multiple <code>&lt;x:scenario&gt;</code> elements are all siblings
      of each other.</para>
    <para>In a given <code>&lt;x:scenario&gt;</code> element, you use a
      child <code>&lt;x:call&gt;</code> element to express how you want to
      call the test target. Following <code>&lt;x:call&gt;</code>, you use
      a child <code>&lt;x:expect&gt;</code> element for each verification
      you want to perform. To make test files and reports easier to
      understand, you also label each <code>&lt;x:scenario&gt;</code> and
        <code>&lt;x:expect&gt;</code> element with a text description. The
      label can be an attribute named <code>label</code> or a child
      element named <code>&lt;x:label&gt;</code>; for simplicity, we’ll use
      the attribute here.</para>
    <para>The described structures look like <xref linkend="figure_structure"/>. If fully populated, it would be a
      test file with two test cases, each of which performs two
      verifications.</para>
    <figure xml:id="figure_structure">
      <title>Structure of XSpec file (not fully populated)</title>
<programlisting language="xml" xml:space="preserve">
&lt;x:description xmlns:c="http://www.w3.org/ns/xproc-step"
  xmlns:my="http://example.com/syntax"
  xmlns:p="http://www.w3.org/ns/xproc"
  xmlns:x="http://www.jenitennison.com/xslt/xspec"
  xproc="count-words.xpl"&gt;

  &lt;x:scenario label="With one document having 3 words"&gt;
    &lt;x:call/&gt;
    &lt;x:expect label="there is exactly one output document"/&gt;
    &lt;x:expect label="an XML c:result document with content 3"/&gt;
  &lt;/x:scenario&gt;

  &lt;x:scenario label="With two documents, requesting total count"&gt;
    &lt;x:call/&gt;
    &lt;x:expect label="there is exactly one output document"/&gt;
    &lt;x:expect label="for the number of words in both documents"/&gt;
  &lt;/x:scenario&gt;

&lt;/x:description&gt;
</programlisting>
    </figure>
    <para>In <xref linkend="figure_structure"/>, the <code>xproc</code>
      attribute is the only thing specific to testing XProc. However,
      populating the <code>&lt;x:call&gt;</code> and
        <code>&lt;x:expect&gt;</code> elements with details about each test
      case reveals other ways in which the XSpec vocabulary is tailored
      for XProc.</para>
  </section>
  <section xml:id="section_zxn_wxz_43c">
    <title>Type of step to run</title>
    <para>Any XProc step you test using XSpec must have a
        <code>type</code> attribute on the
        <code>&lt;p:declare-step&gt;</code> element. To configure the
        <code>&lt;x:call&gt;</code> element for your test target, you set
      the <code>step</code> attribute to the type as a qualified name
      (e.g., <code>my:count-words</code>). The <code>step</code>
      attribute is analogous to attributes named <code>template</code>
      and <code>function</code> for testing XSLT.</para>
    <para>As usual in XML files, the XSpec file must declare any
      namespace prefixes that appear.</para>
  </section>
  <section xml:id="section_ykc_tm4_g3c">
    <title>Input documents and option values</title>
    <para>The <code>&lt;x:call&gt;</code> element often needs more
      configuration than merely the type of step to call. Your test case
      might require non-default documents on any of the step’s input
      ports, or it might require non-default values of any of the step’s
      options. The <code>&lt;x:call&gt;</code> element supports two kinds
      of child elements in tests for XProc only:</para>
    <itemizedlist>
      <listitem>
        <para><code>&lt;x:input&gt;</code>, which specifies zero or more
          documents on an input port. The <code>port</code> attribute is
          the name of the input port.</para>
      </listitem>
      <listitem>
        <para><code>&lt;x:option&gt;</code>, which specifies a value of a
          step option. The <code>name</code> attribute is the qualified
          name of the option.</para>
      </listitem>
    </itemizedlist>
    <para>As for how you specify documents or values using these
      elements, you have several choices. Most of them are analogous to
      behaviors and markup patterns that XSpec supports across
      languages. You can embed custom XML content directly within
        <code>&lt;x:input&gt;</code> or <code>&lt;x:option&gt;</code>; point
      to an external XML file; provide an XPath expression that derives
      something from the embedded content or external XML file; or
      provide a standalone XPath expression that constructs a document
      or value.</para>
    <para>In tests for XProc only, XSpec offers one additional choice,
      which is the ability to insert one or more XProc
        <code>&lt;p:document&gt;</code> elements as children of XSpec
        <code>&lt;x:input&gt;</code> or <code>&lt;x:option&gt;</code>
      elements. Using <code>&lt;p:document&gt;</code> is especially
      convenient for specifying multiple documents on an input port,
      specifying non-XML documents, or modifying the set of document
      properties of a document.</para>
    <para>When you execute the test suite, these
        <code>&lt;p:document&gt;</code> elements get passed to the XProc
      processor for evaluation. By contrast, the other ways of
      specifying input documents and option values are evaluated in
      XSLT, for reasons I explain later, in the implementation-related
      portion of this paper.</para>
    <para><xref linkend="figure_inputs"/> continues the structural
      example in <xref linkend="figure_structure"/>, by populating the
        <code>&lt;x:call&gt;</code> elements with the step type, input
      documents, and an option value.</para>
    <figure xml:id="figure_inputs">
      <title>Calling a step from XSpec file</title>
<programlisting language="xml" xml:space="preserve">
&lt;x:description xmlns:c="http://www.w3.org/ns/xproc-step"
  xmlns:my="http://example.com/syntax"
  xmlns:p="http://www.w3.org/ns/xproc"
  xmlns:x="http://www.jenitennison.com/xslt/xspec"
  xproc="count-words.xpl"&gt;

  &lt;x:scenario label="With one document having 3 words"&gt;
    &lt;x:call step="my:count-words"&gt;
      &lt;x:input port="source" as="document-node(element(doc))"&gt;
        &lt;doc&gt;One two three&lt;/doc&gt;
      &lt;/x:input&gt;
    &lt;/x:call&gt;
    &lt;x:expect label="there is exactly one output document"/&gt;
    &lt;x:expect label="an XML c:result document with content 3"/&gt;
  &lt;/x:scenario&gt;

  &lt;x:scenario label="With two documents, requesting total count"&gt;
    &lt;x:call step="my:count-words"&gt;
      &lt;x:input port="source"&gt;
        &lt;p:document href="three-words.xml"/&gt;
        &lt;p:document href="two-words.xml"/&gt;
      &lt;/x:input&gt;
      &lt;x:option name="total" select="true()"/&gt;
    &lt;/x:call&gt;
    &lt;x:expect label="there is exactly one output document"/&gt;
    &lt;x:expect label="for the number of words in both documents"/&gt;
  &lt;/x:scenario&gt;

&lt;/x:description&gt;
</programlisting>
    </figure>
    <section xml:id="section_hjy_53g_p3c">
      <title>Documents vs. elements</title>
      <para>Experienced users of XSpec might see the first
          <code>&lt;x:input&gt;</code> element in <xref linkend="figure_inputs"/> (repeated below) and wonder what
        the <code>my:count-words</code> step is actually processing: a
          <code>&lt;doc&gt;</code> element, or a document node containing
        a <code>&lt;doc&gt;</code> element.</para>
<programlisting language="xml" xml:space="preserve">
&lt;x:input port="source" as="document-node(element(doc))"&gt;
  &lt;doc&gt;One two three&lt;/doc&gt;
&lt;/x:input&gt;
</programlisting>
      <para>A reason for wondering is that if you embed an XML element
        in an XSpec element like <code>&lt;x:param&gt;</code>, within an
        XSpec test for other languages, then XSpec assumes you want
        the parameter value to be the element. If you instead want the
        parameter value to be a document node containing that element,
        you add <code>select="/"</code> to the
          <code>&lt;x:param&gt;</code> element.</para>
      <para>Tests for XProc work differently. Because XProc is so
        document-oriented, XSpec tests for XProc don’t require you to
        add <code>select="/"</code> to <code>&lt;x:input&gt;</code>
        elements in order to get a document node instead of an
        element. If you specify a <code>&lt;doc&gt;</code> element as an
        input document, the element gets wrapped in a document node,
        and the step sees the document node. If you specify five
        sibling XML elements as input documents,
          <emphasis>each</emphasis> element gets separately wrapped in
        a document node, and the step sees five input documents. The
        automatic wrapping of one <code>&lt;doc&gt;</code> element is why
        the <code>as</code> attribute above says
          <code>as="document-node(element(doc))"</code>, not
          <code>as="element(doc)"</code>.</para>
      <para>This automatic <quote>wrap-each</quote> behavior is relevant
        for both new and experienced users of XSpec to know about when
        writing tests for XProc steps. The behavior will come up again
        in the discussion of the <code>&lt;x:expect&gt;</code> element,
        next.</para>
    </section>
  </section>
  <section xml:id="section_tly_kn4_g3c">
    <title>Output documents and verifications</title>
    <para>A fully configured <code>&lt;x:call&gt;</code> element
      accomplishes the first part of the two-part testing process,
      namely, running the test target step with your choice of input
      documents and/or option values. The second part is to use
        <code>&lt;x:expect&gt;</code> elements to verify the results of
      running the step. This element can appear in XSpec tests for any
      of the supported languages. It can check whether a boolean
      condition you specify evaluates to <code>true</code>. An example
      is a condition like <code>exists($x:result//p)</code> using a
      variable named <code>x:result</code> that XSpec predefines—or
        <code>exists(//p)</code> for short, if exactly one document
      appears on a particular output port. Alternatively,
        <code>&lt;x:expect&gt;</code> can check whether an actual result
      equals a corresponding expected result that you specify.</para>
    <para>Longstanding XSpec features that also work in tests for
      XProc can help you implement use cases like verifying only a
      portion of a result and integrating your own filtering or
      normalization (canonicalization) behaviors into the verification
      process.</para>
    <para>While being largely consistent across supported test target
      languages, the <code>&lt;x:expect&gt;</code> element also has a few
      ways in which it is tailored for testing XProc. First,
        <code>&lt;x:expect&gt;</code> has an optional <code>port</code>
      attribute whose value is the name of an output port of the step.
      This attribute says which output port you are interested in for
      that verification. If you set the <code>port</code> attribute,
      XSpec predefines its variable named <code>x:result</code> as the
      sequence of output documents on that port only. Specifying the
      port also makes certain other aspects of the
        <code>&lt;x:expect&gt;</code> element more convenient, as the
      next three subsections describe.</para>
    <section xml:id="section_mcs_dgn_p3c">
      <title>Predefined variable for document properties</title>
      <para>When <code>&lt;x:expect&gt;</code> has a <code>port</code>
        attribute, XSpec predefines an additional variable, named
          <code>x:document-properties</code>. It stores properties of
        the documents on the specified output port. You can use both
          <code>$x:result</code> and <code>$x:document-properties</code>
        in verification expressions.<footnote>
          <para>An <code>&lt;x:expect&gt;</code> element without a
              <code>port</code> attribute can access the same
            information, but it requires lookups in a multiple-level map
            that stores results across all the output ports.</para>
        </footnote></para>
    </section>
    <section xml:id="section_mfc_2gn_p3c">
      <title>Automatic wrapping of embedded expected results in document
        nodes</title>
      <para>If you are embedding an XML expected result within
          <code>&lt;x:expect&gt;</code> for XSpec to compare with the
        unmodified actual result on the specified port, then XSpec
        interprets each child node as a separate document and
        automatically wraps each in a document node. Here,
          <quote>unmodified</quote> means you are not using a
          <code>test</code> attribute of <code>&lt;x:expect&gt;</code>
        either to drill down into the actual result (e.g.,
          <code>$x:result//p</code>) or to derive data from the actual
        result (e.g., <code>count($x:result)</code>).</para>
      <para>For instance, <xref linkend="figure_expect-wrap"/>
        compares the sequence of actual result documents on the output
        port named <code>result</code> with a pair of expected
        documents. One document contains the element
          <code>&lt;a/&gt;</code> and the other document contains the
        element <code>&lt;b/&gt;</code>. Your intention is probably more
        likely to be this pair of unrelated documents than a single
        document containing sibling top-level elements
          <code>&lt;a/&gt;</code> and <code>&lt;b/&gt;</code>. Also, you are
        unlikely to expect the step to produce bare elements on its
        output port, because XProc steps that produce XML documents
        represent them as document nodes.</para>
      <figure xml:id="figure_expect-wrap">
        <title>Two embedded XML elements</title>
<programlisting language="xml" xml:space="preserve">
&lt;x:expect label="..." port="result"&gt;
  &lt;a/&gt;
  &lt;b/&gt;
&lt;/x:expect&gt;
</programlisting>
      </figure>
    </section>
    <section xml:id="section_a5b_2gn_p3c">
      <title>Failure details in HTML report</title>
      <para>Another consequence of setting a <code>port</code> attribute
        on the <code>&lt;x:expect&gt;</code> element affects the display of
        verification failures in the HTML report of test results. For
        all the supported languages, the report has a section of details
        about each verification failure. If the failure section of the
        report shows diffs between actual and expected results, then you
        probably want the displayed actual result to be only the result
        on the XProc output port that you specified as your port of
        interest. Indeed, that’s what XSpec does.</para>
      <para>In case the failure is caused by a boolean condition that
        evaluates to <code>false</code>, the failure section of the
        report shows an actual result, and it’s up to you to determine
        why the condition was false for that result. Here, too, XSpec
        shows the actual result on that output port only. Without a
          <code>port</code> attribute, the actual result appearing in
        this section of the report would be a multiple-level map that
        stores results across all the output ports. This map would be
        cumbersome to read when investigating the failure.</para>
    </section>
  </section>
  <section xml:id="section_ank_x5m_p3c">
    <title>Fully populated XSpec file</title>
    <para>We can now complete the syntax example. <xref linkend="figure_verification"/> shows a fully populated XSpec
      file that calls a step with particular documents and option
      values and then verifies some results. (The XProc and XML files
      are not shown, as the purpose of the example is merely to
      illustrate XSpec syntax.)</para>
    <figure xml:id="figure_verification">
      <title>XSpec file that calls a step and verifies results</title>
<programlisting language="xml" xml:space="preserve">
&lt;x:description xmlns:c="http://www.w3.org/ns/xproc-step"
  xmlns:my="http://example.com/syntax"
  xmlns:p="http://www.w3.org/ns/xproc"
  xmlns:x="http://www.jenitennison.com/xslt/xspec"
  xproc="count-words.xpl"&gt;

  &lt;x:scenario label="With one document having 3 words"&gt;
    &lt;x:call step="my:count-words"&gt;
      &lt;x:input port="source" as="document-node(element(doc))"&gt;
        &lt;doc&gt;One two three&lt;/doc&gt;
      &lt;/x:input&gt;
    &lt;/x:call&gt;
    &lt;x:expect label="there is exactly one output document"
      port="result" test="count($x:result)" select="1"/&gt;
    &lt;x:expect label="an XML c:result document with content 3"
      port="result"&gt;
      &lt;c:result&gt;3&lt;/c:result&gt;
    &lt;/x:expect&gt;
  &lt;/x:scenario&gt;

  &lt;x:scenario label="With two documents, requesting total count"&gt;
    &lt;x:call step="my:count-words"&gt;
      &lt;x:input port="source"&gt;
        &lt;p:document href="three-words.xml"/&gt;
        &lt;p:document href="two-words.xml"/&gt;
      &lt;/x:input&gt;
      &lt;x:option name="total" select="true()"/&gt;
    &lt;/x:call&gt;
    &lt;x:expect label="there is exactly one output document"
      port="result" test="count($x:result)" select="1"/&gt;
    &lt;x:expect label="for the number of words in both documents"
      port="result" select="5"/&gt;
  &lt;/x:scenario&gt;

&lt;/x:description&gt;
</programlisting>
    </figure>
    <para>Running this test suite produces an HTML report that looks
      like <xref linkend="figure_test-result-report"/>.</para>
    <figure xml:id="figure_test-result-report">
      <title>HTML report of test results</title>
      <mediaobject>
        <alt>XSpec report of test results, showing both test cases and
          the pass/fail status of each verification.</alt>
        <imageobject>
          <imagedata fileref="../../../vol31/graphics/Galtman01/Galtman01-001.png"/>
        </imageobject>
      </mediaobject>
    </figure>
  </section>
  <section xml:id="section_iws_5n4_g3c">
    <title>Familiar XSpec features</title>
    <para>The preceding descriptions of testing XProc steps have
      pointed out some ways in which the XSpec vocabulary expresses
      XProc-specific information, as well as some ways in which the
      XSpec semantics align with XProc handling of data and documents.
      At the same time, many features of XSpec work the same way in
      tests for either XProc or other languages that XSpec supports.
      Here is a non-comprehensive list of such language-agnostic
      features:</para>
    <itemizedlist>
      <listitem>
        <para><code>&lt;x:scenario&gt;</code> element for individual test
          cases and for grouping</para>
      </listitem>
      <listitem>
        <para><code>&lt;x:import&gt;</code> element for importing from
          another XSpec file</para>
      </listitem>
      <listitem>
        <para><code>&lt;x:helper&gt;</code> element for integrating custom
          code into the testing process (where the custom code’s
          language depends on the language of the test target; tests for
          XProc can use custom code written in either XProc or
          XSLT)</para>
      </listitem>
      <listitem>
        <para><code>&lt;x:like&gt;</code> element for reusing XSpec
          code</para>
      </listitem>
      <listitem>
        <para><code>&lt;x:variable&gt;</code> element for defining
          test-specific variables</para>
      </listitem>
      <listitem>
        <para><code>&lt;x:pending&gt;</code> element and
            <code>pending</code> attribute for disabling portions of a
          test file</para>
      </listitem>
      <listitem>
        <para><code>focus</code> attribute for enabling only specific
          scenarios and disabling all others</para>
      </listitem>
      <listitem>
        <para>Attribute value templates</para>
      </listitem>
      <listitem>
        <para>Text value templates that behave like those in XSLT</para>
      </listitem>
      <listitem>
        <para>Three-dot syntax (<code>...</code>) for skipping pieces of
          XML content when comparing results</para>
      </listitem>
      <listitem>
        <para>Inheritance and override semantics in nested
            <code>&lt;x:scenario&gt;</code> elements</para>
      </listitem>
      <listitem>
        <para>Whitespace handling (default behaviors and ways to
          override them)</para>
      </listitem>
      <listitem>
        <para>HTML report of test results</para>
      </listitem>
    </itemizedlist>
  </section>
  <section xml:id="section_dn2_h44_g3c">
    <title>Limitations</title>
    <para>You should be aware of the following limitations of XSpec
      v4.0 support for testing XProc:</para>
    <itemizedlist>
      <listitem>
        <para>It requires XML Calabash 3 as the XProc processor. The
          reason for this limitation is that the implementation
          depends on a nonstandard feature of XML Calabash 3
          (discussed in the second half of this paper). Furthermore,
          the support in XSpec v4.0 expects the XSLT processor to be
          the Saxon version that is bundled with XML Calabash. Other
          versions might happen to work, but there is no
          guarantee.</para>
        <note>
          <para>A future XSpec version (hopefully later in 2026) will
            be able to use MorganaXProc-IIIee as the XProc processor,
            along with Saxon 13 as the XSLT processor. Support will
            require a version of MorganaXProc-IIIee that has the
            feature described at [<xref linkend="MX"/>].</para>
        </note>
      </listitem>
      <listitem>
        <para>It requires the step referenced by <code>&lt;x:call&gt;</code> to have a
            <code>type</code> attribute in the step declaration.</para>
      </listitem>
      <listitem>
        <para>It cannot test steps that are private or locally defined
          within a step.</para>
      </listitem>
      <listitem>
        <para>It cannot directly test steps that are part of the XProc
          specifications themselves (core specification, standard step
          library, and additional step libraries listed at [<xref linkend="XPS"/>]). However, XSpec can test custom steps
          that invoke standard steps that the XProc processor
          supports. That includes steps like <code>p:run</code> and
            <code>p:file-copy</code>.</para>
      </listitem>
      <listitem>
        <para>It cannot assign a non-default value to an XProc option
          that is declared as static.</para>
      </listitem>
      <listitem>
        <para>It cannot verify informational messages that go to the
          console. (It can verify information about dynamic errors,
          however.)</para>
      </listitem>
      <listitem>
        <para>Any configuration you specify for the XProc processor
          takes effect throughout the test suite, not per
          scenario.</para>
      </listitem>
    </itemizedlist>
    <para>Some XProc steps present testability challenges even though
      XSpec can call the steps. Testing code that has side effects
      (that is, the code modifies its environment) is tricky. For
      instance, suppose your step invokes <code>p:file-copy</code>,
      and you want to verify existence of the copied file. Maybe the
      copy existed even before your step ran. Maybe calling your step
      to copy the file interferes with a subsequent test case. XSpec
      doesn’t give you a way to set up the environment before a test
      case executes, clean up afterward, or enforce an order of
      operations; this is an XSpec limitation across languages, not
      specific to testing XProc.</para>
    <para>This concludes the part of this paper about user-facing
      aspects of XSpec support for testing XProc. Readers not interested
      in the implementation may skip to the conclusion.</para>
  </section>
  <section xml:id="section_my1_hjw_g3c">
    <title>Implementation: XSLT-based test runner</title>
    <para>Now that we’ve glimpsed how to write an XSpec test suite for
      XProc, let’s look more at what happens when you execute the test
      suite. Somehow, an XProc processor has to invoke your test target
      with the input documents and option values that each test case
      specifies.</para>
    <para>A precedent worth considering is how XSpec executes a test
      suite for XQuery. An XSLT stylesheet known as the <emphasis>XSpec
        compiler</emphasis> generates an XQuery main module known as the
        <emphasis>test runner</emphasis>. The test runner is what calls
      the XQuery code being tested and determines which verifications
      pass or fail.</para>
    <para>The XSpec repository’s associated wiki [<xref linkend="XW"/>]
      contains a UML diagram that shows how XSpec works internally, and
      it appears with minor labeling modifications in <xref linkend="figure_uml"/>. In this diagram, the XSpec compiler is
      the box near the top that says, <code>compile-xslt-tests.xsl (or
        compile-xquery-tests.xsl)</code>. The test runner is the box to
      which arrow 5 points.</para>
    <figure xml:id="figure_uml">
      <title>XSpec processing</title>
      <mediaobject>
        <alt>Diagram of XSpec internal process. User runs shell
          script, which runs XSpec compiler, which generates test
          runner.</alt>
        <imageobject>
          <imagedata fileref="../../../vol31/graphics/Galtman01/Galtman01-002.png"/>
        </imageobject>
      </mediaobject>
    </figure>
    <para>One logical way to extend XSpec for testing XProc would be to
      make the XSpec compiler generate an XProc pipeline as the test
      runner, instead of generating a stylesheet or query. After all, an
      XProc pipeline is capable of calling the XProc step being tested.
      XProc is also capable of determining which verifications pass or
      fail.</para>
    <para>Although generating an XProc-based test runner would be a
      logical implementation path, I chose to do it differently.
      Before explaining that, I need to share some XProc processor
      background.</para>
    <section xml:id="section_jn5_z53_43c">
      <title>Nonstandard feature: Step functions</title>
      <para>The XML Calabash documentation has a topic named
          <quote>Calling steps as functions</quote> [<xref linkend="XC"/>] about a nonstandard feature that creates
        so-called <emphasis>step functions.</emphasis> A step function
        is an XPath function that the XProc processor creates,
        corresponding to a custom step that declares an explicit type.
        The step function behaves like the step and is callable from
        XPath expressions. For instance, if you create a typed XProc
        step that counts words in a document, then you can use its
        corresponding step function to count words in a document that
        is accessible from an XPath expression. That XPath expression
        can reside within XProc, XSLT, XQuery, and so on—as long as
        the XPath processor knows how to access the step function that
        the XProc processor created. The <quote>knowing how to
          access</quote> part is related to the scope of steps in
        XProc or the configuration of a non-XProc processor, and you
        can find those details in the XProc processor’s
        documentation.</para>
    </section>
    <section xml:id="section_ypn_lwq_t3c">
      <title>Connection with XSpec test runner</title>
      <para>Returning to the matter of the XSpec test runner for
        testing XProc code: XSpec has mature capabilities for testing
        stylesheet functions that it calls from XPath expressions. I
        figured that XSpec should be able to test XProc steps if it
        can call XProc step functions and capture their results. In
        step functions, I saw an opportunity to support testing XProc
        by building on the existing, solid XSpec compiler code for
        testing stylesheet functions. To me, that approach seemed
        easier than generating a new, XProc-based test runner that
        would have most or all of the features that XSpec users are
        accustomed to. Therefore, I modified the XSpec compiler so
        that the test runner for testing XProc is an XSLT stylesheet.
        That is, the way XSpec executes a test suite for XProc is by
        generating and executing an XSLT-based test runner that relies
        on the step function feature of the XProc processor.</para>
      <para>When I talk about XSLT relying on the step function
        feature of the XProc processor, I mean that the XSLT processor
        is configured so that it is capable of telling the XProc
        processor to perform an XProc step’s operation and return
        results back to XSLT. The XSLT processor and XProc processor
        hand control back and forth as the XSLT-based test runner
        dictates. It doesn’t mean that the XProc step is being
        translated into XSLT or executed by an XSLT processor.</para>
      <para>As of the XSpec v4.0 release date, step functions are a
        nonstandard feature of XML Calabash only. Therefore, the
        ability to test XProc using XSpec v4.0 depends on XML Calabash
        and cannot run with MorganaXProc-III. Although depending on a
        nonstandard feature of one processor is not ideal, support for
        testing XProc with one processor is much better than no
        support at all. At least the step function feature is
        documented, and it is included in the XML Calabash automated
        test suite. As noted earlier, a future XSpec version will be
        able to use MorganaXProc-IIIee, which is expected to start
        supporting step functions.</para>
    </section>
  </section>
  <section xml:id="section_p3p_hdj_43c">
    <title>Calling XProc steps as XPath functions</title>
    <para>The support for testing XProc in XSpec v4 uses step functions
      for two distinct tasks that only an XProc processor can
      accomplish:</para>
    <orderedlist numeration="loweralpha">
      <listitem>
        <para>Reporting the version number of the XProc processor
          being used</para>
      </listitem>
      <listitem>
        <para>Invoking the step being tested</para>
      </listitem>
    </orderedlist>
    <para>Let’s look at these in order. The first case is simpler and
      helps explain the paradigm, while the second case is more
      central to the implementation.</para>
    <section xml:id="section_j5w_wn4_g3c">
      <title>From XSLT to XProc, to get a version number</title>
      <para>When you run an XSpec test suite for XProc, part of the
        console output indicates the versions of the XSLT and XProc
        processors that are in use. (In the UML diagram in <xref linkend="figure_uml"/>, this text is part of item 8,
          <quote>Print test result.</quote>)</para>
<programlisting xml:space="preserve">
Running Tests...
Testing with SAXON HE 12.9 and XML Calabash 3.0.42
</programlisting>
      <para>This command-line output comes from the test runner,
        specifically from an XSLT message instruction. XML Calabash
        can report its own version number using a function named
          <code>p:system-property</code>, but how can a test runner
        written in XSLT call a function that belongs to an XProc
        processor? The <code>p:system-property</code> function is not
        callable from XSLT.</para>
      <para>The answer is that the step function feature acts as a
        bridge from XSLT to XProc and back. <xref linkend="figure_pf-system-property"/> shows a small XProc step
        of type <code>pf:system-property</code> that wraps around the
          <code>p:system-property</code> function. (The prefix
          <code>pf</code> binds to an XSpec-related namespace URI,
          <code>http://www.jenitennison.com/xslt/xspec/xproc/steps/wrap-standard-functions</code>.)</para>
      <figure xml:id="figure_pf-system-property">
        <title>Step calling <code>p:system-property</code>
          function</title>
<programlisting language="xml" xml:space="preserve">
&lt;p:declare-step type="pf:system-property" name="system-property"&gt;
  &lt;p:output port="result"/&gt;
  &lt;p:option name="property" required="true" as="xs:string"/&gt;
  &lt;p:identity&gt;
    &lt;p:with-input select="string()"&gt;
      &lt;p:inline&gt;{ p:system-property($property) }&lt;/p:inline&gt;
    &lt;/p:with-input&gt;
  &lt;/p:identity&gt;
&lt;/p:declare-step&gt;
</programlisting>
      </figure>
      <para>Under appropriate configuration circumstances, a stylesheet
        like the test runner can enjoy the behavior of the custom
          <code>pf:system-property</code> step by calling it as a
        function within an XPath expression anywhere in the stylesheet.
        To provide a value for the <code>property</code> step option,
        the function call provides a map with key <code>property</code>
        as a function argument. This function syntax is part of how the
        step function feature works. In <xref linkend="figure_step-function-call"/>, the <code>select</code>
        attribute illustrates the function call in an XSLT element taken
        from the XSLT-based test runner that XSpec produced.</para>
      <figure xml:id="figure_step-function-call">
        <title>XSLT element calling <code>pf:system-property</code> step
          as a function</title>
<programlisting language="xml" xml:space="preserve">
&lt;xsl:value-of select="
  Q{http://www.jenitennison.com/xslt/xspec/xproc/steps/wrap-standard-functions}system-property(
    map{'property': 'Q{http://www.w3.org/ns/xproc}product-version'}
  )?result"/&gt;
</programlisting>
      </figure>
      <para>The function is executed by the XProc processor, which
        reports its version number as a document (in this case, a
        string) on the output port named <code>result</code>. As a
        function output, the XProc processor returns a map containing
        output documents organized by output port. To get the string,
        the XPath expression in the stylesheet uses map lookup syntax
          <code>?result</code> after the function call.</para>
      <section xml:id="section_rbj_22j_43c">
        <title>Summary of simple usage of step function</title>
        <para>To recap, the objective was to have the XSLT-based test
          runner fetch some data that only the XProc processor knows.
          When developing this feature, I:</para>
        <orderedlist>
          <listitem>
            <para>Created an XProc step that obtains the data (<xref linkend="figure_pf-system-property"/>), and</para>
          </listitem>
          <listitem>
            <para>Made the (generated) test runner call this XProc
              step as an XPath function and then retrieve the desired
              data from a map in which the XProc processor stores the
              step’s output (<xref linkend="figure_step-function-call"/>).</para>
          </listitem>
        </orderedlist>
        <para>I also ensured that the process of invoking the XSpec
          test runner has a suitable configuration for calling the
          step function.</para>
      </section>
    </section>
    <section xml:id="section_pw5_yjw_g3c">
      <title>From XSLT to XProc, to run the test target</title>
      <para>The creation of step functions also applies to steps that
        return documents more complicated than a string. There is no
        limit on the number of ports, options, or documents. Initially,
        I thought the XSLT-based test runner could use an XPath function
        call to invoke the test target. However, that turned out to be
        inadequate.</para>
      <section xml:id="section_crw_qp3_43c">
        <title>Relevance of XProc document properties</title>
        <para>The reason is that document properties, which are not
          relevant for the <code>pf:system-property</code> step we
          looked at earlier, might be relevant in an arbitrary XProc
          test target. When you travel from XSLT to XProc and back, by
          calling a step function and then examining the result in XSLT,
          the process doesn’t necessarily handle document properties in
          the way you need for proper testing.<footnote>
            <para>Thanks to Adrian Bird for asking about document
              properties when reviewing my code.</para>
          </footnote> For instance, the XProc notion of content type
          doesn’t translate precisely to the XPath/XDM world.</para>
        <para>For instance, suppose your test target is a step whose
          input and output documents have content type
            <code>text/csv</code>. The XSLT-based test runner can
          provide comma-separated text in an input argument to a step
          function but lacks a way to assign a content type. Therefore,
          the step sees a more generic content type: plain text. Upon
          receiving an output map from the step function, the XSLT-based
          test runner can retrieve a document, but its
            <code>content-type</code> property is gone. Therefore, the
          XSpec <code>&lt;x:expect&gt;</code> element cannot verify the
          content type.</para>
        <para>To make an XSLT-based test runner work well, the system
          needs some opportunities to provide properties of input
          documents and to make XSLT aware of the properties of output
          documents. Having the XSLT-based test runner call the step
          function corresponding to the XProc test target doesn’t offer
          any such opportunities; the transition between XSLT and the
          test target is too abrupt.</para>
      </section>
      <section xml:id="section_gfw_jp3_43c">
        <title>Overview of document property handling from XSLT to XProc
          and back</title>
        <para>To handle document properties adequately for testing, the
          XSpec support for testing XProc has two levels of indirection
          that serve as a kind of anteroom for transferring properties
          back and forth between XSLT and XProc. The code in these
          levels of indirection performs the following tasks in XProc,
          for each XSpec test case:</para>
        <orderedlist>
          <listitem>
            <para>Receive any input documents and option values that
              the XSLT test runner already computed. (The reason for
              computing some input data in XSLT is to reuse
              pre-existing XSpec compiler code that was written to
              support tests for XSLT.) For instance, the XSpec element
                <code>&lt;x:option name="my-opt" select="1"/&gt;</code>
              causes the test runner to set up the option value in
              XSLT and pass it to XProc.</para>
          </listitem>
          <listitem>
            <para>Compute any input data that requires processing in
              XProc, namely, <code>&lt;p:document&gt;</code> elements
              within XSpec <code>&lt;x:input&gt;</code> or
                <code>&lt;x:option&gt;</code> elements. The resulting
              document has the document properties you expect from an
              XProc processor.</para>
          </listitem>
          <listitem>
            <para>Call the XProc test target, passing in the data from
              steps 1 and 2.</para>
          </listitem>
          <listitem>
            <para>Capture the properties of output documents in a data
              structure that can be returned back to the XSLT world,
              so that XSpec can proceed with evaluating
                <code>&lt;x:expect&gt;</code> verification
              elements.</para>
          </listitem>
          <listitem>
            <para>Return output documents and the additional data from
              step 4 back to XSLT.</para>
          </listitem>
        </orderedlist>
        <para>Returning to the example with CSV documents, we can
          learn how performing these tasks (especially items 2 and 4)
          solves the problems mentioned earlier. Suppose we are
          testing a step that has CSV documents on both the input and
          output port. Consider the following XSpec test scenario for
          this step:</para>
        <figure xml:id="figure_csv">
          <title>XSpec test case with CSV input/output</title>
<programlisting language="xml" xml:space="preserve">
&lt;x:scenario label="Call a step"&gt;
  &lt;x:call step="my-csv:test-target-step"&gt;
    &lt;x:input port="source"&gt;
      &lt;p:document href="document.csv"/&gt;
    &lt;/x:input&gt;
  &lt;/x:call&gt;
  &lt;x:expect label="Verify content" port="result"&gt;1,2&lt;/x:expect&gt;
  &lt;x:expect label="Verify content type" port="result"
    test="$x:document-properties?(QName('', 'content-type'))"
    select="'text/csv'"/&gt;
&lt;/x:scenario&gt;
</programlisting>
        </figure>
        <para>Item 2 in the task list causes the
            <code>document.csv</code> file to be loaded by the XProc
          processor, not the XSLT processor. The XProc processor also
          assigns the document the <code>text/csv</code> content type
          as expected, not the more generic <code>text/plain</code>
          type. Item 3 runs the test target
            (<code>my-csv:test-target-step</code>) using this document
          that has content type <code>text/csv</code>. Items 4 and 5
          return data to the XSLT-based test runner, and this data
          includes all properties of output documents. Tasks 2, 3, 4,
          and 5 all happen in the same XProc process. When the
          XSLT-based test runner regains control, it evaluates the
            <code>&lt;x:expect&gt;</code> elements. In that context, the
          XSpec variable named <code>x:document-properties</code>
          enables the test author to verify properties of output
          documents, including the <code>content-type</code>
          property.</para>
      </section>
      <section xml:id="section_drw_jp3_43c">
        <title>Step runner</title>
        <para>The overview mentioned two levels of indirection. The
          first is what I call a <quote>step runner.</quote> It’s a
          small XProc step that uses the standard <code>p:run</code>
          step to call a dynamically generated XProc step that appears
          on the input port. The step runner’s corresponding step
          function is what the XSLT-based test runner calls during
          test execution. It’s the same function call from XSLT to
          XProc for any test target or test case.</para>
        <para>The step runner looks like the following:</para>
        <figure xml:id="figure_f5r_bbc_43c">
          <title>Step runner</title>
<programlisting language="xml" xml:space="preserve">
&lt;p:declare-step xmlns:impl="urn:x-xspec:compile:impl"
  xmlns:p="http://www.w3.org/ns/xproc"
  type="impl:step-runner" name="step-runner" version="3.1"&gt;

  &lt;p:input port="step-to-call-test-target" content-types="application/xml"/&gt;
  &lt;p:output port="map-of-outputs" pipe="map-of-outputs@call-test-target"
    content-types="application/json"/&gt;
  &lt;p:option name="map-of-inputs" as="map(*)"/&gt;
  &lt;p:option name="map-of-options" as="map(*)"/&gt;

  &lt;p:run name="call-test-target"&gt;
    &lt;p:with-input pipe="step-to-call-test-target@step-runner"/&gt;
    &lt;p:run-option name="map-of-inputs" select="$map-of-inputs"/&gt;
    &lt;p:run-option name="map-of-options" select="$map-of-options"/&gt;
    &lt;p:output port="map-of-outputs" primary="true"/&gt;
  &lt;/p:run&gt;
&lt;/p:declare-step&gt;
</programlisting>
        </figure>
        <para>Generic names like <code>map-of-inputs</code> are a hint
          that this step is not at all customized for a specific test
          target or test case.</para>
      </section>
      <section xml:id="section_m2x_jp3_43c">
        <title>Generated test-case step</title>
        <para>The dynamically generated XProc step that appears on the
          step runner’s input port is more interesting, and that’s the
          second level of indirection. I call it a <quote>test-case
            step.</quote> The XSpec compiler generates a test-case step
          for each test case in your XSpec test suite. When generating a
          test-case step, the XSpec compiler relies on information
          located in the XSpec document, as indicated in <xref linkend="table_test-case-step"/>.</para>
        <table frame="void" xml:id="table_test-case-step">
          <caption>
            <para>Mapping XSpec test case to corresponding XProc
              code</para>
          </caption>
          <col width="50%" span="1"/>
          <col width="50%" span="1"/>
          <thead>
            <tr>
              <th>Information in XSpec</th>
              <th>Resulting code in test-case step</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Location of XProc file containing the test target,
                from the <code>&lt;x:description&gt;</code> element’s
                  <code>xproc</code> attribute</td>
              <td><code>&lt;p:import&gt;</code></td>
            </tr>
            <tr>
              <td>Type of step to call, from the
                  <code>&lt;x:call&gt;</code> element’s <code>step</code>
                attribute</td>
              <td>XML element that invokes the step</td>
            </tr>
            <tr>
              <td>Any documents to pass in, from
                  <code>&lt;x:input&gt;</code> elements</td>
              <td><code>&lt;p:with-input&gt;</code></td>
            </tr>
            <tr>
              <td>Any option values to pass in, from
                  <code>&lt;x:option&gt;</code> elements</td>
              <td><code>&lt;p:with-option&gt;</code></td>
            </tr>
            <tr>
              <td>Any input ports and options that should use defaults
                that the step declares, implied by the absence of
                  <code>&lt;x:input&gt;</code> or
                  <code>&lt;x:option&gt;</code> elements</td>
              <td>Absence of <code>&lt;p:with-input&gt;</code> or
                  <code>&lt;p:with-option&gt;</code></td>
            </tr>
            <tr>
              <td>Whether to catch any dynamic errors, from the
                optional <code>catch</code> attribute of ancestor
                  <code>&lt;x:scenario&gt;</code> elements</td>
              <td><code>&lt;p:try&gt;</code></td>
            </tr>
          </tbody>
        </table>
        <para>For instance, the <code>&lt;x:call&gt;</code> element in
            <xref linkend="figure_csv"/> corresponds to the following
          XProc code within the test-case step:</para>
<figure xml:id="figure_m5n_h5z_43c">
          <title>Invocation of test target in test-case step</title>
<programlisting language="xml" xml:space="preserve">
&lt;my-csv:test-target-step xmlns:my-csv="http://example.com/csv"
  name="test-target"&gt;
  &lt;p:with-input port="source"&gt;
    &lt;p:document
      xml:base="file:/C:/.../example/csv-example.xspec"
      href="document.csv"/&gt;
  &lt;/p:with-input&gt;
&lt;/my-csv:test-target-step&gt;
</programlisting>
        </figure>
        <para>The process of generating a test-case step also relies
          on parsing the XProc code that declares the test target
          step, to find the names of output ports. That is because the
          test-case step needs to organize output data into a map to
          send back to the step runner, which in turn sends it back to
          the XSLT world. The XSLT test runner needs the data in this
          map when defining the variables named <code>x:result</code>
          and <code>x:document-properties</code>, among other
          things.</para>
        <para>Organizing data into the output map is straightforward,
          relying mostly on the <code>p:identity</code> and
            <code>p:json-merge</code> steps. <xref linkend="figure_create-map"/> and <xref linkend="figure_combine-map"/> give the idea. There is also
          a little extra processing to accommodate the case of an output
          port that produces no documents and the case of a caught
          dynamic error.</para>
<figure xml:id="figure_create-map">
          <title>Storing document and properties in map</title>
<programlisting language="xml" xml:space="preserve">
&lt;p:identity&gt;
  &lt;p:with-input
    select="map{ 'result':
      map{ 'document': ., 'document-properties': p:document-properties(.) }
    }"
    pipe="result@test-target"/&gt;
&lt;/p:identity&gt;
</programlisting>
        </figure>
        <figure xml:id="figure_combine-map">
          <title>Combining maps across output ports</title>
<programlisting language="xml" xml:space="preserve">
&lt;p:json-merge duplicates="combine" name="merged"&gt;
  &lt;p:with-input port="source"&gt;
    &lt;p:pipe step="map_result"/&gt;
    &lt;p:pipe step="map_secondary"/&gt;
  &lt;/p:with-input&gt;
&lt;/p:json-merge&gt;
</programlisting>
        </figure>
      </section>
      <section xml:id="section_rbp_jp3_43c">
        <title>Summary of more complicated usage of step
          function</title>
        <para>To recap this more complicated case, the primary
          objective was to invoke the test target, but that wasn’t
          actually enough. It was also necessary to set up input
          documents in true XProc fashion and provide the XSLT-based
          test runner with information about document properties and
          caught errors. For a given test case, all these operations
          needed to happen in the <emphasis>same</emphasis> journey
          from XSLT to XProc and back. To that end, when developing
          this feature, I:</para>
        <orderedlist>
          <listitem>
            <para>Determined all the information the test runner might
              need for verification, and designed a map data structure
              capable of storing that information.</para>
          </listitem>
          <listitem>
            <para>Made the (generated) test runner declare, for each
              test case, an XProc step that does all the necessary XProc
              work for that test case and creates a map having the
              desired structure.</para>
          </listitem>
          <listitem>
            <para>Created a fixed XProc step that uses
                <code>p:run</code> and that the XSLT-based test runner
              can call as an XPath function.</para>
          </listitem>
          <listitem>
            <para>Made the test runner call the step function in item
              3, passing in the step declaration in item 2 plus any
              input data that the test runner itself computed in
              XSLT.</para>
          </listitem>
        </orderedlist>
        <para>I also made the XSpec compiler’s verification operations
          retrieve documents, properties, and error information from
          the output map as needed.</para>
      </section>
    </section>
  </section>
  <section xml:id="section_ot2_q5l_w1c">
    <title>Conclusion</title>
    <para>If you believe that the future of markup includes the XProc
      language and you want to bolster the future of your own XProc
      code, then it’s wise to invest in readable, runnable
      descriptions of how your code behaves. I’m delighted that XSpec
      now extends its capabilities for automated testing and
      behavior-driven development into the XProc realm. When designing
      user-facing aspects of XSpec for XProc, I wanted XProc users to
      find it sufficiently tailored for XProc, and I also wanted
      existing XSpec users to find it familiar. The tailored-for-XProc
      features include dedicated XSpec elements to represent input
      port documents and option values; the ability to use the XProc
        <code>&lt;p:document&gt;</code> element within your test suite
      for full control over input data; access to document properties;
      and verifications that optionally focus on one output port at a
      time. XSpec for testing XProc still has an XSpec feel, though,
      because the way you do things like organize and label test
      cases, express what you want to verify, and examine your test
      results is consistent across supported languages.</para>
    <para>We saw that the extension of XSpec for testing XProc depends
      on the ability of an XSLT processor and an XProc processor to
      call an XProc step as if it were an XPath function. This
      capability shortened my development time for this XSpec
      enhancement and, more critically, motivated me to attempt it in
      the first place. In my opinion, this <quote>step
        function</quote> feature is worth knowing about so that you
      can apply it in situations where the feature fits well. Such
      situations might even include an XSpec test suite, where
        <code>&lt;x:helper&gt;</code> integrates your custom XProc step
      into the testing process (e.g., for normalization, whitespace
      handling, or filtering) and you then call the step as a step
      function. More generally, an XProc step that wears an XPath hat
      can gain entry to—and exhibit its special XProc behaviors
      in—XPath expressions that reside within XProc or other languages
      that use XPath as an expression language.</para>
  </section>
  <appendix>
    <title>How XSpec Tests Itself</title>
    <para>This section gives high-level information about the XSpec
      repository’s way of testing its own support for testing XProc
      steps. The testing has multiple independent parts:</para>
    <itemizedlist>
      <listitem>
        <para>Unit-level tests for selected XSLT templates and
          functions in the XSpec compiler. These tests help verify
          that the templates and functions generate the expected code
          in the test runner or test-case step.</para>
      </listitem>
      <listitem>
        <para>XProc steps and XSpec tests for them, in which all
          verifications are supposed to pass. The emphasis is on
          verifying XSpec features (e.g., access to properties of
          documents on output ports, or inheritance in nested test
          scenarios) as they pertain to testing XProc, not testing the
          underlying XProc processor.</para>
      </listitem>
      <listitem>
        <para>XProc steps and XSpec tests for them, in which an error
          is supposed to occur at compile time or run time. These
          tests verify that the expected error messages occur.</para>
      </listitem>
      <listitem>
        <para>XProc steps and XSpec tests for them, in which we
          compare the HTML report, JUnit report, and an intermediate
          test-result format against previously stored files. These
          tests don’t require all verifications to pass, so the tests
          are especially useful for verifying the test failure
          information in the HTML report.</para>
      </listitem>
      <listitem>
        <para>Commands that execute XSpec tests, to verify that the
          command syntaxes (shell/batch scripts, Ant build script, and
          XProc harness) work as expected. These tests are especially
          useful for verifying informational messages in the console
          or error-producing situations that cannot be tested in other
          ways.</para>
      </listitem>
    </itemizedlist>
  </appendix>
    <bibliography>
        <title>Bibliography</title>
    <bibliomixed xml:id="XC">“Calling steps as functions,” XML
      Calabash documentation, <link xlink:href="https://docs.xmlcalabash.com/userguide/current/pipelineception.html" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/></bibliomixed>
    <bibliomixed xml:id="S">Siegel, Erik. <emphasis>XProc 3.0
        Programmer Reference</emphasis>. XML Press: Laguna Hills, CA,
      2020.</bibliomixed>
    <bibliomixed xml:id="XPC">Walsh, Norman, Achim Berndzen, Gerrit
      Imsieke, and Erik Siegel, Editors. XProc 3.1: An XML Pipeline
      Language, Community Group Report 30 May 2025, <link xlink:href="https://spec.xproc.org/3.1/xproc/" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/></bibliomixed>
    <bibliomixed xml:id="XD">Walsh, Norman, Achim Berndzen, Gerrit
      Imsieke, and Erik Siegel, Editors. XProc 3.1: dynamic pipeline
      execution, Community Group Report 30 May 2025, <link xlink:href="https://spec.xproc.org/3.1/run/" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/></bibliomixed>
    <bibliomixed xml:id="XP">XProc, <link xlink:href="https://xproc.org" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/></bibliomixed>
    <bibliomixed xml:id="XPS">XProc 3.1 Specifications, <link xlink:href="https://xproc.org/specifications.html" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/></bibliomixed>
    <bibliomixed xml:id="MX">“XProc steps as XPath functions (in XSLT
      or XQuery),” MorganaXProc-III documentation, <link xlink:href="https://www.xml-project.com/manual/ch05.html" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/></bibliomixed>
        <bibliomixed xml:id="XS">XSpec, <link xlink:href="https://github.com/xspec/xspec" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/></bibliomixed>
    <bibliomixed xml:id="XW">XSpec wiki, <link xlink:href="https://github.com/xspec/xspec/wiki" xlink:type="simple" xlink:show="new" xlink:actuate="onRequest"/> (source files available by cloning https://github.com/xspec/xspec.wiki.git in Git)</bibliomixed>
    </bibliography>
</article>