From 043aadeaf2467d7b4c263ba339fe1daea3ac8ae5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 11 Jan 2017 17:11:56 -0200 Subject: [PATCH 1/2] Consider plugins loaded by PYTEST_PLUGINS for assertion rewrite Fix #2185 --- CHANGELOG.rst | 5 +++++ _pytest/config.py | 7 +++++-- testing/test_assertrewrite.py | 18 +++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7c3df0327..bb5c5e6a6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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. @@ -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) diff --git a/_pytest/config.py b/_pytest/config.py index 55326447e..9aa465954 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -399,13 +399,15 @@ class PytestPluginManager(PluginManager): self.consider_module(conftestmodule) def consider_env(self): - self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) + specs = os.environ.get("PYTEST_PLUGINS") + if specs: + plugins = specs.split(',') + self._import_plugin_specs(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) def _import_plugin_specs(self, spec): @@ -427,6 +429,7 @@ class PytestPluginManager(PluginManager): importspec = "_pytest." + modname else: importspec = modname + self.rewrite_hook.mark_rewrite(modname) try: __import__(importspec) except ImportError as e: diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 8e26cdb1b..fdf674f25 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -682,7 +682,7 @@ def test_rewritten(): 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': "", @@ -695,6 +695,22 @@ def test_rewritten(): 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): From 7cd7c283ddde7c45ad1b97372948ff6ba2f0aaa4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 Jan 2017 14:23:09 -0200 Subject: [PATCH 2/2] Refactor plugin specs handling into an isolated function --- _pytest/config.py | 38 ++++++++++++++++++++++++-------------- testing/test_config.py | 15 +++++++++++++++ 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 9aa465954..8578e8aaa 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -399,30 +399,22 @@ class PytestPluginManager(PluginManager): self.consider_module(conftestmodule) def consider_env(self): - specs = os.environ.get("PYTEST_PLUGINS") - if specs: - plugins = specs.split(',') - self._import_plugin_specs(plugins) + 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._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: @@ -450,6 +442,24 @@ class PytestPluginManager(PluginManager): 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. diff --git a/testing/test_config.py b/testing/test_config.py index 6bc867737..e6aa423e8 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -587,6 +587,21 @@ def test_load_initial_conftest_last_ordering(testdir): 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("""