Skip to content

Commit

Permalink
ENH,TST: Add new warning suppression/filtering context
Browse files Browse the repository at this point in the history
This context has a couple of advantages over the typical one, and can
ensure that warnings are cleanly filtered without side effect for
later tests.
  • Loading branch information
seberg authored and charris committed Aug 28, 2016
1 parent 90668d0 commit 3c34457
Show file tree
Hide file tree
Showing 4 changed files with 504 additions and 39 deletions.
17 changes: 17 additions & 0 deletions doc/release/1.12.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,23 @@ to ``logspace``, but with start and stop specified directly:
``geomspace(start, stop)`` behaves the same as
``logspace(log10(start), log10(stop))``.

New context manager for testing warnings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A new context manager ``suppress_warnings`` has been added to the testing
utils. This context manager is designed to help reliably test warnings.
Specifically to reliably filter/ignore warnings. Ignoring warnings
by using an "ignore" filter in Python versions before 3.4.x can quickly
result in these (or similar) warnings not being tested reliably.

The context manager allows to filter (as well as record) warnings similar
to the ``catch_warnings`` context, but allows for easier specificity.
Also printing warnings that have not been filtered or nesting the
context manager will work as expected. Additionally, it is possible
to use the context manager as a decorator which can be useful when
multiple tests give need to hide the same warning.


Improvements
============

Expand Down
23 changes: 2 additions & 21 deletions numpy/testing/nosetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from numpy.compat import basestring
import numpy as np

from .utils import import_nose, suppress_warnings


def get_package_name(filepath):
"""
Expand Down Expand Up @@ -53,27 +55,6 @@ def get_package_name(filepath):

return '.'.join(pkg_name)

def import_nose():
""" Import nose only when needed.
"""
fine_nose = True
minimum_nose_version = (1, 0, 0)
try:
import nose
except ImportError:
fine_nose = False
else:
if nose.__versioninfo__ < minimum_nose_version:
fine_nose = False

if not fine_nose:
msg = ('Need nose >= %d.%d.%d for tests - see '
'http://somethingaboutorange.com/mrl/projects/nose' %
minimum_nose_version)
raise ImportError(msg)

return nose

def run_module_suite(file_to_run=None, argv=None):
"""
Run a test module.
Expand Down
186 changes: 182 additions & 4 deletions numpy/testing/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
assert_array_almost_equal, build_err_msg, raises, assert_raises,
assert_warns, assert_no_warnings, assert_allclose, assert_approx_equal,
assert_array_almost_equal_nulp, assert_array_max_ulp,
clear_and_catch_warnings, run_module_suite,
clear_and_catch_warnings, suppress_warnings, run_module_suite,
assert_string_equal, assert_, tempdir, temppath,
)
import unittest
Expand Down Expand Up @@ -795,14 +795,16 @@ def test_simple(self):
lambda: assert_string_equal("foo", "hello"))


def assert_warn_len_equal(mod, n_in_context):
def assert_warn_len_equal(mod, n_in_context, py3_n_in_context=None):
mod_warns = mod.__warningregistry__
# Python 3.4 appears to clear any pre-existing warnings of the same type,
# when raising warnings inside a catch_warnings block. So, there is a
# warning generated by the tests within the context manager, but no
# previous warnings.
if 'version' in mod_warns:
assert_equal(len(mod_warns), 2) # including 'version'
if py3_n_in_context is None:
py3_n_in_context = n_in_context
assert_equal(len(mod_warns)-1, py3_n_in_context)
else:
assert_equal(len(mod_warns), n_in_context)

Expand Down Expand Up @@ -840,7 +842,183 @@ def test_clear_and_catch_warnings():
with clear_and_catch_warnings():
warnings.simplefilter('ignore')
warnings.warn('Another warning')
assert_warn_len_equal(my_mod, 2)
assert_warn_len_equal(my_mod, 2, 1)


def test_suppress_warnings_module():
# Initial state of module, no warnings
my_mod = _get_fresh_mod()
assert_equal(getattr(my_mod, '__warningregistry__', {}), {})

def warn_other_module():
# Apply along axis is implemented in python; stacklevel=2 means
# we end up inside its module, not ours.
def warn(arr):
warnings.warn("Some warning 2", stacklevel=2)
return arr
np.apply_along_axis(warn, 0, [0])

# Test module based warning suppression:
with suppress_warnings() as sup:
sup.record(UserWarning)
# suppress warning from other module (may have .pyc ending),
# if apply_along_axis is moved, had to be changed.
sup.filter(module=np.lib.shape_base)
warnings.warn("Some warning")
warn_other_module()
# Check that the suppression did test the file correctly (this module
# got filtered)
assert_(len(sup.log) == 1)
assert_(sup.log[0].message.args[0] == "Some warning")

assert_warn_len_equal(my_mod, 0)
sup = suppress_warnings()
# Will have to be changed if apply_along_axis is moved:
sup.filter(module=my_mod)
with sup:
warnings.warn('Some warning')
assert_warn_len_equal(my_mod, 0)
# And test repeat works:
sup.filter(module=my_mod)
with sup:
warnings.warn('Some warning')
assert_warn_len_equal(my_mod, 0)

# Without specified modules, don't clear warnings during context
with suppress_warnings():
warnings.simplefilter('ignore')
warnings.warn('Some warning')
assert_warn_len_equal(my_mod, 1)


def test_suppress_warnings_type():
# Initial state of module, no warnings
my_mod = _get_fresh_mod()
assert_equal(getattr(my_mod, '__warningregistry__', {}), {})

# Test module based warning suppression:
with suppress_warnings() as sup:
sup.filter(UserWarning)
warnings.warn('Some warning')
assert_warn_len_equal(my_mod, 0)
sup = suppress_warnings()
sup.filter(UserWarning)
with sup:
warnings.warn('Some warning')
assert_warn_len_equal(my_mod, 0)
# And test repeat works:
sup.filter(module=my_mod)
with sup:
warnings.warn('Some warning')
assert_warn_len_equal(my_mod, 0)

# Without specified modules, don't clear warnings during context
with suppress_warnings():
warnings.simplefilter('ignore')
warnings.warn('Some warning')
assert_warn_len_equal(my_mod, 1)


def test_suppress_warnings_decorate_no_record():
sup = suppress_warnings()
sup.filter(UserWarning)

@sup
def warn(category):
warnings.warn('Some warning', category)

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
warn(UserWarning) # should be supppressed
warn(RuntimeWarning)
assert_(len(w) == 1)


def test_suppress_warnings_record():
sup = suppress_warnings()
log1 = sup.record()

with sup:
log2 = sup.record(message='Some other warning 2')
sup.filter(message='Some warning')
warnings.warn('Some warning')
warnings.warn('Some other warning')
warnings.warn('Some other warning 2')

assert_(len(sup.log) == 2)
assert_(len(log1) == 1)
assert_(len(log2) == 1)
assert_(log2[0].message.args[0] == 'Some other warning 2')

# Do it again, with the same context to see if some warnings survived:
with sup:
log2 = sup.record(message='Some other warning 2')
sup.filter(message='Some warning')
warnings.warn('Some warning')
warnings.warn('Some other warning')
warnings.warn('Some other warning 2')

assert_(len(sup.log) == 2)
assert_(len(log1) == 1)
assert_(len(log2) == 1)
assert_(log2[0].message.args[0] == 'Some other warning 2')

# Test nested:
with suppress_warnings() as sup:
sup.record()
with suppress_warnings() as sup2:
sup2.record(message='Some warning')
warnings.warn('Some warning')
warnings.warn('Some other warning')
assert_(len(sup2.log) == 1)
assert_(len(sup.log) == 1)


def test_suppress_warnings_forwarding():
def warn_other_module():
# Apply along axis is implemented in python; stacklevel=2 means
# we end up inside its module, not ours.
def warn(arr):
warnings.warn("Some warning", stacklevel=2)
return arr
np.apply_along_axis(warn, 0, [0])

with suppress_warnings() as sup:
sup.record()
with suppress_warnings("always"):
for i in range(2):
warnings.warn("Some warning")

assert_(len(sup.log) == 2)

with suppress_warnings() as sup:
sup.record()
with suppress_warnings("location"):
for i in range(2):
warnings.warn("Some warning")
warnings.warn("Some warning")

assert_(len(sup.log) == 2)

with suppress_warnings() as sup:
sup.record()
with suppress_warnings("module"):
for i in range(2):
warnings.warn("Some warning")
warnings.warn("Some warning")
warn_other_module()

assert_(len(sup.log) == 2)

with suppress_warnings() as sup:
sup.record()
with suppress_warnings("once"):
for i in range(2):
warnings.warn("Some warning")
warnings.warn("Some other warning")
warn_other_module()

assert_(len(sup.log) == 2)


def test_tempdir():
Expand Down
Loading

0 comments on commit 3c34457

Please sign in to comment.