Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

- Declare ``setuptools`` runtime dependency [#93]

- Add ``SHOW_WARNINGS`` flag to show warnings. [#136]

0.8.0 (2020-07-31)
==================

Expand Down
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@ you can make use of the ``IGNORE_WARNINGS`` flag. For example:
>>> np.mean([]) # doctest: +IGNORE_WARNINGS
np.nan

Showing warnings
~~~~~~~~~~~~~~~~

If code in a doctest emits a warning and you want to make sure that warning is
shown, you can make use of the ``SHOW_WARNINGS`` flag. This is useful when
warnings are turned into errors by pytest, and also because by default warnings
are printed to stderr. This is the opposite from ``IGNORE_WARNINGS`` so
obviously the two flags should not be used together. For example:

.. code-block:: python

>>> import numpy as np
>>> np.mean([]) # doctest: +SHOW_WARNINGS
Copy link
Contributor

Choose a reason for hiding this comment

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

One more question: If the expected warning does not show, will doctest fail or ignore?

Is this the doctest equivalent of pytest.warns?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One more question: If the expected warning does not show, will doctest fail or ignore?

Yep. I added more tests.

Is this the doctest equivalent of pytest.warns?

Yes, it can be seen as such.

RuntimeWarning: Mean of empty slice.
RuntimeWarning: invalid value encountered in double_scalars
np.nan

Skipping Tests
~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions pytest_doctestplus/output_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
IGNORE_OUTPUT = doctest.register_optionflag('IGNORE_OUTPUT')
IGNORE_OUTPUT_3 = doctest.register_optionflag('IGNORE_OUTPUT_3')
IGNORE_WARNINGS = doctest.register_optionflag('IGNORE_WARNINGS')
SHOW_WARNINGS = doctest.register_optionflag('SHOW_WARNINGS')

# These might appear in some doctests and are used in the default pytest
# doctest plugin. This plugin doesn't actually implement these flags but this
Expand Down
59 changes: 51 additions & 8 deletions pytest_doctestplus/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
import sys
import warnings

from packaging.version import Version

import pytest
from packaging.version import Version

from pytest_doctestplus.utils import ModuleChecker
from .output_checker import FIX, IGNORE_WARNINGS, OutputChecker, REMOTE_DATA

from .output_checker import (FIX, IGNORE_WARNINGS, REMOTE_DATA, SHOW_WARNINGS,
OutputChecker)

try:
from textwrap import indent
Expand All @@ -32,12 +33,12 @@ def indent(text, prefix):
}


# For the IGNORE_WARNINGS option, we create a context manager that doesn't
# require us to add any imports to the example list and contains everything
# that is needed to silence warnings.
# For the IGNORE_WARNINGS and SHOW_WARNINGS option, we create a context manager
# that doesn't require us to add any imports to the example list and contains
# everything that is needed to silence or print warnings.

IGNORE_WARNINGS_CONTEXT = """
class _doctestplus_ignore_all_warnings(object):
class _doctestplus_ignore_all_warnings:

def __init__(self):
import warnings
Expand All @@ -54,6 +55,26 @@ def __exit__(self, *args, **kwargs):
""".lstrip()


SHOW_WARNINGS_CONTEXT = """
class _doctestplus_show_all_warnings:

def __init__(self):
import warnings
self._cw = warnings.catch_warnings(record=True)

def __enter__(self, *args, **kwargs):
self.result = self._cw.__enter__(*args, **kwargs)
import warnings
warnings.simplefilter('always')
return self.result

def __exit__(self, *args, **kwargs):
self._cw.__exit__(*args, **kwargs)
for warn in self.result:
print(f'{warn._category_name}: {warn.message}')
""".lstrip()


# these pytest hooks allow us to mark tests and run the marked tests with
# specific command line options.
def pytest_addoption(parser):
Expand Down Expand Up @@ -200,6 +221,7 @@ def collect(self):
if config.getoption('remote_data', 'none') != 'any':

ignore_warnings_context_needed = False
show_warnings_context_needed = False

for example in test.examples:

Expand All @@ -210,13 +232,24 @@ def collect(self):
+ indent(example.source, ' '))
ignore_warnings_context_needed = True

# Same for SHOW_WARNINGS
if example.options.get(SHOW_WARNINGS, False):
example.source = ("with _doctestplus_show_all_warnings():\n"
+ indent(example.source, ' '))
show_warnings_context_needed = True

if example.options.get(REMOTE_DATA):
example.options[doctest.SKIP] = True

# We insert the definition of the context manager to ignore
# warnings at the start of the file if needed.
if ignore_warnings_context_needed:
test.examples.insert(0, doctest.Example(source=IGNORE_WARNINGS_CONTEXT, want=''))
test.examples.insert(0, doctest.Example(
source=IGNORE_WARNINGS_CONTEXT, want=''))

if show_warnings_context_needed:
test.examples.insert(0, doctest.Example(
source=SHOW_WARNINGS_CONTEXT, want=''))

try:
yield doctest_plugin.DoctestItem.from_parent(
Expand Down Expand Up @@ -289,6 +322,7 @@ def parse(self, s, name=None):
comment_char = comment_characters[ext]

ignore_warnings_context_needed = False
show_warnings_context_needed = False

for entry in result:

Expand Down Expand Up @@ -344,6 +378,12 @@ def parse(self, s, name=None):
+ indent(entry.source, ' '))
ignore_warnings_context_needed = True

# Same to show warnings
if entry.options.get(SHOW_WARNINGS, False):
entry.source = ("with _doctestplus_show_all_warnings():\n"
+ indent(entry.source, ' '))
show_warnings_context_needed = True

has_required_modules = DocTestFinderPlus.check_required_modules(required)
if skip_all or skip_next or not has_required_modules:
entry.options[doctest.SKIP] = True
Expand All @@ -356,6 +396,9 @@ def parse(self, s, name=None):
if ignore_warnings_context_needed:
result.insert(0, doctest.Example(source=IGNORE_WARNINGS_CONTEXT, want=''))

if show_warnings_context_needed:
result.insert(0, doctest.Example(source=SHOW_WARNINGS_CONTEXT, want=''))

return result

config.pluginmanager.register(
Expand Down
66 changes: 66 additions & 0 deletions tests/test_doctestplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,72 @@ def test_ignore_warnings_rst(testdir):
reprec.assertoutcome(failed=0, passed=1)


def test_show_warnings_module(testdir):

p = testdir.makepyfile(
"""
def myfunc():
'''
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
UserWarning: A warning occurred
'''
pass
""")
reprec = testdir.inline_run(p, "--doctest-plus", "-W error")
reprec.assertoutcome(failed=0, passed=1)

# Make sure it fails if warning message is missing
p = testdir.makepyfile(
"""
def myfunc():
'''
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
'''
pass
""")
reprec = testdir.inline_run(p, "--doctest-plus", "-W error")
reprec.assertoutcome(failed=1, passed=0)


def test_show_warnings_rst(testdir):

p = testdir.makefile(".rst",
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
UserWarning: A warning occurred
""")
reprec = testdir.inline_run(p, "--doctest-plus", "--doctest-rst",
"--text-file-format=rst", "-W error")
reprec.assertoutcome(failed=0, passed=1)

# Make sure it fails if warning message is missing
p = testdir.makefile(".rst",
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
""")
reprec = testdir.inline_run(p, "--doctest-plus", "--doctest-rst",
"--text-file-format=rst", "-W error")
reprec.assertoutcome(failed=1, passed=0)

# Make sure it fails if warning message is missing
p = testdir.makefile(".rst",
"""
::
>>> import warnings
>>> warnings.warn('A warning occurred', UserWarning) # doctest: +SHOW_WARNINGS
Warning: Another warning occurred
""")
reprec = testdir.inline_run(p, "--doctest-plus", "--doctest-rst",
"--text-file-format=rst", "-W error")
reprec.assertoutcome(failed=1, passed=0)


def test_doctest_glob(testdir):
testdir.makefile(
'.rst',
Expand Down