Skip to content
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

Deprecations fixture #19

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
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
9 changes: 9 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,15 @@ in test failure descriptions. Very useful in combination with MonkeyPatch.
... pass
>>> fixture.cleanUp()

Deprecations
++++++++++++

Prevent code under test from calling deprecated functions.

Copy link
Member

Choose a reason for hiding this comment

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

You'll need to import warnings here.

You can make this a little cleaner by writing the test using with (we no longer support 2.5)

>>> with fixture:
...    warnings.warn('stop using me', DeprecationWarning)
Exception...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

imported warnings and now local tox -e py27 works. Also switched to using context for the setUp/cleanUp.

>>> import warnings
>>> with fixtures.Deprecations('my_module'):
... warnings.warn('stop using me', DeprecationWarning) # will raise

EnvironmentVariable
+++++++++++++++++++

Expand Down
2 changes: 2 additions & 0 deletions fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
__all__ = [
'ByteStream',
'CompoundFixture',
'Deprecations',
'DetailStream',
'EnvironmentVariable',
'EnvironmentVariableFixture',
Expand Down Expand Up @@ -88,6 +89,7 @@
)
from fixtures._fixtures import (
ByteStream,
Deprecations,
DetailStream,
EnvironmentVariable,
EnvironmentVariableFixture,
Expand Down
4 changes: 4 additions & 0 deletions fixtures/_fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

__all__ = [
'ByteStream',
'Deprecations',
'DetailStream',
'EnvironmentVariable',
'EnvironmentVariableFixture',
Expand All @@ -43,6 +44,9 @@
]


from fixtures._fixtures.deprecations import (
Deprecations,
)
from fixtures._fixtures.environ import (
EnvironmentVariable,
EnvironmentVariableFixture,
Expand Down
95 changes: 95 additions & 0 deletions fixtures/_fixtures/deprecations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright (c) 2015 IBM Corp.
#
# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
# license at the users choice. A copy of both licenses are available in the
# project source as Apache-2.0 and BSD. You may not use this file except in
# compliance with one of these two licences.

# Unless required by applicable law or agreed to in writing, software
# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# license you chose for the specific language governing permissions and
# limitations under that license.

from __future__ import absolute_import

__all__ = [
'Deprecations',
]

import contextlib
import re
import warnings # conflicts with the local warnings module so absolute_import

import fixtures


class Deprecations(fixtures.Fixture):
"""Prevent calls to deprecated functions.

This fixture can be added to a testcase to ensure that the code under test
isn't calling deprecated function. It sets Python's `warnings` module for
the module under test to "error" so that DeprecationWarning will be
raised.

You might want your application to not use any deprecated function.
Deprecated function is going to be removed and sometimes is being removed
because it's buggy and you shouldn't be using it.

It can be difficult to tell just through code reviews that new code is
calling deprecated function. This fixture can be used to protect you from
developers proposing changes that use deprecated function.

It can also be useful to be able to test if your application is still using
some function that's been newly deprecated.

.. note:: This fixture uses :func:`warnings.catch_warnings`, as such the
note there applies: The fixture modifies global state and therefore is
not thread safe.

:param str module: The name of a Python module. DeprecationWarnings emitted
from this module will cause an error to be raised.
"""

def __init__(self, module):
super(Deprecations, self).__init__()
self._module_regex = '^%s' % re.escape(module + '.')

def _setUp(self):
cw = warnings.catch_warnings()
cw.__enter__()
self.addCleanup(cw.__exit__)
warnings.filterwarnings('error', category=DeprecationWarning,
module=self._module_regex)

def ignore_deprecations(self):
"""Indicate that this test expects to call deprecated function.

Normally you'll want to protect all tests from calling deprecated
functions, then some function is deprecated and now tests are failing
due to the deprecation. This function can be used to indicate
that the test is going to call deprecated function and not to fail.
This can be used as a marker for either tests that are there to verify
deprecated functions continue to work and will be removed along with
the function, or as tests that need to be fixed to stop calling
deprecated functions.
"""
warnings.filterwarnings('ignore', category=DeprecationWarning,
module=self._module_regex)

@contextlib.contextmanager
def ignore_deprecations_here(self):
"""This section of code ignores calls to deprecated functions.

If you've got a test that part of it is testing deprecated functions
then wrap the part in this context manager::

with self.deprecations.expect_deprecations_here():
call_deprecated_function()

"""
self.cleanUp()
try:
yield
finally:
self.setUp()
1 change: 1 addition & 0 deletions fixtures/tests/_fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

def load_tests(loader, standard_tests, pattern):
test_modules = [
'deprecations',
'environ',
'logger',
'mockpatch',
Expand Down
72 changes: 72 additions & 0 deletions fixtures/tests/_fixtures/test_deprecations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright (c) 2015 IBM Corp.
#
# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
# license at the users choice. A copy of both licenses are available in the
# project source as Apache-2.0 and BSD. You may not use this file except in
# compliance with one of these two licences.

# Unless required by applicable law or agreed to in writing, software
# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# license you chose for the specific language governing permissions and
# limitations under that license.

import warnings

import testtools

from fixtures import Deprecations


MODULE = 'fixtures'


class TestDeprecations(testtools.TestCase):
def test_null_case(self):
# When the Deprecations fixture isn't used then deprecations are not
# errors. This shows that python works as required for these tests.
warnings.warn('message ignored', DeprecationWarning)

def test_enabled_raises(self):
# When the Deprecations fixture is active, calling deprecated function
# is an error.
self.useFixture(Deprecations(MODULE))
self.assertRaises(
DeprecationWarning,
lambda: warnings.warn('message ignored', DeprecationWarning))

def test_ignore_deprecations(self):
# When ignore_deprecations() in a test, deprecations are not an error.
deprecations = self.useFixture(Deprecations(MODULE))
deprecations.ignore_deprecations()
warnings.warn('message ignored', DeprecationWarning)

def test_ignore_deprecations_here(self):
# While in the ignore_deprecations_here() context, deprecations are not
# errors, and afterwards deprecations are errors.
deprecations = self.useFixture(Deprecations(MODULE))
with deprecations.ignore_deprecations_here():
warnings.warn('message ignored', DeprecationWarning)
self.assertRaises(
DeprecationWarning,
lambda: warnings.warn('not ignored', DeprecationWarning))

def test_other_module(self):
# When the Deprecations fixture is active, deprecations from other
# modules are ignored.
self.useFixture(Deprecations('different_module'))
warnings.warn('message ignored', DeprecationWarning)

def test_multiple_instances(self):
# When there are multiple Deprecations fixtures in use, one going out
# of scope doesn't mess up the other one.
self.useFixture(Deprecations(MODULE))

with Deprecations('different_module'):
self.assertRaises(
DeprecationWarning,
lambda: warnings.warn('not ignored', DeprecationWarning))

self.assertRaises(
DeprecationWarning,
lambda: warnings.warn('not ignored', DeprecationWarning))