-
Notifications
You must be signed in to change notification settings - Fork 261
Simplify interface for testing of warnings. #345
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,22 +7,23 @@ | |
# | ||
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## | ||
''' Utilities for testing ''' | ||
from __future__ import division, print_function | ||
|
||
import sys | ||
import warnings | ||
from os.path import dirname, abspath, join as pjoin | ||
|
||
import numpy as np | ||
from warnings import catch_warnings, simplefilter | ||
|
||
# set path to example data | ||
data_path = abspath(pjoin(dirname(__file__), '..', 'tests', 'data')) | ||
|
||
# Allow failed import of nose if not now running tests | ||
try: | ||
import nose.tools as nt | ||
except ImportError: | ||
pass | ||
else: | ||
from nose.tools import (assert_equal, assert_not_equal, | ||
assert_true, assert_false, assert_raises) | ||
except ImportError: | ||
pass | ||
|
||
# set path to example data | ||
data_path = abspath(pjoin(dirname(__file__), '..', 'tests', 'data')) | ||
|
||
|
||
def assert_dt_equal(a, b): | ||
|
@@ -56,35 +57,110 @@ def assert_allclose_safely(a, b, match_nans=True): | |
assert_true(np.allclose(a, b)) | ||
|
||
|
||
class suppress_warnings(catch_warnings): | ||
""" Version of ``catch_warnings`` class that suppresses warnings | ||
""" | ||
def __enter__(self): | ||
res = super(suppress_warnings, self).__enter__() | ||
simplefilter('ignore') | ||
return res | ||
def get_fresh_mod(mod_name=__name__): | ||
# Get this module, with warning registry empty | ||
my_mod = sys.modules[mod_name] | ||
try: | ||
my_mod.__warningregistry__.clear() | ||
except AttributeError: | ||
pass | ||
return my_mod | ||
|
||
|
||
class clear_and_catch_warnings(warnings.catch_warnings): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a copy of code from https://github.com/numpy/numpy/pull/5682/files . I did NOT copy over the tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: I just changed the default to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you mind copying over the tests as well? |
||
""" Context manager that resets warning registry for catching warnings | ||
|
||
Warnings can be slippery, because, whenever a warning is triggered, Python | ||
adds a ``__warningregistry__`` member to the *calling* module. This makes | ||
it impossible to retrigger the warning in this module, whatever you put in | ||
the warnings filters. This context manager accepts a sequence of `modules` | ||
as a keyword argument to its constructor and: | ||
|
||
* stores and removes any ``__warningregistry__`` entries in given `modules` | ||
on entry; | ||
* resets ``__warningregistry__`` to its previous state on exit. | ||
|
||
This makes it possible to trigger any warning afresh inside the context | ||
manager without disturbing the state of warnings outside. | ||
|
||
For compatibility with Python 3.0, please consider all arguments to be | ||
keyword-only. | ||
|
||
class catch_warn_reset(catch_warnings): | ||
""" Version of ``catch_warnings`` class that resets warning registry | ||
Parameters | ||
---------- | ||
record : bool, optional | ||
Specifies whether warnings should be captured by a custom | ||
implementation of ``warnings.showwarning()`` and be appended to a list | ||
returned by the context manager. Otherwise None is returned by the | ||
context manager. The objects appended to the list are arguments whose | ||
attributes mirror the arguments to ``showwarning()``. | ||
|
||
NOTE: nibabel difference from numpy: default is True | ||
|
||
modules : sequence, optional | ||
Sequence of modules for which to reset warnings registry on entry and | ||
restore on exit | ||
|
||
Examples | ||
-------- | ||
>>> import warnings | ||
>>> with clear_and_catch_warnings(modules=[np.core.fromnumeric]): | ||
... warnings.simplefilter('always') | ||
... # do something that raises a warning in np.core.fromnumeric | ||
""" | ||
def __init__(self, *args, **kwargs): | ||
self.modules = kwargs.pop('modules', []) | ||
class_modules = () | ||
|
||
def __init__(self, record=True, modules=()): | ||
self.modules = set(modules).union(self.class_modules) | ||
self._warnreg_copies = {} | ||
super(catch_warn_reset, self).__init__(*args, **kwargs) | ||
super(clear_and_catch_warnings, self).__init__(record=record) | ||
|
||
def __enter__(self): | ||
for mod in self.modules: | ||
if hasattr(mod, '__warningregistry__'): | ||
mod_reg = mod.__warningregistry__ | ||
self._warnreg_copies[mod] = mod_reg.copy() | ||
mod_reg.clear() | ||
return super(catch_warn_reset, self).__enter__() | ||
return super(clear_and_catch_warnings, self).__enter__() | ||
|
||
def __exit__(self, *exc_info): | ||
super(catch_warn_reset, self).__exit__(*exc_info) | ||
super(clear_and_catch_warnings, self).__exit__(*exc_info) | ||
for mod in self.modules: | ||
if hasattr(mod, '__warningregistry__'): | ||
mod.__warningregistry__.clear() | ||
if mod in self._warnreg_copies: | ||
mod.__warningregistry__.update(self._warnreg_copies[mod]) | ||
|
||
|
||
class error_warnings(clear_and_catch_warnings): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inherits from |
||
""" Context manager to check for warnings as errors. Usually used with | ||
``assert_raises`` in the with block | ||
|
||
Examples | ||
-------- | ||
>>> with error_warnings(): | ||
... try: | ||
... warnings.warn('Message', UserWarning) | ||
... except UserWarning: | ||
... print('I consider myself warned') | ||
I consider myself warned | ||
""" | ||
filter = 'error' | ||
|
||
def __enter__(self): | ||
mgr = super(error_warnings, self).__enter__() | ||
warnings.simplefilter(self.filter) | ||
return mgr | ||
|
||
|
||
class suppress_warnings(error_warnings): | ||
""" Version of ``catch_warnings`` class that suppresses warnings | ||
""" | ||
filter = 'ignore' | ||
|
||
|
||
class catch_warn_reset(clear_and_catch_warnings): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed docstring, and all functions take |
||
def __init__(self, *args, **kwargs): | ||
warnings.warn('catch_warn_reset is deprecated and will be removed in ' | ||
'nibabel v3.0; use nibabel.testing.clear_and_catch_warnings.', | ||
FutureWarning) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,24 +11,19 @@ | |
import numpy as np | ||
|
||
from ..externals.six import BytesIO | ||
|
||
from ..arraywriters import (SlopeInterArrayWriter, SlopeArrayWriter, | ||
WriterError, ScalingError, ArrayWriter, | ||
make_array_writer, get_slope_inter) | ||
|
||
from ..casting import int_abs, type_info, shared_range, on_powerpc | ||
|
||
from ..volumeutils import array_from_file, apply_read_scaling, _dt_min_max | ||
|
||
from numpy.testing import (assert_array_almost_equal, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. drive-by linting. I couldn't resist! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :) |
||
assert_array_equal) | ||
|
||
from nose.tools import (assert_true, assert_false, | ||
assert_equal, assert_not_equal, | ||
assert_raises) | ||
|
||
from ..testing import assert_allclose_safely, suppress_warnings | ||
from ..checkwarns import ErrorWarnings | ||
from ..testing import (assert_allclose_safely, suppress_warnings, | ||
error_warnings) | ||
|
||
|
||
FLOAT_TYPES = np.sctypes['float'] | ||
|
@@ -524,7 +519,7 @@ def test_nan2zero(): | |
data_back = round_trip(aw) | ||
assert_array_equal(np.isnan(data_back), [True, False]) | ||
# Deprecation warning for nan2zero as argument to `to_fileobj` | ||
with ErrorWarnings(): | ||
with error_warnings(): | ||
assert_raises(DeprecationWarning, | ||
aw.to_fileobj, BytesIO(), 'F', True) | ||
assert_raises(DeprecationWarning, | ||
|
@@ -545,7 +540,7 @@ def test_nan2zero(): | |
astype_res = np.array(np.nan).astype(np.int32) | ||
assert_array_equal(data_back, [astype_res, 99]) | ||
# Deprecation warning for nan2zero as argument to `to_fileobj` | ||
with ErrorWarnings(): | ||
with error_warnings(): | ||
assert_raises(DeprecationWarning, | ||
aw.to_fileobj, BytesIO(), 'F', False) | ||
assert_raises(DeprecationWarning, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,21 @@ | ||
""" Tests for warnings context managers | ||
""" | ||
|
||
from __future__ import division, print_function, absolute_import | ||
|
||
from warnings import warn, simplefilter, filters | ||
|
||
from ..checkwarns import ErrorWarnings, IgnoreWarnings | ||
|
||
from nose.tools import assert_true, assert_equal, assert_raises | ||
from ..testing import clear_and_catch_warnings, suppress_warnings | ||
|
||
|
||
def test_warn_error(): | ||
# Check warning error context manager | ||
n_warns = len(filters) | ||
with ErrorWarnings(): | ||
assert_raises(UserWarning, warn, 'A test') | ||
with ErrorWarnings() as w: # w not used for anything | ||
assert_raises(UserWarning, warn, 'A test') | ||
assert_equal(n_warns, len(filters)) | ||
# Check other errors are propagated | ||
def f(): | ||
with ErrorWarnings(): | ||
raise ValueError('An error') | ||
assert_raises(ValueError, f) | ||
def test_ignore_and_error_warnings(): | ||
with suppress_warnings(): | ||
from .. import checkwarns | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing what is happening here is that nose has previously imported There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By the way - I do see the error if I run the full test-suite locally, but not if I run the individual test. |
||
|
||
with clear_and_catch_warnings() as w: | ||
checkwarns.IgnoreWarnings() | ||
assert_equal(len(w), 1) | ||
assert_equal(w[0].category, FutureWarning) | ||
|
||
def test_warn_ignore(): | ||
# Check warning ignore context manager | ||
n_warns = len(filters) | ||
with IgnoreWarnings(): | ||
warn('Here is a warning, you will not see it') | ||
warn('Nor this one', DeprecationWarning) | ||
with IgnoreWarnings() as w: # w not used | ||
warn('Here is a warning, you will not see it') | ||
warn('Nor this one', DeprecationWarning) | ||
assert_equal(n_warns, len(filters)) | ||
# Check other errors are propagated | ||
def f(): | ||
with IgnoreWarnings(): | ||
raise ValueError('An error') | ||
assert_raises(ValueError, f) | ||
with clear_and_catch_warnings() as w: | ||
checkwarns.ErrorWarnings() | ||
assert_equal(len(w), 1) | ||
assert_equal(w[0].category, FutureWarning) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matthew-brett Any idea if
try...catch
is still necessary? I took the liberty to blow it away. I couldn't tell from the commits (5+ years ago) where this was being used. I didn't seent
being used anywhere in my cursory search, and tests pass...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is so that people can still import nibabel if they don't have nose installed.