Skip to content

Commit

Permalink
Merge pull request #203 from neumond/master
Browse files Browse the repository at this point in the history
Use importlib to avoid module name clashes for pytest
  • Loading branch information
nicoddemus committed Feb 19, 2019
2 parents c6f4da5 + 642c4d2 commit 7c2b288
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG
@@ -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
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
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

0 comments on commit 7c2b288

Please sign in to comment.