Merge pull request #3010 from cryvate/fix-issue-2985

Improve handling of pyargs
This commit is contained in:
Ronny Pfannschmidt 2017-12-13 13:56:42 +01:00 committed by GitHub
commit 476d4df1b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 114 additions and 3 deletions

View File

@ -74,6 +74,7 @@ Grig Gheorghiu
Grigorii Eremeev (budulianin) Grigorii Eremeev (budulianin)
Guido Wesdorp Guido Wesdorp
Harald Armin Massa Harald Armin Massa
Henk-Jaap Wagenaar
Hugo van Kemenade Hugo van Kemenade
Hui Wang (coldnight) Hui Wang (coldnight)
Ian Bicking Ian Bicking

View File

@ -1,8 +1,10 @@
""" core implementation of testing process: init, session, runtest loop. """ """ core implementation of testing process: init, session, runtest loop. """
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
import contextlib
import functools import functools
import os import os
import pkgutil
import six import six
import sys import sys
@ -206,6 +208,46 @@ def pytest_ignore_collect(path, config):
return False return False
@contextlib.contextmanager
def _patched_find_module():
"""Patch bug in pkgutil.ImpImporter.find_module
When using pkgutil.find_loader on python<3.4 it removes symlinks
from the path due to a call to os.path.realpath. This is not consistent
with actually doing the import (in these versions, pkgutil and __import__
did not share the same underlying code). This can break conftest
discovery for pytest where symlinks are involved.
The only supported python<3.4 by pytest is python 2.7.
"""
if six.PY2: # python 3.4+ uses importlib instead
def find_module_patched(self, fullname, path=None):
# Note: we ignore 'path' argument since it is only used via meta_path
subname = fullname.split(".")[-1]
if subname != fullname and self.path is None:
return None
if self.path is None:
path = None
else:
# original: path = [os.path.realpath(self.path)]
path = [self.path]
try:
file, filename, etc = pkgutil.imp.find_module(subname,
path)
except ImportError:
return None
return pkgutil.ImpLoader(fullname, file, filename, etc)
old_find_module = pkgutil.ImpImporter.find_module
pkgutil.ImpImporter.find_module = find_module_patched
try:
yield
finally:
pkgutil.ImpImporter.find_module = old_find_module
else:
yield
class FSHookProxy: class FSHookProxy:
def __init__(self, fspath, pm, remove_mods): def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath self.fspath = fspath
@ -728,9 +770,10 @@ class Session(FSCollector):
"""Convert a dotted module name to path. """Convert a dotted module name to path.
""" """
import pkgutil
try: try:
loader = pkgutil.find_loader(x) with _patched_find_module():
loader = pkgutil.find_loader(x)
except ImportError: except ImportError:
return x return x
if loader is None: if loader is None:
@ -738,7 +781,8 @@ class Session(FSCollector):
# This method is sometimes invoked when AssertionRewritingHook, which # This method is sometimes invoked when AssertionRewritingHook, which
# does not define a get_filename method, is already in place: # does not define a get_filename method, is already in place:
try: try:
path = loader.get_filename(x) with _patched_find_module():
path = loader.get_filename(x)
except AttributeError: except AttributeError:
# Retrieve path from AssertionRewritingHook: # Retrieve path from AssertionRewritingHook:
path = loader.modules[x][0].co_filename path = loader.modules[x][0].co_filename

1
changelog/2985.bugfix Normal file
View File

@ -0,0 +1 @@
Fix conversion of pyargs to filename to not convert symlinks and not use deprecated features on Python 3.

View File

@ -3,6 +3,8 @@ from __future__ import absolute_import, division, print_function
import os import os
import sys import sys
import six
import _pytest._code import _pytest._code
import py import py
import pytest import pytest
@ -645,6 +647,69 @@ class TestInvocationVariants(object):
"*1 passed*" "*1 passed*"
]) ])
@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks")
def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
"""
test --pyargs option with packages with path containing symlink can
have conftest.py in their package (#2985)
"""
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False)
search_path = ["lib", os.path.join("local", "lib")]
dirname = "lib"
d = testdir.mkdir(dirname)
foo = d.mkdir("foo")
foo.ensure("__init__.py")
lib = foo.mkdir('bar')
lib.ensure("__init__.py")
lib.join("test_bar.py"). \
write("def test_bar(): pass\n"
"def test_other(a_fixture):pass")
lib.join("conftest.py"). \
write("import pytest\n"
"@pytest.fixture\n"
"def a_fixture():pass")
d_local = testdir.mkdir("local")
symlink_location = os.path.join(str(d_local), "lib")
if six.PY2:
os.symlink(str(d), symlink_location)
else:
os.symlink(str(d), symlink_location, target_is_directory=True)
# The structure of the test directory is now:
# .
# ├── local
# │ └── lib -> ../lib
# └── lib
# └── foo
# ├── __init__.py
# └── bar
# ├── __init__.py
# ├── conftest.py
# └── test_bar.py
def join_pythonpath(*dirs):
cur = os.getenv('PYTHONPATH')
if cur:
dirs += (cur,)
return os.pathsep.join(str(p) for p in dirs)
monkeypatch.setenv('PYTHONPATH', join_pythonpath(*search_path))
for p in search_path:
monkeypatch.syspath_prepend(p)
# module picked up in symlink-ed directory:
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
testdir.chdir()
assert result.ret == 0
result.stdout.fnmatch_lines([
"*lib/foo/bar/test_bar.py::test_bar*PASSED*",
"*lib/foo/bar/test_bar.py::test_other*PASSED*",
"*2 passed*"
])
def test_cmdline_python_package_not_exists(self, testdir): def test_cmdline_python_package_not_exists(self, testdir):
result = testdir.runpytest("--pyargs", "tpkgwhatv") result = testdir.runpytest("--pyargs", "tpkgwhatv")
assert result.ret assert result.ret