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

Use importlib to avoid module name clashes for pytest #203

Merged
merged 2 commits into from
Feb 19, 2019
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
6 changes: 6 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
(unreleased)
============

- add ``"importlib"`` pyimport mode for python3.5+, allowing unimportable test suites
to contain identically named modules.

1.7.0 (2018-10-11)
==================

Expand Down
30 changes: 30 additions & 0 deletions py/_path/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ def map_as_list(func, iter):
else:
map_as_list = map

ALLOW_IMPORTLIB_MODE = sys.version_info > (3,5)
if ALLOW_IMPORTLIB_MODE:
import importlib


class Stat(object):
def __getattr__(self, name):
return getattr(self._osstatresult, "st_" + name)
Expand Down Expand Up @@ -647,10 +652,35 @@ def pyimport(self, modname=None, ensuresyspath=True):
If ensuresyspath=="append" the root dir will be appended
if it isn't already contained in sys.path.
if ensuresyspath is False no modification of syspath happens.

Special value of ensuresyspath=="importlib" is intended
purely for using in pytest, it is capable only of importing
separate .py files outside packages, e.g. for test suite
without any __init__.py file. It effectively allows having
same-named test modules in different places and offers
mild opt-in via this option. Note that it works only in
recent versions of python.
"""
if not self.check():
raise py.error.ENOENT(self)

if ensuresyspath == 'importlib':
if modname is None:
modname = self.purebasename
if not ALLOW_IMPORTLIB_MODE:
raise ImportError(
"Can't use importlib due to old version of Python")
spec = importlib.util.spec_from_file_location(
modname, str(self))
if spec is None:
raise ImportError(
"Can't find module %s at location %s" %
(modname, str(self))
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod

pkgpath = None
if modname is None:
pkgpath = self.pypkgpath()
Expand Down
33 changes: 33 additions & 0 deletions testing/path/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,39 @@ def test_ensuresyspath_append(self, tmpdir):
assert str(root1) not in sys.path[:-1]


class TestImportlibImport:
pytestmark = py.test.mark.skipif("sys.version_info < (3, 5)")

OPTS = {'ensuresyspath': 'importlib'}

def test_pyimport(self, path1):
obj = path1.join('execfile.py').pyimport(**self.OPTS)
assert obj.x == 42
assert obj.__name__ == 'execfile'

def test_pyimport_dir_fails(self, tmpdir):
p = tmpdir.join("hello_123")
p.ensure("__init__.py")
with pytest.raises(ImportError):
p.pyimport(**self.OPTS)

def test_pyimport_execfile_different_name(self, path1):
obj = path1.join('execfile.py').pyimport(modname="0x.y.z", **self.OPTS)
assert obj.x == 42
assert obj.__name__ == '0x.y.z'

def test_pyimport_relative_import_fails(self, path1):
otherdir = path1.join('otherdir')
with pytest.raises(ImportError):
otherdir.join('a.py').pyimport(**self.OPTS)

def test_pyimport_doesnt_use_sys_modules(self, tmpdir):
p = tmpdir.ensure('file738jsk.py')
mod = p.pyimport(**self.OPTS)
assert mod.__name__ == 'file738jsk'
assert 'file738jsk' not in sys.modules


def test_pypkgdir(tmpdir):
pkg = tmpdir.ensure('pkg1', dir=1)
pkg.ensure("__init__.py")
Expand Down