Skip to content

Commit

Permalink
move doctest marker to run-tests.py, add build-docs.py -b doctest sup…
Browse files Browse the repository at this point in the history
…port
  • Loading branch information
xflr6 committed Nov 20, 2021
1 parent b219c13 commit f317b7e
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 58 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __pycache__/
/build/
/dist/
/docs/_build/
/docs/_doctest/
/*.egg-info/
/*.egg
/.eggs/
Expand Down
41 changes: 15 additions & 26 deletions build-docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

"""Build the docs with https://www.sphinx-doc.org."""

import contextlib
import functools
import os
import pathlib
Expand All @@ -14,15 +13,15 @@

SOURCE = pathlib.Path('docs')

TARGET = pathlib.Path('_build')
TARGET = SOURCE / '_build'

RESULT = SOURCE / TARGET / 'index.html'
RESULT = TARGET / 'index.html'

BROWSER_OPEN = '--open'

SKIP_OPEN_RESULT = '--no-open'

DEFAULT_ARGS = [BROWSER_OPEN, '-n', '-v', '.', str(TARGET)]
DEFAULT_ARGS = [BROWSER_OPEN, '-n', '-v', str(SOURCE), str(TARGET)]

OPEN_RESULT = BROWSER_OPEN in DEFAULT_ARGS

Expand All @@ -32,20 +31,8 @@
print = functools.partial(print, sep='\n')


@contextlib.contextmanager
def chdir(path):
cwd_before = os.getcwd()
print(f'os.chdir({path!r})')
os.chdir(path)
try:
yield path
finally:
print('', f'os.chdir({cwd_before!r})')
os.chdir(cwd_before)


args = sys.argv[1:]
print(f'run {SELF.name} {args}', '')
print(f'run {SELF.name} {args}')
if not args:
args = DEFAULT_ARGS

Expand All @@ -72,17 +59,19 @@ def chdir(path):
args = [a for a in DEFAULT_ARGS
if a != SKIP_OPEN_RESULT and a.partition('=')[0] != BROWSER_OPEN]

with chdir(SOURCE):
print('', f'sphinx.cmd.build.main({args})', '')
returncode = build.main(args)
if args == ['-b', 'doctest']:
args += [str(SOURCE), str(SOURCE / '_doctest')]

print('', f'returncode: {returncode!r}')
print('', f'sphinx.cmd.build.main({args})',)
returncode = build.main(args)
print('', f'returncode: {returncode!r}', end='')

try:
print('', f'index: {RESULT}', f'assert {RESULT!r}.stat().st_size', end='')
assert open_result.stat().st_size, f'non-empty {open_result}'
if open_result:
print('', f'webbrowser.open({open_result!r})', end='')
webbrowser.open(open_result)
if 'doctest' not in args:
print('', f'index: {RESULT}', f'assert {RESULT!r}.stat().st_size', end='')
assert open_result.stat().st_size, f'non-empty {open_result}'
if open_result:
print('', f'webbrowser.open({open_result!r})', end='')
webbrowser.open(open_result)
finally:
sys.exit(returncode)
17 changes: 1 addition & 16 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
"""pytest command line options and doctest namespace."""

import doctest as _doctest
from unittest import mock as _mock

_NO_EXE = _doctest.register_optionflag('NO_EXE')
import pytest

SKIP_EXE = '--skip-exe'

ONLY_EXE = '--only-exe'


class _NoExeChecker(_doctest.OutputChecker):

def check_output(self, want, got, optionflags, *args, **kwargs) -> bool:
if optionflags & _NO_EXE:
return True
return super().check_output(want, got, optionflags, *args, **kwargs)


_mock.patch.object(_doctest, 'OutputChecker', new=_NoExeChecker).start()
import pytest # noqa: E402


def pytest_addoption(parser):
parser.addoption(SKIP_EXE, action='store_true',
help='Skip tests with pytest.mark.exe.'
Expand Down
9 changes: 9 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@
'sphinx.ext.viewcode',
]

doctest_global_setup = '''\
import doctest as _doctest
_doctest.register_optionflag('NO_EXE')
def doctest_mark_exe(**kwargs):
pass
'''

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

Expand Down
53 changes: 38 additions & 15 deletions docs/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,9 @@ undirected and directed graphs respectively. They have the same
Create a graph by instantiating a new :class:`.Graph` or
:class:`.Digraph` object:

.. testsetup::
import graphviz

.. doctest::

>>> import graphviz
>>> dot = graphviz.Digraph('round-table', comment='The Round Table', directory='doctest-output') # doctest: +NO_EXE

>>> dot # doctest: +ELLIPSIS
Expand Down Expand Up @@ -98,7 +96,7 @@ Check the generated source code:

>>> print(dot.source) # doctest: +NORMALIZE_WHITESPACE +NO_EXE
// The Round Table
digraph {
digraph "round-table" {
A [label="King Arthur"]
B [label="Sir Bedevere the Wise"]
L [label="Sir Lancelot the Brave"]
Expand All @@ -111,18 +109,20 @@ Use the :meth:`~.Graph.render`-method to save the source code and render it with
default layout program (``dot``, see below for using `other layout commands
<Engines_>`_).

.. testsetup:: render
doctest_mark_exe()

.. doctest:: render
.. doctest::

>>> doctest_mark_exe()

>>> dot.render().replace('\\', '/')
'doctest-output/round-table.gv.pdf'

Passing ``view=True`` will automatically open the resulting (PDF, PNG, SVG,
etc.) file with your system's default viewer application for the file type.

.. doctest:: render
.. doctest::

>>> doctest_mark_exe()

>>> dot.render(view=True) # doctest: +SKIP
'doctest-output/round-table.gv.pdf'
Expand All @@ -146,11 +146,11 @@ To use a different `output file format`_ than the default PDF, use the
You can also change the :attr:`~.Graph.format` attribute on an existing graph
object:

..testsetup:: render
doctest_mark_exe()

.. doctest::

>>> doctest_mark_exe()

>>> dot.format = 'svg'

>>> dot.render().replace('\\', '/')
Expand All @@ -171,7 +171,7 @@ use the :meth:`~.Graph.pipe`-method of your :class:`.Graph` or

>>> h.edge('Hello', 'World')

.. doctest:: render
.. doctest::

>>> print(h.pipe().decode('utf-8')) # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
Expand Down Expand Up @@ -514,7 +514,7 @@ instance is created with ``strict=None`` and the parent graph's values for
Note that these attributes are only relevant when rendering the subgraph independently
(i.e. as a stand-alone graph) from within the ``with``-block:

.. doctest:: render
.. doctest::

>>> import pytest
>>> pytest.xfail(reason='FIXME')
Expand Down Expand Up @@ -558,6 +558,10 @@ the unflatten_ preprocessor (`PDF <unflatten_pdf_>`_), use the

>>> w.edges(('0', str(i)) for i in range(1, 10))

.. doctest::

>>> doctest_mark_exe()

>>> w.view() # doctest: +SKIP

.. image:: _static/wide.svg
Expand All @@ -570,6 +574,10 @@ disconnected nodes.

>>> u = w.unflatten(stagger=3) # doctest: +NO_EXE

.. doctest::

>>> doctest_mark_exe()

>>> u.view() # doctest: +SKIP

.. image:: _static/wide-unflatten-stagger-3.svg
Expand All @@ -581,8 +589,13 @@ The method returns a :class:`.Source` object that you can

.. doctest::

>>> u = w.unflatten(stagger=2) # doctest: +NO_EXE
>>> u = w.unflatten(stagger=2) # doctest: +ELLIPSIS +NO_EXE
>>> u
<graphviz.sources.Source object at 0x...>

.. doctest::

>>> doctest_mark_exe()

>>> u.view() # doctest: +SKIP

Expand Down Expand Up @@ -634,11 +647,17 @@ the higher-level interface of :class:`.Graph` or :class:`.Digraph`), create a

Use the :meth:`~.Source.render`-method to save and render it:

.. doctest:: render
.. doctest::

>>> doctest_mark_exe()

>>> src.render('doctest-output/holy-grenade.gv').replace('\\', '/')
'doctest-output/holy-grenade.gv.pdf'

.. doctest::

>>> doctest_mark_exe()

>>> src.render('doctest-output/holy-grenade.gv', view=True).replace('\\', '/') # doctest: +SKIP
'doctest-output/holy-grenade.gv.pdf'

Expand All @@ -657,7 +676,7 @@ Existing files
To directly render an existing DOT source file (e.g. created with other tools),
you can use the :func:`graphviz.render` function.

.. doctest:: render
.. doctest::

>>> graphviz.render('dot', 'png', 'doctest-output/holy-grenade.gv').replace('\\', '/')
'doctest-output/holy-grenade.gv.png'
Expand Down Expand Up @@ -703,6 +722,10 @@ incremental workflow (and also preserve its intermediate steps):

>>> g.node('spam')

.. doctest::

>>> doctest_mark_exe()

>>> g.view(tempfile.mktemp('.gv')) # doctest: +SKIP
'C:\\Users\\User\\AppData\\Local\\Temp\\tmp3aoie8d0.gv.pdf'

Expand Down
1 change: 1 addition & 0 deletions docs/release_process.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Run the tests, run the linter, and build the docs:
$ python -m tox -r -- -W error # --recreate, raise error on warning
$ python -m flake8 --disable-noqa
$ ./build-docs.py -b doctest
$ ./build-docs.py
$ git clean -f -d -x
Expand Down
18 changes: 17 additions & 1 deletion run-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

"""Run the tests with https://pytest.org."""

import doctest as doctest
from unittest import mock

import platform
import sys

import pytest
NO_EXE = doctest.register_optionflag('NO_EXE')

ARGS = [#'--skip-exe',
#'--only-exe',
Expand All @@ -19,6 +22,19 @@
#'--cov-append',
]


class NoExeChecker(doctest.OutputChecker):

def check_output(self, want, got, optionflags, *args, **kwargs) -> bool:
if optionflags & NO_EXE:
return True
return super().check_output(want, got, optionflags, *args, **kwargs)


mock.patch.object(doctest, 'OutputChecker', new=NoExeChecker).start()
import pytest # noqa: E402


if platform.system() == 'Windows' and 'idlelib' in sys.modules:
ARGS += ['--capture=sys', '--color=no']

Expand Down

0 comments on commit f317b7e

Please sign in to comment.