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

Consider plugins loaded by PYTEST_PLUGINS for assertion rewrite #2186

Merged
merged 2 commits into from
Jan 13, 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
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
* pytest no longer recognizes coroutine functions as yield tests (`#2129`_).
Thanks to `@malinoff`_ for the PR.

* Plugins loaded by the ``PYTEST_PLUGINS`` environment variable are now automatically
considered for assertion rewriting (`#2185`_).
Thanks `@nicoddemus`_ for the PR.

* Improve error message when pytest.warns fails (`#2150`_). The type(s) of the
expected warnings and the list of caught warnings is added to the
error message. Thanks `@lesteve`_ for the PR.
Expand All @@ -23,6 +27,7 @@
.. _#2129: https://github.com/pytest-dev/pytest/issues/2129
.. _#2148: https://github.com/pytest-dev/pytest/issues/2148
.. _#2150: https://github.com/pytest-dev/pytest/issues/2150
.. _#2185: https://github.com/pytest-dev/pytest/issues/2185


3.0.5 (2016-12-05)
Expand Down
35 changes: 24 additions & 11 deletions _pytest/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,31 +402,26 @@ def consider_env(self):
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))

def consider_module(self, mod):
plugins = getattr(mod, 'pytest_plugins', [])
if isinstance(plugins, str):
plugins = [plugins]
self.rewrite_hook.mark_rewrite(*plugins)
self._import_plugin_specs(plugins)
self._import_plugin_specs(getattr(mod, 'pytest_plugins', []))

def _import_plugin_specs(self, spec):
if spec:
if isinstance(spec, str):
spec = spec.split(",")
for import_spec in spec:
self.import_plugin(import_spec)
plugins = _get_plugin_specs_as_list(spec)
for import_spec in plugins:
self.import_plugin(import_spec)

def import_plugin(self, modname):
# most often modname refers to builtin modules, e.g. "pytester",
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
assert isinstance(modname, str)
assert isinstance(modname, str), "module name as string required, got %r" % modname
if self.get_plugin(modname) is not None:
return
if modname in builtin_plugins:
importspec = "_pytest." + modname
else:
importspec = modname
self.rewrite_hook.mark_rewrite(modname)
try:
__import__(importspec)
except ImportError as e:
Expand All @@ -447,6 +442,24 @@ def import_plugin(self, modname):
self.consider_module(mod)


def _get_plugin_specs_as_list(specs):
"""
Parses a list of "plugin specs" and returns a list of plugin names.

Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in
which case it is returned as a list. Specs can also be `None` in which case an
empty list is returned.
"""
if specs is not None:
if isinstance(specs, str):
specs = specs.split(',') if specs else []
if not isinstance(specs, (list, tuple)):
raise UsageError("Plugin specs must be a ','-separated string or a "
"list/tuple of strings for plugin names. Given: %r" % specs)
return list(specs)
return []


class Parser:
""" Parser for command line arguments and ini-file values.

Expand Down
18 changes: 17 additions & 1 deletion testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ def test_remember_rewritten_modules(self, pytestconfig, testdir, monkeypatch):
hook.mark_rewrite('test_remember_rewritten_modules')
assert warnings == []

def test_rewrite_warning_using_pytest_plugins(self, testdir, monkeypatch):
def test_rewrite_warning_using_pytest_plugins(self, testdir):
testdir.makepyfile(**{
'conftest.py': "pytest_plugins = ['core', 'gui', 'sci']",
'core.py': "",
Expand All @@ -695,6 +695,22 @@ def test_rewrite_warning_using_pytest_plugins(self, testdir, monkeypatch):
result.stdout.fnmatch_lines(['*= 1 passed in *=*'])
assert 'pytest-warning summary' not in result.stdout.str()

def test_rewrite_warning_using_pytest_plugins_env_var(self, testdir, monkeypatch):
monkeypatch.setenv('PYTEST_PLUGINS', 'plugin')
testdir.makepyfile(**{
'plugin.py': "",
'test_rewrite_warning_using_pytest_plugins_env_var.py': """
import plugin
pytest_plugins = ['plugin']
def test():
pass
""",
})
testdir.chdir()
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(['*= 1 passed in *=*'])
assert 'pytest-warning summary' not in result.stdout.str()


class TestAssertionRewriteHookDetails(object):
def test_loader_is_package_false_for_module(self, testdir):
Expand Down
15 changes: 15 additions & 0 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,21 @@ def pytest_load_initial_conftests(self):
assert [x.function.__module__ for x in l] == expected


def test_get_plugin_specs_as_list():
from _pytest.config import _get_plugin_specs_as_list
with pytest.raises(pytest.UsageError):
_get_plugin_specs_as_list(set(['foo']))
with pytest.raises(pytest.UsageError):
_get_plugin_specs_as_list(dict())

assert _get_plugin_specs_as_list(None) == []
assert _get_plugin_specs_as_list('') == []
assert _get_plugin_specs_as_list('foo') == ['foo']
assert _get_plugin_specs_as_list('foo,bar') == ['foo', 'bar']
assert _get_plugin_specs_as_list(['foo', 'bar']) == ['foo', 'bar']
assert _get_plugin_specs_as_list(('foo', 'bar')) == ['foo', 'bar']


class TestWarning:
def test_warn_config(self, testdir):
testdir.makeconftest("""
Expand Down