Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@ venv
# PyCharm
.idea

# VS code
.vscode

pytest_doctestplus/version.py
pip-wheel-metadata/
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
0.10.0 (unreleased)
===================

- Added ``..doctest-remote-data::`` directive to control remote data
access for a chunk of code. [#137]

- Drop support for ``python`` 3.6. [#159]

- Fixed a bug where the command-line option ``--remote-data=any`` (associated
Expand All @@ -10,6 +13,7 @@
- Fix wrong behavior with ``IGNORE_WARNINGS`` and ``SHOW_WARNINGS`` that could
make a block to pass instead of being skipped. [#148]


0.9.0 (2021-01-14)
==================

Expand Down
12 changes: 11 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,17 @@ marked:

The ``+REMOTE_DATA`` directive indicates that the marked statement should only
be executed if the ``--remote-data`` option is given. By default, all
statements marked with ``--remote-data`` will be skipped.
statements marked with the remote data directive will be skipped.

Whole code example blocks can also be marked to control access to data from the internet
this way:

.. code-block:: python

.. doctest-remote-data::

>>> import requests
>>> r = requests.get('https://www.astropy.org')

.. _pytest-remotedata: https://github.com/astropy/pytest-remotedata
__ pytest-remotedata_
Expand Down
28 changes: 21 additions & 7 deletions pytest_doctestplus/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ def pytest_addoption(parser):

parser.addoption("--text-file-format", action="store",
help=(
"Text file format for narrative documentation. "
"Options accepted are 'txt', 'tex', and 'rst'. "
"This is no longer recommended, use --doctest-glob instead."
"Text file format for narrative documentation. "
"Options accepted are 'txt', 'tex', and 'rst'. "
"This is no longer recommended, use --doctest-glob instead."
))

# Defaults to `atol` parameter from `numpy.allclose`.
Expand Down Expand Up @@ -140,8 +140,8 @@ def pytest_addoption(parser):
default=[])

parser.addini("doctest_subpackage_requires",
"A list of paths to skip if requirements are not satisfied. Each item in the list "
"should have the syntax path=req1;req2",
"A list of paths to skip if requirements are not satisfied."
"Each item in the list should have the syntax path=req1;req2",
type='linelist',
default=[])

Expand All @@ -157,7 +157,8 @@ def get_optionflags(parent):
def pytest_configure(config):
doctest_plugin = config.pluginmanager.getplugin('doctest')
run_regular_doctest = config.option.doctestmodules and not config.option.doctest_plus
use_doctest_plus = config.getini('doctest_plus') or config.option.doctest_plus or config.option.doctest_only
use_doctest_plus = config.getini(
'doctest_plus') or config.option.doctest_plus or config.option.doctest_only
if doctest_plugin is None or run_regular_doctest or not use_doctest_plus:
return

Expand Down Expand Up @@ -324,6 +325,9 @@ class DocTestParserPlus(doctest.DocTestParser):
installed.

- ``.. doctest-skip-all``: Skip all subsequent doctests.

- ``.. doctest-remote-data::``: Skip the next doctest chunk if
--remote-data is not passed.
"""

def parse(self, s, name=None):
Expand Down Expand Up @@ -355,7 +359,8 @@ def parse(self, s, name=None):
required = []
skip_next = False
lines = entry.strip().splitlines()
if any([re.match('{} doctest-skip-all'.format(comment_char), x.strip()) for x in lines]):
if any([re.match(
'{} doctest-skip-all'.format(comment_char), x.strip()) for x in lines]):
skip_all = True
continue

Expand All @@ -382,6 +387,15 @@ def parse(self, s, name=None):
skip_next = True
continue

if config.getoption('remote_data', 'none') != 'any':
matches = [re.match(
r'{}\s+doctest-remote-data\s*::'.format(comment_char),
last_line) for last_line in last_lines]

if any(matches):
skip_next = True
continue

matches = [re.match(
r'{}\s+doctest-requires\s*::\s+(.*)'.format(comment_char),
last_line) for last_line in last_lines]
Expand Down
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ install_requires =
setuptools>=30.3.0
packaging>=17.0

[options.extras_require]
test =
pytest-remotedata>=0.3.2

[options.entry_points]
pytest11 =
pytest_doctestplus = pytest_doctestplus.plugin
Expand Down
74 changes: 74 additions & 0 deletions tests/docs/skip_some.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,77 @@ Code in doctest should run only if version condition is satisfied:
.. doctest-requires:: pytest>=1.0 pytest>=2.0

>>> import pytest


Remote data block code sandwiched in block codes
================================================

This code block should work just fine::

>>> 1 + 1
2

This should be skipped when remote data is not requested
otherwise the test should fail::

.. doctest-remote-data::

>>> 1 + 3
2

This code block should work just fine::

>>> 1 + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we know if this is actually silently skipped too, say, because it thinks this code is still somehow part of remote date block above? That is, how are we sure the remote data block really ended before this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a blank line within a doctest separates one chunk of code from another in the case of ..doctes-remote-data:: i.e the directive considers the chunk of code after a blank line as a separate chunk of code. That way we know that it does not control the chunk of code.

2


Remote data followed by plain block code
========================================

This one should be skipped when remote data is not requested
otherwise the test should fail::

.. doctest-remote-data::

>>> 1 + 3
2

This code block should work just fine::

>>> 1 + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we know if this is actually silently skipped too, say, because it thinks this code is still somehow part of remote date block above? That is, how are we sure the remote data block really ended before this line?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm not sure how we could do that, as currently all the narrative docs testings are done as one test, if it's stopped somewhere the rest of the file is not being picked up. But then we have a tracelog with a failure. Whenever it's skipped, we have no easy way to see that the rest of the file was run as expected. Maybe add a failing one to the very bottom of the file? But then it's a failing test, and here we cannot really have it as an xfail, afaik.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, perhaps that test case is too complicated to be tested this way. If this is tested in the other unit tests, then we don't have to do it here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, good point, there is indeed a better handle of these in the unittest content.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pllim - So if we run this file with --remote-data, then it of course stops with a fail on L98. As expected. So I'm not sure what to do with the file itself, maybe only include it in when --remote-data is not used, and test the working of the remote-data stuff separately, only in unit tests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe put those tests in a dedicated file that can be run with and without --remote-data ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what was the goal of having all those blocks that should fail ? Since the first failing test will fail the file anyway, we cannot test that the other blocks that should fail actually fail. So I guess we could test the expected failures in the unit tests, and in this file test blocks that should either pass or be skipped ?

2


Several blocks of Remote data
=============================

The three block codes should be skipped when remote data
is not requested otherwise the tests should fail:

.. doctest-remote-data::

>>> 1 + 3
2

.. doctest-remote-data::

>>> 1 + 4
2

.. doctest-remote-data::

>>> 1 + 5
2

composite directive with remote data
====================================

This should be skipped otherwise the test should fail::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This should be skipped otherwise the test should fail::
This should be skipped otherwise the test should fail:


.. doctest-remote-data::

>>> 1 + 1
3
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +IGNORE_WARNINGS

149 changes: 144 additions & 5 deletions tests/test_doctestplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def test_ignore_warnings_rst(testdir):
# First check that we get a warning if we don't add the IGNORE_WARNINGS
# directive
p = testdir.makefile(".rst",
"""
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning)
Expand All @@ -444,7 +444,7 @@ def test_ignore_warnings_rst(testdir):

# Now try with the IGNORE_WARNINGS directive
p = testdir.makefile(".rst",
"""
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +IGNORE_WARNINGS
Expand Down Expand Up @@ -486,7 +486,7 @@ def myfunc():
def test_show_warnings_rst(testdir):

p = testdir.makefile(".rst",
"""
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
Expand All @@ -498,7 +498,7 @@ def test_show_warnings_rst(testdir):

# Make sure it fails if warning message is missing
p = testdir.makefile(".rst",
"""
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
Expand All @@ -509,7 +509,7 @@ def test_show_warnings_rst(testdir):

# Make sure it fails if warning message is missing
p = testdir.makefile(".rst",
"""
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
Expand Down Expand Up @@ -775,3 +775,142 @@ def test_doctest_skip(testdir):
"""
)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1)


# We repeat all testst including remote data with and without it opted in
def test_remote_data_url(testdir):
testdir.makeini(
"""
[pytest]
doctestplus = enabled
""")

p = testdir.makefile(
'.rst',
"""
# This test should be skipped when remote data is not requested.
.. doctest-remote-data::

>>> from contextlib import closing
>>> from urllib.request import urlopen
>>> with closing(urlopen('https://www.astropy.org')) as remote:
... remote.read() # doctest: +IGNORE_OUTPUT
"""
)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst', '--remote-data').assertoutcome(passed=1)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1)


def test_remote_data_float_cmp(testdir):
testdir.makeini(
"""
[pytest]
doctestplus = enabled
""")

p = testdir.makefile(
'.rst',
"""
#This test is skipped when remote data is not requested
.. doctest-remote-data::

>>> x = 1/3.
>>> x # doctest: +FLOAT_CMP
0.333333
"""
)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst', '--remote-data').assertoutcome(passed=1)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1)


def test_remote_data_ignore_whitespace(testdir):
testdir.makeini(
"""
[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE
doctestplus = enabled
""")

p = testdir.makefile(
'.rst',
"""
#This test should be skipped when remote data is not requested, and should
#pass when remote data is requested
.. doctest-remote-data::

>>> a = "foo "
>>> print(a)
foo
"""
)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst', '--remote-data').assertoutcome(passed=1)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1)


def test_remote_data_ellipsis(testdir):
testdir.makeini(
"""
[pytest]
doctest_optionflags = ELLIPSIS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of the extra doctest_optionflags = ELLIPSIS in this test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to test if the ELLIPSIS option works well with the .. doctest-remote-data:: directive, but I think I misunderstood it's function. The correct option here should be NORMALIZE_WHITESPACE

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pllim - we run into some cases where it seemed some of the default flags don't work nicely together, so as mentioned above the purpose was to test some of them.
Maybe the test is superfluous, then we can remove it later.

doctestplus = enabled
""")

p = testdir.makefile(
'.rst',
"""
# This test should be skipped when remote data is not requested, and should
# pass when remote data is requested
.. doctest-remote-data::

>>> a = "freedom at last"
>>> print(a)
freedom ...
"""
)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst', '--remote-data').assertoutcome(passed=1)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1)


def test_remote_data_requires(testdir):
testdir.makeini(
"""
[pytest]
doctestplus = enabled
""")

p = testdir.makefile(
'.rst',
"""
# This test should be skipped when remote data is not requested.
# It should also be skipped instead of failing when remote data is requested because
# the module required does not exist
.. doctest-remote-data::
.. doctest-requires:: does-not-exist

>>> 1 + 1
3
"""
)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst', '--remote-data').assertoutcome(skipped=1)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1)


def test_remote_data_ignore_warnings(testdir):
testdir.makeini(
"""
[pytest]
doctestplus = enabled
""")

p = testdir.makefile(
'.rst',
"""
# This test should be skipped if remote data is not requested.
.. doctest-remote-data::

>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +IGNORE_WARNINGS
"""
)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst', '--remote-data').assertoutcome(passed=1)
testdir.inline_run(p, '--doctest-plus', '--doctest-rst').assertoutcome(skipped=1)
Loading