Skip to content

Doctest directive options are parsed and then discarded #84

Description

@tony

Summary

TestDirective.run() reads three pieces of metadata off a .. doctest:: / ```{doctest} block and stores them on the docutils node. None of the three is ever read again, so all three are silently inert: an inline # doctest: flag inside a directive never applies, :options: never applies, and :skipif: never skips. Anyone using the directive form instead of a plain fence gets an unexplained failure.

Reproduction

opts.rst:

Title
=====

.. doctest::

   >>> print("a  b")  # doctest: +NORMALIZE_WHITESPACE
   a b

.. doctest::
   :options: +SKIP

   >>> 1 + 1
   99999

.. doctest::
   :skipif: True

   >>> 1 + 1
   88888
$ pytest opts.rst -q --no-header

Expected

The first block passes, because NORMALIZE_WHITESPACE collapses the run of spaces. The second and third are skipped. Written as a plain fence outside a directive, the same inline flag already works — nothing trims it there.

Actual

All three fail. The first reports Expected: a b against Got: a b: the flag was removed from the source before the parser ever saw it. The second and third run their bodies and report the mismatch instead of skipping.

Versions

gp-libs v0.0.19, CPython 3.14, pytest 9.1.1, docutils 0.21.

Root cause

_get_test() receives only the node's rendered text and its line number, so everything else the directive parsed stays on the node and dies there:

  • node["test"] holds the untrimmed source. With trim-doctest-flags on by default, run() strips # doctest: ... out of the rendered code and keeps the original here — but the finder parses node.astext(), the trimmed copy.
  • node["options"] holds the flag map built from :options:, including a reporter warning for an unknown flag name.
  • node["skipif"] holds the condition expression from :skipif:.

sphinx.ext.doctest reads the first one back:

source = node['test'] if 'test' in node else node.astext()

Why it matters

trim-doctest-flags exists so a rendered page does not show test noise to a reader. Today it trims the flag out of the test as well, so the documented workaround — :no-trim-doctest-flags: — fixes the test by putting the noise back on the page, which is the opposite of what the option is for.

:options: and :skipif: are documented options of a directive this project ships. Accepting them, validating them, warning on a bad flag name, and then discarding the result is worse than not accepting them at all.

Proposal

Pass the node's metadata down to _get_test():

  • use node["test"] when present and node.astext() otherwise
  • merge node["options"] into each parsed example's options, letting an example's own inline flag win, as Sphinx does
  • evaluate node["skipif"] and drop the block when it is true

The rendered output should stay trimmed; that part already works.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions