From 35bbcc39a28b3464612f1b4060b05877290794ff Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 18 Jul 2015 14:40:00 -0300 Subject: [PATCH 1/4] Interpret strings to "plugins" arg in pytest.main() as module names See #855 --- CHANGELOG | 5 +++++ _pytest/config.py | 5 ++++- testing/acceptance_test.py | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 26135beac..623e27406 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,11 @@ - fix issue856: consider --color parameter in all outputs (for example --fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR. +- fix issue855: passing str objects as `plugins` argument to pytest.main + is now interpreted as a module name to be imported and registered as a + plugin, instead of silently having no effect. + Thanks xmo-odoo for the report and Bruno Oliveira for the PR. + - fix issue744: fix for ast.Call changes in Python 3.5+. Thanks Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and Thomas Kluyver. diff --git a/_pytest/config.py b/_pytest/config.py index a954a6222..ad944adc5 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -80,7 +80,10 @@ def _prepareconfig(args=None, plugins=None): try: if plugins: for plugin in plugins: - pluginmanager.register(plugin) + if isinstance(plugin, py.builtin._basestring): + pluginmanager.consider_pluginarg(plugin) + else: + pluginmanager.register(plugin) return pluginmanager.hook.pytest_cmdline_parse( pluginmanager=pluginmanager, args=args) except Exception: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 614af6a3a..fffb67e71 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,3 +1,4 @@ +import sys import py, pytest class TestGeneralUsage: @@ -370,6 +371,21 @@ class TestGeneralUsage: "*fixture 'invalid_fixture' not found", ]) + def test_plugins_given_as_strings(self, tmpdir, monkeypatch): + """test that str values passed to main() as `plugins` arg + are interpreted as module names to be imported and registered. + #855. + """ + with pytest.raises(ImportError) as excinfo: + pytest.main([str(tmpdir)], plugins=['invalid.module']) + assert 'invalid' in str(excinfo.value) + + p = tmpdir.join('test_test_plugins_given_as_strings.py') + p.write('def test_foo(): pass') + mod = py.std.types.ModuleType("myplugin") + monkeypatch.setitem(sys.modules, 'myplugin', mod) + assert pytest.main(args=[str(tmpdir)], plugins=['myplugin']) == 0 + class TestInvocationVariants: def test_earlyinit(self, testdir): From d10448728247beabc0a8b4fea3dd411544116292 Mon Sep 17 00:00:00 2001 From: Eric Hunsberger Date: Thu, 23 Jul 2015 16:38:25 -0400 Subject: [PATCH 2/4] importorskip: Allow non-integer version strings Use `pkg_resources.parse_version` to parse version strings. This can handle 'dev', 'rc', alpha and beta version strings, among others. --- CHANGELOG | 3 +++ _pytest/runner.py | 8 +++----- testing/test_runner.py | 13 +++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 26135beac..b4989bb1f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ 2.7.3 (compared to 2.7.2) ----------------------------- +- Allow 'dev', 'rc', or other non-integer version strings in `importorskip`. + Thanks to Eric Hunsberger for the PR. + - fix issue856: consider --color parameter in all outputs (for example --fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR. diff --git a/_pytest/runner.py b/_pytest/runner.py index 8accd3b0c..ad106b4b4 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -3,6 +3,8 @@ import bdb import sys from time import time +from pkg_resources import parse_version + import py import pytest from py._code.code import TerminalRepr @@ -483,8 +485,6 @@ def importorskip(modname, minversion=None): """ return imported module if it has at least "minversion" as its __version__ attribute. If no minversion is specified the a skip is only triggered if the module can not be imported. - Note that version comparison only works with simple version strings - like "1.2.3" but not "1.2.3.dev1" or others. """ __tracebackhide__ = True compile(modname, '', 'eval') # to catch syntaxerrors @@ -496,9 +496,7 @@ def importorskip(modname, minversion=None): if minversion is None: return mod verattr = getattr(mod, '__version__', None) - def intver(verstring): - return [int(x) for x in verstring.split(".")] - if verattr is None or intver(verattr) < intver(minversion): + if verattr is None or parse_version(verattr) < parse_version(minversion): skip("module %r has __version__ %r, required is: %r" %( modname, verattr, minversion)) return mod diff --git a/testing/test_runner.py b/testing/test_runner.py index e62aea9f7..e359127e2 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -471,6 +471,19 @@ def test_importorskip_imports_last_module_part(): ospath = pytest.importorskip("os.path") assert os.path == ospath +def test_importorskip_dev_module(): + try: + mod = py.std.types.ModuleType("mockmodule") + mod.__version__ = '0.13.0.dev-43290' + sys.modules['mockmodule'] = mod + mod2 = pytest.importorskip('mockmodule', minversion='0.12.0') + assert mod2 == mod + pytest.raises(pytest.skip.Exception, """ + pytest.importorskip('mockmodule1', minversion='0.14.0')""") + except pytest.skip.Exception: + print(py.code.ExceptionInfo()) + pytest.fail("spurious skip") + def test_pytest_cmdline_main(testdir): p = testdir.makepyfile(""" From 3cd19a7e45fac0694e9d91bb01611ff2d85684f5 Mon Sep 17 00:00:00 2001 From: Eric Hunsberger Date: Fri, 24 Jul 2015 01:12:01 -0400 Subject: [PATCH 3/4] Use monkeypatch for setting modules in tests Instead of directly setting `sys.modules`. This ensures that they get removed at the end of the test. --- testing/test_runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/test_runner.py b/testing/test_runner.py index e359127e2..167ddc57b 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -439,7 +439,7 @@ def test_exception_printing_skip(): s = excinfo.exconly(tryshort=True) assert s.startswith("Skipped") -def test_importorskip(): +def test_importorskip(monkeypatch): importorskip = pytest.importorskip def f(): importorskip("asdlkj") @@ -457,7 +457,7 @@ def test_importorskip(): pytest.raises(SyntaxError, "pytest.importorskip('x=y')") mod = py.std.types.ModuleType("hello123") mod.__version__ = "1.3" - sys.modules["hello123"] = mod + monkeypatch.setitem(sys.modules, "hello123", mod) pytest.raises(pytest.skip.Exception, """ pytest.importorskip("hello123", minversion="1.3.1") """) @@ -471,11 +471,11 @@ def test_importorskip_imports_last_module_part(): ospath = pytest.importorskip("os.path") assert os.path == ospath -def test_importorskip_dev_module(): +def test_importorskip_dev_module(monkeypatch): try: mod = py.std.types.ModuleType("mockmodule") mod.__version__ = '0.13.0.dev-43290' - sys.modules['mockmodule'] = mod + monkeypatch.setitem(sys.modules, 'mockmodule', mod) mod2 = pytest.importorskip('mockmodule', minversion='0.12.0') assert mod2 == mod pytest.raises(pytest.skip.Exception, """ From 4f1ae8c45e0e29bdcf4b92d582a9fd3a407c208b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 24 Jul 2015 17:52:56 -0300 Subject: [PATCH 4/4] Workaround for cmdexec bug on Windows This bug fails the entire pytest suite when executed with the --lsof option in Python 2 on Windows. --- testing/conftest.py | 8 +++++++- testing/test_capture.py | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/testing/conftest.py b/testing/conftest.py index 7ebfccd79..3fe1c80f3 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -15,7 +15,13 @@ class LsofFdLeakChecker(object): def _exec_lsof(self): pid = os.getpid() #return py.process.cmdexec("lsof -Ffn0 -p %d" % pid) - return py.process.cmdexec("lsof -p %d" % pid) + try: + return py.process.cmdexec("lsof -p %d" % pid) + except UnicodeDecodeError: + # cmdexec may raise UnicodeDecodeError on Windows systems + # with locale other than english: + # https://bitbucket.org/pytest-dev/py/issues/66 + return '' def _parse_lsof_output(self, out): def isopen(line): diff --git a/testing/test_capture.py b/testing/test_capture.py index 0fd13b58c..2c69254ad 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -651,7 +651,8 @@ def lsof_check(): pid = os.getpid() try: out = py.process.cmdexec("lsof -p %d" % pid) - except py.process.cmdexec.Error: + except (py.process.cmdexec.Error, UnicodeDecodeError): + # about UnicodeDecodeError, see note on conftest.py pytest.skip("could not run 'lsof'") yield out2 = py.process.cmdexec("lsof -p %d" % pid)