Ensure that a module within a namespace package can be found by --pyargs.
This commit is contained in:
parent
66e66f61e8
commit
e2e6e31711
|
@ -1,7 +1,5 @@
|
||||||
""" core implementation of testing process: init, session, runtest loop. """
|
""" core implementation of testing process: init, session, runtest loop. """
|
||||||
import imp
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
|
@ -25,8 +23,6 @@ EXIT_INTERNALERROR = 3
|
||||||
EXIT_USAGEERROR = 4
|
EXIT_USAGEERROR = 4
|
||||||
EXIT_NOTESTSCOLLECTED = 5
|
EXIT_NOTESTSCOLLECTED = 5
|
||||||
|
|
||||||
name_re = re.compile("^[a-zA-Z_]\w*$")
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
|
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
|
||||||
type="args", default=['.*', 'CVS', '_darcs', '{arch}', '*.egg'])
|
type="args", default=['.*', 'CVS', '_darcs', '{arch}', '*.egg'])
|
||||||
|
@ -658,36 +654,29 @@ class Session(FSCollector):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _tryconvertpyarg(self, x):
|
def _tryconvertpyarg(self, x):
|
||||||
mod = None
|
"""Convert a dotted module name to path.
|
||||||
path = [os.path.abspath('.')] + sys.path
|
|
||||||
for name in x.split('.'):
|
|
||||||
# ignore anything that's not a proper name here
|
|
||||||
# else something like --pyargs will mess up '.'
|
|
||||||
# since imp.find_module will actually sometimes work for it
|
|
||||||
# but it's supposed to be considered a filesystem path
|
|
||||||
# not a package
|
|
||||||
if name_re.match(name) is None:
|
|
||||||
return x
|
|
||||||
try:
|
|
||||||
fd, mod, type_ = imp.find_module(name, path)
|
|
||||||
except ImportError:
|
|
||||||
return x
|
|
||||||
else:
|
|
||||||
if fd is not None:
|
|
||||||
fd.close()
|
|
||||||
|
|
||||||
if type_[2] != imp.PKG_DIRECTORY:
|
"""
|
||||||
path = [os.path.dirname(mod)]
|
import pkgutil
|
||||||
else:
|
try:
|
||||||
path = [mod]
|
loader = pkgutil.find_loader(x)
|
||||||
return mod
|
except ImportError:
|
||||||
|
return x
|
||||||
|
if loader is None:
|
||||||
|
return x
|
||||||
|
try:
|
||||||
|
path = loader.get_filename()
|
||||||
|
except:
|
||||||
|
path = loader.modules[x][0].co_filename
|
||||||
|
if loader.is_package(x):
|
||||||
|
path = os.path.dirname(path)
|
||||||
|
return path
|
||||||
|
|
||||||
def _parsearg(self, arg):
|
def _parsearg(self, arg):
|
||||||
""" return (fspath, names) tuple after checking the file exists. """
|
""" return (fspath, names) tuple after checking the file exists. """
|
||||||
arg = str(arg)
|
|
||||||
if self.config.option.pyargs:
|
|
||||||
arg = self._tryconvertpyarg(arg)
|
|
||||||
parts = str(arg).split("::")
|
parts = str(arg).split("::")
|
||||||
|
if self.config.option.pyargs:
|
||||||
|
parts[0] = self._tryconvertpyarg(parts[0])
|
||||||
relpath = parts[0].replace("/", os.sep)
|
relpath = parts[0].replace("/", os.sep)
|
||||||
path = self.config.invocation_dir.join(relpath, abs=True)
|
path = self.config.invocation_dir.join(relpath, abs=True)
|
||||||
if not path.check():
|
if not path.check():
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
|
@ -514,11 +515,20 @@ class TestInvocationVariants:
|
||||||
|
|
||||||
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
|
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
|
||||||
assert result.ret != 0
|
assert result.ret != 0
|
||||||
# FIXME: It would be more natural to match NOT
|
|
||||||
# "ERROR*file*or*package*not*found*".
|
# Depending on whether the process running the test is the
|
||||||
result.stdout.fnmatch_lines([
|
# same as the process parsing the command-line arguments, the
|
||||||
"*collected 0 items*"
|
# type of failure can be different:
|
||||||
])
|
if result.stderr.str() == '':
|
||||||
|
# Different processes
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"collected*0*items*/*1*errors"
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
# Same process
|
||||||
|
result.stderr.fnmatch_lines([
|
||||||
|
"ERROR:*file*or*package*not*found:*tpkg.test_hello"
|
||||||
|
])
|
||||||
|
|
||||||
def test_cmdline_python_package(self, testdir, monkeypatch):
|
def test_cmdline_python_package(self, testdir, monkeypatch):
|
||||||
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
|
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
|
||||||
|
@ -557,6 +567,68 @@ class TestInvocationVariants:
|
||||||
"*not*found*test_hello*",
|
"*not*found*test_hello*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_cmdline_python_namespace_package(self, testdir, monkeypatch):
|
||||||
|
"""
|
||||||
|
test --pyargs option with namespace packages (#1567)
|
||||||
|
"""
|
||||||
|
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False)
|
||||||
|
|
||||||
|
search_path = []
|
||||||
|
for dirname in "hello", "world":
|
||||||
|
d = testdir.mkdir(dirname)
|
||||||
|
search_path.append(d)
|
||||||
|
ns = d.mkdir("ns_pkg")
|
||||||
|
ns.join("__init__.py").write(
|
||||||
|
"__import__('pkg_resources').declare_namespace(__name__)")
|
||||||
|
lib = ns.mkdir(dirname)
|
||||||
|
lib.ensure("__init__.py")
|
||||||
|
lib.join("test_{0}.py".format(dirname)). \
|
||||||
|
write("def test_{0}(): pass\n"
|
||||||
|
"def test_other():pass".format(dirname))
|
||||||
|
|
||||||
|
# The structure of the test directory is now:
|
||||||
|
# .
|
||||||
|
# ├── hello
|
||||||
|
# │ └── ns_pkg
|
||||||
|
# │ ├── __init__.py
|
||||||
|
# │ └── hello
|
||||||
|
# │ ├── __init__.py
|
||||||
|
# │ └── test_hello.py
|
||||||
|
# └── world
|
||||||
|
# └── ns_pkg
|
||||||
|
# ├── __init__.py
|
||||||
|
# └── world
|
||||||
|
# ├── __init__.py
|
||||||
|
# └── test_world.py
|
||||||
|
|
||||||
|
def join_pythonpath(*dirs):
|
||||||
|
cur = py.std.os.environ.get('PYTHONPATH')
|
||||||
|
if cur:
|
||||||
|
dirs += (cur,)
|
||||||
|
return ':'.join(str(p) for p in dirs)
|
||||||
|
monkeypatch.setenv('PYTHONPATH', join_pythonpath(*search_path))
|
||||||
|
for p in search_path:
|
||||||
|
monkeypatch.syspath_prepend(p)
|
||||||
|
|
||||||
|
# mixed module and filenames:
|
||||||
|
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "world/ns_pkg")
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*test_hello.py::test_hello*PASSED",
|
||||||
|
"*test_hello.py::test_other*PASSED",
|
||||||
|
"*test_world.py::test_world*PASSED",
|
||||||
|
"*test_world.py::test_other*PASSED",
|
||||||
|
"*4 passed*"
|
||||||
|
])
|
||||||
|
|
||||||
|
# specify tests within a module
|
||||||
|
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other")
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*test_world.py::test_other*PASSED",
|
||||||
|
"*1 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
|
||||||
|
@ -697,4 +769,3 @@ class TestDurationWithFixture:
|
||||||
* setup *test_1*
|
* setup *test_1*
|
||||||
* call *test_1*
|
* call *test_1*
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue