Skip to content

Refactor recwarn to use warnings.catch_warnings instead of custom code #2303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 15, 2017
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
42 changes: 9 additions & 33 deletions _pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
import sys
import warnings
import pytest
from collections import namedtuple


@pytest.yield_fixture
def recwarn(request):
def recwarn():
"""Return a WarningsRecorder instance that provides these methods:

* ``pop(category=None)``: return last warning matching the category.
Expand Down Expand Up @@ -115,19 +114,14 @@ def warns(expected_warning, *args, **kwargs):
return func(*args[1:], **kwargs)


RecordedWarning = namedtuple('RecordedWarning', (
'message', 'category', 'filename', 'lineno', 'file', 'line',
))


class WarningsRecorder(object):
class WarningsRecorder(warnings.catch_warnings):
"""A context manager to record raised warnings.
Copy link
Member

Choose a reason for hiding this comment

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

isnt that a breach of api? is it exposed in 3.0 ?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, that was added by you in features branch for 3.1, so it has never seen the light of day.


Adapted from `warnings.catch_warnings`.
"""

def __init__(self, module=None):
self._module = sys.modules['warnings'] if module is None else module
def __init__(self):
super(WarningsRecorder, self).__init__(record=True)
self._entered = False
self._list = []

Expand Down Expand Up @@ -164,38 +158,20 @@ def __enter__(self):
if self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning

def showwarning(message, category, filename, lineno,
file=None, line=None):
self._list.append(RecordedWarning(
message, category, filename, lineno, file, line))

# still perform old showwarning functionality
self._showwarning(
message, category, filename, lineno, file=file, line=line)

self._module.showwarning = showwarning

# allow the same warning to be raised more than once

self._module.simplefilter('always')
self._list = super(WarningsRecorder, self).__enter__()
warnings.simplefilter('always')
return self

def __exit__(self, *exc_info):
if not self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters
self._module.showwarning = self._showwarning
super(WarningsRecorder, self).__exit__(*exc_info)


class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None, module=None):
super(WarningsChecker, self).__init__(module=module)
def __init__(self, expected_warning=None):
super(WarningsChecker, self).__init__()

msg = ("exceptions must be old-style classes or "
"derived from Warning, not %s")
Expand Down
16 changes: 2 additions & 14 deletions testing/test_recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,19 @@
def test_recwarn_functional(testdir):
reprec = testdir.inline_runsource("""
import warnings
oldwarn = warnings.showwarning
def test_method(recwarn):
Copy link
Member Author

Choose a reason for hiding this comment

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

This is test and test_recording below were testing an implementation detail which is no longer true in 3.6, because catch_warnings has changed significantly since 3.5.

assert warnings.showwarning != oldwarn
warnings.warn("hello")
warn = recwarn.pop()
assert isinstance(warn.message, UserWarning)
def test_finalized():
assert warnings.showwarning == oldwarn
""")
res = reprec.countoutcomes()
assert tuple(res) == (2, 0, 0), res
assert tuple(res) == (1, 0, 0), res


class TestWarningsRecorderChecker(object):
def test_recording(self, recwarn):
showwarning = py.std.warnings.showwarning
def test_recording(self):
rec = WarningsRecorder()
with rec:
assert py.std.warnings.showwarning != showwarning
assert not rec.list
py.std.warnings.warn_explicit("hello", UserWarning, "xyz", 13)
assert len(rec.list) == 1
Expand All @@ -40,8 +34,6 @@ def test_recording(self, recwarn):
assert l is rec.list
pytest.raises(AssertionError, "rec.pop()")

assert showwarning == py.std.warnings.showwarning

def test_typechecking(self):
from _pytest.recwarn import WarningsChecker
with pytest.raises(TypeError):
Expand Down Expand Up @@ -217,17 +209,13 @@ def test_as_contextmanager(self):
excinfo.match(re.escape(message_template.format(warning_classes,
[each.message for each in warninfo])))


def test_record(self):
with pytest.warns(UserWarning) as record:
warnings.warn("user", UserWarning)

assert len(record) == 1
assert str(record[0].message) == "user"

print(repr(record[0]))
assert str(record[0].message) in repr(record[0])

def test_record_only(self):
with pytest.warns(None) as record:
warnings.warn("user", UserWarning)
Expand Down