Introduce --import-mode=importlib (#7246)
Fix #5821 Co-authored-by: Ran Benita <ran@unusedvar.com>
This commit is contained in:
parent
2c37585f58
commit
ab6dacf1d1
|
@ -0,0 +1,14 @@
|
||||||
|
New ``--import-mode=importlib`` option that uses `importlib <https://docs.python.org/3/library/importlib.html>`__ to import test modules.
|
||||||
|
|
||||||
|
Traditionally pytest used ``__import__`` while changing ``sys.path`` to import test modules (which
|
||||||
|
also changes ``sys.modules`` as a side-effect), which works but has a number of drawbacks, like requiring test modules
|
||||||
|
that don't live in packages to have unique names (as they need to reside under a unique name in ``sys.modules``).
|
||||||
|
|
||||||
|
``--import-mode=importlib`` uses more fine grained import mechanisms from ``importlib`` which don't
|
||||||
|
require pytest to change ``sys.path`` or ``sys.modules`` at all, eliminating much of the drawbacks
|
||||||
|
of the previous mode.
|
||||||
|
|
||||||
|
We intend to make ``--import-mode=importlib`` the default in future versions, so users are encouraged
|
||||||
|
to try the new mode and provide feedback (both positive or negative) in issue `#7245 <https://github.com/pytest-dev/pytest/issues/7245>`__.
|
||||||
|
|
||||||
|
You can read more about this option in `the documentation <https://docs.pytest.org/en/latest/pythonpath.html#import-modes>`__.
|
|
@ -91,7 +91,8 @@ This has the following benefits:
|
||||||
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
|
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
|
||||||
``python -m pytest``.
|
``python -m pytest``.
|
||||||
|
|
||||||
Note that using this scheme your test files must have **unique names**, because
|
Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode <import-modes>`
|
||||||
|
(which is the default): your test files must have **unique names**, because
|
||||||
``pytest`` will import them as *top-level* modules since there are no packages
|
``pytest`` will import them as *top-level* modules since there are no packages
|
||||||
to derive a full package name from. In other words, the test files in the example above will
|
to derive a full package name from. In other words, the test files in the example above will
|
||||||
be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
|
be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
|
||||||
|
@ -118,9 +119,12 @@ Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test
|
||||||
you to have modules with the same name. But now this introduces a subtle problem: in order to load
|
you to have modules with the same name. But now this introduces a subtle problem: in order to load
|
||||||
the test modules from the ``tests`` directory, pytest prepends the root of the repository to
|
the test modules from the ``tests`` directory, pytest prepends the root of the repository to
|
||||||
``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
|
``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
|
||||||
|
|
||||||
This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
|
This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
|
||||||
because you want to test the *installed* version of your package, not the local code from the repository.
|
because you want to test the *installed* version of your package, not the local code from the repository.
|
||||||
|
|
||||||
|
.. _`src-layout`:
|
||||||
|
|
||||||
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
|
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
|
||||||
sub-directory of your root:
|
sub-directory of your root:
|
||||||
|
|
||||||
|
@ -145,6 +149,15 @@ sub-directory of your root:
|
||||||
This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
|
This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
|
||||||
`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
|
`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have
|
||||||
|
any of the drawbacks above because ``sys.path`` and ``sys.modules`` are not changed when importing
|
||||||
|
test modules, so users that run
|
||||||
|
into this issue are strongly encouraged to try it and report if the new option works well for them.
|
||||||
|
|
||||||
|
The ``src`` directory layout is still strongly recommended however.
|
||||||
|
|
||||||
|
|
||||||
Tests as part of application code
|
Tests as part of application code
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -190,8 +203,8 @@ Note that this layout also works in conjunction with the ``src`` layout mentione
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If ``pytest`` finds an "a/b/test_module.py" test file while
|
In ``prepend`` and ``append`` import-modes, if pytest finds a ``"a/b/test_module.py"``
|
||||||
recursing into the filesystem it determines the import name
|
test file while recursing into the filesystem it determines the import name
|
||||||
as follows:
|
as follows:
|
||||||
|
|
||||||
* determine ``basedir``: this is the first "upward" (towards the root)
|
* determine ``basedir``: this is the first "upward" (towards the root)
|
||||||
|
@ -212,6 +225,10 @@ Note that this layout also works in conjunction with the ``src`` layout mentione
|
||||||
from each other and thus deriving a canonical import name helps
|
from each other and thus deriving a canonical import name helps
|
||||||
to avoid surprises such as a test module getting imported twice.
|
to avoid surprises such as a test module getting imported twice.
|
||||||
|
|
||||||
|
With ``--import-mode=importlib`` things are less convoluted because
|
||||||
|
pytest doesn't need to change ``sys.path`` or ``sys.modules``, making things
|
||||||
|
much less surprising.
|
||||||
|
|
||||||
|
|
||||||
.. _`virtualenv`: https://pypi.org/project/virtualenv/
|
.. _`virtualenv`: https://pypi.org/project/virtualenv/
|
||||||
.. _`buildout`: http://www.buildout.org/
|
.. _`buildout`: http://www.buildout.org/
|
||||||
|
|
|
@ -3,11 +3,65 @@
|
||||||
pytest import mechanisms and ``sys.path``/``PYTHONPATH``
|
pytest import mechanisms and ``sys.path``/``PYTHONPATH``
|
||||||
========================================================
|
========================================================
|
||||||
|
|
||||||
Here's a list of scenarios where pytest may need to change ``sys.path`` in order
|
.. _`import-modes`:
|
||||||
to import test modules or ``conftest.py`` files.
|
|
||||||
|
Import modes
|
||||||
|
------------
|
||||||
|
|
||||||
|
pytest as a testing framework needs to import test modules and ``conftest.py`` files for execution.
|
||||||
|
|
||||||
|
Importing files in Python (at least until recently) is a non-trivial processes, often requiring
|
||||||
|
changing `sys.path <https://docs.python.org/3/library/sys.html#sys.path>`__. Some aspects of the
|
||||||
|
import process can be controlled through the ``--import-mode`` command-line flag, which can assume
|
||||||
|
these values:
|
||||||
|
|
||||||
|
* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
|
||||||
|
of ``sys.path`` if not already there, and then imported with the `__import__ <https://docs.python.org/3/library/functions.html#__import__>`__ builtin.
|
||||||
|
|
||||||
|
This requires test module names to be unique when the test directory tree is not arranged in
|
||||||
|
packages, because the modules will put in ``sys.modules`` after importing.
|
||||||
|
|
||||||
|
This is the classic mechanism, dating back from the time Python 2 was still supported.
|
||||||
|
|
||||||
|
* ``append``: the directory containing each module is appended to the end of ``sys.path`` if not already
|
||||||
|
there, and imported with ``__import__``.
|
||||||
|
|
||||||
|
This better allows to run test modules against installed versions of a package even if the
|
||||||
|
package under test has the same import root. For example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
testing/__init__.py
|
||||||
|
testing/test_pkg_under_test.py
|
||||||
|
pkg_under_test/
|
||||||
|
|
||||||
|
the tests will run against the installed version
|
||||||
|
of ``pkg_under_test`` when ``--import-mode=append`` is used whereas
|
||||||
|
with ``prepend`` they would pick up the local version. This kind of confusion is why
|
||||||
|
we advocate for using :ref:`src <src-layout>` layouts.
|
||||||
|
|
||||||
|
Same as ``prepend``, requires test module names to be unique when the test directory tree is
|
||||||
|
not arranged in packages, because the modules will put in ``sys.modules`` after importing.
|
||||||
|
|
||||||
|
* ``importlib``: new in pytest-6.0, this mode uses `importlib <https://docs.python.org/3/library/importlib.html>`__ to import test modules. This gives full control over the import process, and doesn't require
|
||||||
|
changing ``sys.path`` or ``sys.modules`` at all.
|
||||||
|
|
||||||
|
For this reason this doesn't require test module names to be unique at all, but also makes test
|
||||||
|
modules non-importable by each other. This was made possible in previous modes, for tests not residing
|
||||||
|
in Python packages, because of the side-effects of changing ``sys.path`` and ``sys.modules``
|
||||||
|
mentioned above. Users which require this should turn their tests into proper packages instead.
|
||||||
|
|
||||||
|
We intend to make ``importlib`` the default in future releases.
|
||||||
|
|
||||||
|
``prepend`` and ``append`` import modes scenarios
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
Here's a list of scenarios when using ``prepend`` or ``append`` import modes where pytest needs to
|
||||||
|
change ``sys.path`` in order to import test modules or ``conftest.py`` files, and the issues users
|
||||||
|
might encounter because of that.
|
||||||
|
|
||||||
Test modules / ``conftest.py`` files inside packages
|
Test modules / ``conftest.py`` files inside packages
|
||||||
----------------------------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Consider this file and directory layout::
|
Consider this file and directory layout::
|
||||||
|
|
||||||
|
@ -28,8 +82,6 @@ When executing:
|
||||||
|
|
||||||
pytest root/
|
pytest root/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a package given that
|
pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a package given that
|
||||||
there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the
|
there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the
|
||||||
last folder which still contains an ``__init__.py`` file in order to find the package *root* (in
|
last folder which still contains an ``__init__.py`` file in order to find the package *root* (in
|
||||||
|
@ -44,7 +96,7 @@ and allow test modules to have duplicated names. This is also discussed in detai
|
||||||
:ref:`test discovery`.
|
:ref:`test discovery`.
|
||||||
|
|
||||||
Standalone test modules / ``conftest.py`` files
|
Standalone test modules / ``conftest.py`` files
|
||||||
-----------------------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Consider this file and directory layout::
|
Consider this file and directory layout::
|
||||||
|
|
||||||
|
|
|
@ -1204,7 +1204,10 @@ _PY_DIR = py.path.local(py.__file__).dirpath()
|
||||||
|
|
||||||
|
|
||||||
def filter_traceback(entry: TracebackEntry) -> bool:
|
def filter_traceback(entry: TracebackEntry) -> bool:
|
||||||
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
"""Return True if a TracebackEntry instance should be included in tracebacks.
|
||||||
|
|
||||||
|
We hide traceback entries of:
|
||||||
|
|
||||||
* dynamically generated code (no code to show up for it);
|
* dynamically generated code (no code to show up for it);
|
||||||
* internal traceback from pytest or its internal libraries, py and pluggy.
|
* internal traceback from pytest or its internal libraries, py and pluggy.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -33,6 +33,7 @@ else:
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from typing import NoReturn
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from typing_extensions import Final
|
from typing_extensions import Final
|
||||||
|
|
||||||
|
@ -401,3 +402,38 @@ else:
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
order_preserving_dict = OrderedDict
|
order_preserving_dict = OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
# Perform exhaustiveness checking.
|
||||||
|
#
|
||||||
|
# Consider this example:
|
||||||
|
#
|
||||||
|
# MyUnion = Union[int, str]
|
||||||
|
#
|
||||||
|
# def handle(x: MyUnion) -> int {
|
||||||
|
# if isinstance(x, int):
|
||||||
|
# return 1
|
||||||
|
# elif isinstance(x, str):
|
||||||
|
# return 2
|
||||||
|
# else:
|
||||||
|
# raise Exception('unreachable')
|
||||||
|
#
|
||||||
|
# Now suppose we add a new variant:
|
||||||
|
#
|
||||||
|
# MyUnion = Union[int, str, bytes]
|
||||||
|
#
|
||||||
|
# After doing this, we must remember ourselves to go and update the handle
|
||||||
|
# function to handle the new variant.
|
||||||
|
#
|
||||||
|
# With `assert_never` we can do better:
|
||||||
|
#
|
||||||
|
# // throw new Error('unreachable');
|
||||||
|
# return assert_never(x)
|
||||||
|
#
|
||||||
|
# Now, if we forget to handle the new variant, the type-checker will emit a
|
||||||
|
# compile-time error, instead of the runtime error we would have gotten
|
||||||
|
# previously.
|
||||||
|
#
|
||||||
|
# This also work for Enums (if you use `is` to compare) and Literals.
|
||||||
|
def assert_never(value: "NoReturn") -> "NoReturn":
|
||||||
|
assert False, "Unhandled value: {} ({})".format(value, type(value).__name__)
|
||||||
|
|
|
@ -41,6 +41,7 @@ from _pytest.compat import importlib_metadata
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import Skipped
|
from _pytest.outcomes import Skipped
|
||||||
|
from _pytest.pathlib import import_path
|
||||||
from _pytest.pathlib import Path
|
from _pytest.pathlib import Path
|
||||||
from _pytest.store import Store
|
from _pytest.store import Store
|
||||||
from _pytest.warning_types import PytestConfigWarning
|
from _pytest.warning_types import PytestConfigWarning
|
||||||
|
@ -98,6 +99,15 @@ class ConftestImportFailure(Exception):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_traceback_for_conftest_import_failure(entry) -> bool:
|
||||||
|
"""filters tracebacks entries which point to pytest internals or importlib.
|
||||||
|
|
||||||
|
Make a special case for importlib because we use it to import test modules and conftest files
|
||||||
|
in _pytest.pathlib.import_path.
|
||||||
|
"""
|
||||||
|
return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep)
|
||||||
|
|
||||||
|
|
||||||
def main(args=None, plugins=None) -> Union[int, ExitCode]:
|
def main(args=None, plugins=None) -> Union[int, ExitCode]:
|
||||||
""" return exit code, after performing an in-process test run.
|
""" return exit code, after performing an in-process test run.
|
||||||
|
|
||||||
|
@ -115,7 +125,9 @@ def main(args=None, plugins=None) -> Union[int, ExitCode]:
|
||||||
tw.line(
|
tw.line(
|
||||||
"ImportError while loading conftest '{e.path}'.".format(e=e), red=True
|
"ImportError while loading conftest '{e.path}'.".format(e=e), red=True
|
||||||
)
|
)
|
||||||
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
exc_info.traceback = exc_info.traceback.filter(
|
||||||
|
filter_traceback_for_conftest_import_failure
|
||||||
|
)
|
||||||
exc_repr = (
|
exc_repr = (
|
||||||
exc_info.getrepr(style="short", chain=False)
|
exc_info.getrepr(style="short", chain=False)
|
||||||
if exc_info.traceback
|
if exc_info.traceback
|
||||||
|
@ -450,21 +462,21 @@ class PytestPluginManager(PluginManager):
|
||||||
path = path[:i]
|
path = path[:i]
|
||||||
anchor = current.join(path, abs=1)
|
anchor = current.join(path, abs=1)
|
||||||
if anchor.exists(): # we found some file object
|
if anchor.exists(): # we found some file object
|
||||||
self._try_load_conftest(anchor)
|
self._try_load_conftest(anchor, namespace.importmode)
|
||||||
foundanchor = True
|
foundanchor = True
|
||||||
if not foundanchor:
|
if not foundanchor:
|
||||||
self._try_load_conftest(current)
|
self._try_load_conftest(current, namespace.importmode)
|
||||||
|
|
||||||
def _try_load_conftest(self, anchor):
|
def _try_load_conftest(self, anchor, importmode):
|
||||||
self._getconftestmodules(anchor)
|
self._getconftestmodules(anchor, importmode)
|
||||||
# let's also consider test* subdirs
|
# let's also consider test* subdirs
|
||||||
if anchor.check(dir=1):
|
if anchor.check(dir=1):
|
||||||
for x in anchor.listdir("test*"):
|
for x in anchor.listdir("test*"):
|
||||||
if x.check(dir=1):
|
if x.check(dir=1):
|
||||||
self._getconftestmodules(x)
|
self._getconftestmodules(x, importmode)
|
||||||
|
|
||||||
@lru_cache(maxsize=128)
|
@lru_cache(maxsize=128)
|
||||||
def _getconftestmodules(self, path):
|
def _getconftestmodules(self, path, importmode):
|
||||||
if self._noconftest:
|
if self._noconftest:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -482,13 +494,13 @@ class PytestPluginManager(PluginManager):
|
||||||
continue
|
continue
|
||||||
conftestpath = parent.join("conftest.py")
|
conftestpath = parent.join("conftest.py")
|
||||||
if conftestpath.isfile():
|
if conftestpath.isfile():
|
||||||
mod = self._importconftest(conftestpath)
|
mod = self._importconftest(conftestpath, importmode)
|
||||||
clist.append(mod)
|
clist.append(mod)
|
||||||
self._dirpath2confmods[directory] = clist
|
self._dirpath2confmods[directory] = clist
|
||||||
return clist
|
return clist
|
||||||
|
|
||||||
def _rget_with_confmod(self, name, path):
|
def _rget_with_confmod(self, name, path, importmode):
|
||||||
modules = self._getconftestmodules(path)
|
modules = self._getconftestmodules(path, importmode)
|
||||||
for mod in reversed(modules):
|
for mod in reversed(modules):
|
||||||
try:
|
try:
|
||||||
return mod, getattr(mod, name)
|
return mod, getattr(mod, name)
|
||||||
|
@ -496,7 +508,7 @@ class PytestPluginManager(PluginManager):
|
||||||
continue
|
continue
|
||||||
raise KeyError(name)
|
raise KeyError(name)
|
||||||
|
|
||||||
def _importconftest(self, conftestpath):
|
def _importconftest(self, conftestpath, importmode):
|
||||||
# Use a resolved Path object as key to avoid loading the same conftest twice
|
# Use a resolved Path object as key to avoid loading the same conftest twice
|
||||||
# with build systems that create build directories containing
|
# with build systems that create build directories containing
|
||||||
# symlinks to actual files.
|
# symlinks to actual files.
|
||||||
|
@ -512,7 +524,7 @@ class PytestPluginManager(PluginManager):
|
||||||
_ensure_removed_sysmodule(conftestpath.purebasename)
|
_ensure_removed_sysmodule(conftestpath.purebasename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mod = conftestpath.pyimport()
|
mod = import_path(conftestpath, mode=importmode)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ConftestImportFailure(conftestpath, sys.exc_info()) from e
|
raise ConftestImportFailure(conftestpath, sys.exc_info()) from e
|
||||||
|
|
||||||
|
@ -1213,7 +1225,9 @@ class Config:
|
||||||
|
|
||||||
def _getconftest_pathlist(self, name, path):
|
def _getconftest_pathlist(self, name, path):
|
||||||
try:
|
try:
|
||||||
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
|
mod, relroots = self.pluginmanager._rget_with_confmod(
|
||||||
|
name, path, self.getoption("importmode")
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
modpath = py.path.local(mod.__file__).dirpath()
|
modpath = py.path.local(mod.__file__).dirpath()
|
||||||
|
|
|
@ -33,6 +33,7 @@ from _pytest.config import Config
|
||||||
from _pytest.config.argparsing import Parser
|
from _pytest.config.argparsing import Parser
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.outcomes import OutcomeException
|
from _pytest.outcomes import OutcomeException
|
||||||
|
from _pytest.pathlib import import_path
|
||||||
from _pytest.python_api import approx
|
from _pytest.python_api import approx
|
||||||
from _pytest.warning_types import PytestWarning
|
from _pytest.warning_types import PytestWarning
|
||||||
|
|
||||||
|
@ -530,10 +531,12 @@ class DoctestModule(pytest.Module):
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.fspath.basename == "conftest.py":
|
if self.fspath.basename == "conftest.py":
|
||||||
module = self.config.pluginmanager._importconftest(self.fspath)
|
module = self.config.pluginmanager._importconftest(
|
||||||
|
self.fspath, self.config.getoption("importmode")
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
module = self.fspath.pyimport()
|
module = import_path(self.fspath)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
if self.config.getvalue("doctest_ignore_import_errors"):
|
if self.config.getvalue("doctest_ignore_import_errors"):
|
||||||
pytest.skip("unable to import module %r" % self.fspath)
|
pytest.skip("unable to import module %r" % self.fspath)
|
||||||
|
|
|
@ -173,6 +173,14 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
default=False,
|
default=False,
|
||||||
help="Don't ignore tests in a local virtualenv directory",
|
help="Don't ignore tests in a local virtualenv directory",
|
||||||
)
|
)
|
||||||
|
group.addoption(
|
||||||
|
"--import-mode",
|
||||||
|
default="prepend",
|
||||||
|
choices=["prepend", "append", "importlib"],
|
||||||
|
dest="importmode",
|
||||||
|
help="prepend/append to sys.path when importing test modules and conftest files, "
|
||||||
|
"default is to prepend.",
|
||||||
|
)
|
||||||
|
|
||||||
group = parser.getgroup("debugconfig", "test session debugging and configuration")
|
group = parser.getgroup("debugconfig", "test session debugging and configuration")
|
||||||
group.addoption(
|
group.addoption(
|
||||||
|
|
|
@ -547,7 +547,9 @@ class FSCollector(Collector):
|
||||||
# check if we have the common case of running
|
# check if we have the common case of running
|
||||||
# hooks with all conftest.py files
|
# hooks with all conftest.py files
|
||||||
pm = self.config.pluginmanager
|
pm = self.config.pluginmanager
|
||||||
my_conftestmodules = pm._getconftestmodules(fspath)
|
my_conftestmodules = pm._getconftestmodules(
|
||||||
|
fspath, self.config.getoption("importmode")
|
||||||
|
)
|
||||||
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
|
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
|
||||||
if remove_mods:
|
if remove_mods:
|
||||||
# one or more conftests are not in use at this fspath
|
# one or more conftests are not in use at this fspath
|
||||||
|
|
|
@ -1,24 +1,31 @@
|
||||||
import atexit
|
import atexit
|
||||||
import contextlib
|
import contextlib
|
||||||
import fnmatch
|
import fnmatch
|
||||||
|
import importlib.util
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
import warnings
|
import warnings
|
||||||
|
from enum import Enum
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from os.path import expanduser
|
from os.path import expanduser
|
||||||
from os.path import expandvars
|
from os.path import expandvars
|
||||||
from os.path import isabs
|
from os.path import isabs
|
||||||
from os.path import sep
|
from os.path import sep
|
||||||
from posixpath import sep as posix_sep
|
from posixpath import sep as posix_sep
|
||||||
|
from types import ModuleType
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
from typing import Optional
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
from _pytest.compat import assert_never
|
||||||
from _pytest.outcomes import skip
|
from _pytest.outcomes import skip
|
||||||
from _pytest.warning_types import PytestWarning
|
from _pytest.warning_types import PytestWarning
|
||||||
|
|
||||||
|
@ -413,3 +420,134 @@ def symlink_or_skip(src, dst, **kwargs):
|
||||||
os.symlink(str(src), str(dst), **kwargs)
|
os.symlink(str(src), str(dst), **kwargs)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
skip("symlinks not supported: {}".format(e))
|
skip("symlinks not supported: {}".format(e))
|
||||||
|
|
||||||
|
|
||||||
|
class ImportMode(Enum):
|
||||||
|
"""Possible values for `mode` parameter of `import_path`"""
|
||||||
|
|
||||||
|
prepend = "prepend"
|
||||||
|
append = "append"
|
||||||
|
importlib = "importlib"
|
||||||
|
|
||||||
|
|
||||||
|
class ImportPathMismatchError(ImportError):
|
||||||
|
"""Raised on import_path() if there is a mismatch of __file__'s.
|
||||||
|
|
||||||
|
This can happen when `import_path` is called multiple times with different filenames that has
|
||||||
|
the same basename but reside in packages
|
||||||
|
(for example "/tests1/test_foo.py" and "/tests2/test_foo.py").
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def import_path(
|
||||||
|
p: Union[str, py.path.local, Path],
|
||||||
|
*,
|
||||||
|
mode: Union[str, ImportMode] = ImportMode.prepend
|
||||||
|
) -> ModuleType:
|
||||||
|
"""
|
||||||
|
Imports and returns a module from the given path, which can be a file (a module) or
|
||||||
|
a directory (a package).
|
||||||
|
|
||||||
|
The import mechanism used is controlled by the `mode` parameter:
|
||||||
|
|
||||||
|
* `mode == ImportMode.prepend`: the directory containing the module (or package, taking
|
||||||
|
`__init__.py` files into account) will be put at the *start* of `sys.path` before
|
||||||
|
being imported with `__import__.
|
||||||
|
|
||||||
|
* `mode == ImportMode.append`: same as `prepend`, but the directory will be appended
|
||||||
|
to the end of `sys.path`, if not already in `sys.path`.
|
||||||
|
|
||||||
|
* `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib`
|
||||||
|
to import the module, which avoids having to use `__import__` and muck with `sys.path`
|
||||||
|
at all. It effectively allows having same-named test modules in different places.
|
||||||
|
|
||||||
|
:raise ImportPathMismatchError: if after importing the given `path` and the module `__file__`
|
||||||
|
are different. Only raised in `prepend` and `append` modes.
|
||||||
|
"""
|
||||||
|
mode = ImportMode(mode)
|
||||||
|
|
||||||
|
path = Path(p)
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
raise ImportError(path)
|
||||||
|
|
||||||
|
if mode is ImportMode.importlib:
|
||||||
|
module_name = path.stem
|
||||||
|
|
||||||
|
for meta_importer in sys.meta_path:
|
||||||
|
spec = meta_importer.find_spec(module_name, [str(path.parent)])
|
||||||
|
if spec is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
spec = importlib.util.spec_from_file_location(module_name, str(path))
|
||||||
|
|
||||||
|
if spec is None:
|
||||||
|
raise ImportError(
|
||||||
|
"Can't find module {} at location {}".format(module_name, str(path))
|
||||||
|
)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
||||||
|
return mod
|
||||||
|
|
||||||
|
pkg_path = resolve_package_path(path)
|
||||||
|
if pkg_path is not None:
|
||||||
|
pkg_root = pkg_path.parent
|
||||||
|
names = list(path.with_suffix("").relative_to(pkg_root).parts)
|
||||||
|
if names[-1] == "__init__":
|
||||||
|
names.pop()
|
||||||
|
module_name = ".".join(names)
|
||||||
|
else:
|
||||||
|
pkg_root = path.parent
|
||||||
|
module_name = path.stem
|
||||||
|
|
||||||
|
# change sys.path permanently: restoring it at the end of this function would cause surprising
|
||||||
|
# problems because of delayed imports: for example, a conftest.py file imported by this function
|
||||||
|
# might have local imports, which would fail at runtime if we restored sys.path.
|
||||||
|
if mode is ImportMode.append:
|
||||||
|
if str(pkg_root) not in sys.path:
|
||||||
|
sys.path.append(str(pkg_root))
|
||||||
|
elif mode is ImportMode.prepend:
|
||||||
|
if str(pkg_root) != sys.path[0]:
|
||||||
|
sys.path.insert(0, str(pkg_root))
|
||||||
|
else:
|
||||||
|
assert_never(mode)
|
||||||
|
|
||||||
|
importlib.import_module(module_name)
|
||||||
|
|
||||||
|
mod = sys.modules[module_name]
|
||||||
|
if path.name == "__init__.py":
|
||||||
|
return mod
|
||||||
|
|
||||||
|
ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "")
|
||||||
|
if ignore != "1":
|
||||||
|
module_file = mod.__file__
|
||||||
|
if module_file.endswith((".pyc", ".pyo")):
|
||||||
|
module_file = module_file[:-1]
|
||||||
|
if module_file.endswith(os.path.sep + "__init__.py"):
|
||||||
|
module_file = module_file[: -(len(os.path.sep + "__init__.py"))]
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_same = os.path.samefile(str(path), module_file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
is_same = False
|
||||||
|
|
||||||
|
if not is_same:
|
||||||
|
raise ImportPathMismatchError(module_name, module_file, path)
|
||||||
|
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_package_path(path: Path) -> Optional[Path]:
|
||||||
|
"""Return the Python package path by looking for the last
|
||||||
|
directory upwards which still contains an __init__.py.
|
||||||
|
Return None if it can not be determined.
|
||||||
|
"""
|
||||||
|
result = None
|
||||||
|
for parent in itertools.chain((path,), path.parents):
|
||||||
|
if parent.is_dir():
|
||||||
|
if not parent.joinpath("__init__.py").is_file():
|
||||||
|
break
|
||||||
|
if not parent.name.isidentifier():
|
||||||
|
break
|
||||||
|
result = parent
|
||||||
|
return result
|
||||||
|
|
|
@ -59,6 +59,8 @@ from _pytest.mark.structures import MarkDecorator
|
||||||
from _pytest.mark.structures import normalize_mark_list
|
from _pytest.mark.structures import normalize_mark_list
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import skip
|
from _pytest.outcomes import skip
|
||||||
|
from _pytest.pathlib import import_path
|
||||||
|
from _pytest.pathlib import ImportPathMismatchError
|
||||||
from _pytest.pathlib import parts
|
from _pytest.pathlib import parts
|
||||||
from _pytest.reports import TerminalRepr
|
from _pytest.reports import TerminalRepr
|
||||||
from _pytest.warning_types import PytestCollectionWarning
|
from _pytest.warning_types import PytestCollectionWarning
|
||||||
|
@ -115,15 +117,6 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
"side effects(use at your own risk)",
|
"side effects(use at your own risk)",
|
||||||
)
|
)
|
||||||
|
|
||||||
group.addoption(
|
|
||||||
"--import-mode",
|
|
||||||
default="prepend",
|
|
||||||
choices=["prepend", "append"],
|
|
||||||
dest="importmode",
|
|
||||||
help="prepend/append to sys.path when importing test modules, "
|
|
||||||
"default is to prepend.",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||||
if config.option.showfixtures:
|
if config.option.showfixtures:
|
||||||
|
@ -557,10 +550,10 @@ class Module(nodes.File, PyCollector):
|
||||||
# we assume we are only called once per module
|
# we assume we are only called once per module
|
||||||
importmode = self.config.getoption("--import-mode")
|
importmode = self.config.getoption("--import-mode")
|
||||||
try:
|
try:
|
||||||
mod = self.fspath.pyimport(ensuresyspath=importmode)
|
mod = import_path(self.fspath, mode=importmode)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
raise self.CollectError(ExceptionInfo.from_current().getrepr(style="short"))
|
raise self.CollectError(ExceptionInfo.from_current().getrepr(style="short"))
|
||||||
except self.fspath.ImportMismatchError as e:
|
except ImportPathMismatchError as e:
|
||||||
raise self.CollectError(
|
raise self.CollectError(
|
||||||
"import file mismatch:\n"
|
"import file mismatch:\n"
|
||||||
"imported module %r has this __file__ attribute:\n"
|
"imported module %r has this __file__ attribute:\n"
|
||||||
|
|
|
@ -147,7 +147,8 @@ class TestGeneralUsage:
|
||||||
else:
|
else:
|
||||||
assert loaded == ["myplugin1", "myplugin2", "mycov"]
|
assert loaded == ["myplugin1", "myplugin2", "mycov"]
|
||||||
|
|
||||||
def test_assertion_magic(self, testdir):
|
@pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"])
|
||||||
|
def test_assertion_rewrite(self, testdir, import_mode):
|
||||||
p = testdir.makepyfile(
|
p = testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
def test_this():
|
def test_this():
|
||||||
|
@ -155,7 +156,7 @@ class TestGeneralUsage:
|
||||||
assert x
|
assert x
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = testdir.runpytest(p)
|
result = testdir.runpytest(p, "--import-mode={}".format(import_mode))
|
||||||
result.stdout.fnmatch_lines(["> assert x", "E assert 0"])
|
result.stdout.fnmatch_lines(["> assert x", "E assert 0"])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
@ -109,11 +108,10 @@ class TestModule:
|
||||||
assert result.ret == 2
|
assert result.ret == 2
|
||||||
|
|
||||||
stdout = result.stdout.str()
|
stdout = result.stdout.str()
|
||||||
for name in ("_pytest", os.path.join("py", "_path")):
|
if verbose == 2:
|
||||||
if verbose == 2:
|
assert "_pytest" in stdout
|
||||||
assert name in stdout
|
else:
|
||||||
else:
|
assert "_pytest" not in stdout
|
||||||
assert name not in stdout
|
|
||||||
|
|
||||||
def test_show_traceback_import_error_unicode(self, testdir):
|
def test_show_traceback_import_error_unicode(self, testdir):
|
||||||
"""Check test modules collected which raise ImportError with unicode messages
|
"""Check test modules collected which raise ImportError with unicode messages
|
||||||
|
|
|
@ -1894,7 +1894,9 @@ class TestAutouseManagement:
|
||||||
reprec = testdir.inline_run("-v", "-s", confcut)
|
reprec = testdir.inline_run("-v", "-s", confcut)
|
||||||
reprec.assertoutcome(passed=8)
|
reprec.assertoutcome(passed=8)
|
||||||
config = reprec.getcalls("pytest_unconfigure")[0].config
|
config = reprec.getcalls("pytest_unconfigure")[0].config
|
||||||
values = config.pluginmanager._getconftestmodules(p)[0].values
|
values = config.pluginmanager._getconftestmodules(p, importmode="prepend")[
|
||||||
|
0
|
||||||
|
].values
|
||||||
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
|
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
|
||||||
|
|
||||||
def test_scope_ordering(self, testdir):
|
def test_scope_ordering(self, testdir):
|
||||||
|
|
|
@ -1342,3 +1342,83 @@ def test_fscollector_from_parent(tmpdir, request):
|
||||||
parent=request.session, fspath=tmpdir / "foo", x=10
|
parent=request.session, fspath=tmpdir / "foo", x=10
|
||||||
)
|
)
|
||||||
assert collector.x == 10
|
assert collector.x == 10
|
||||||
|
|
||||||
|
|
||||||
|
class TestImportModeImportlib:
|
||||||
|
def test_collect_duplicate_names(self, testdir):
|
||||||
|
"""--import-mode=importlib can import modules with same names that are not in packages."""
|
||||||
|
testdir.makepyfile(
|
||||||
|
**{
|
||||||
|
"tests_a/test_foo.py": "def test_foo1(): pass",
|
||||||
|
"tests_b/test_foo.py": "def test_foo2(): pass",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("-v", "--import-mode=importlib")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"tests_a/test_foo.py::test_foo1 *",
|
||||||
|
"tests_b/test_foo.py::test_foo2 *",
|
||||||
|
"* 2 passed in *",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_conftest(self, testdir):
|
||||||
|
"""Directory containing conftest modules are not put in sys.path as a side-effect of
|
||||||
|
importing them."""
|
||||||
|
tests_dir = testdir.tmpdir.join("tests")
|
||||||
|
testdir.makepyfile(
|
||||||
|
**{
|
||||||
|
"tests/conftest.py": "",
|
||||||
|
"tests/test_foo.py": """
|
||||||
|
import sys
|
||||||
|
def test_check():
|
||||||
|
assert r"{tests_dir}" not in sys.path
|
||||||
|
""".format(
|
||||||
|
tests_dir=tests_dir
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("-v", "--import-mode=importlib")
|
||||||
|
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
||||||
|
|
||||||
|
def setup_conftest_and_foo(self, testdir):
|
||||||
|
"""Setup a tests folder to be used to test if modules in that folder can be imported
|
||||||
|
due to side-effects of --import-mode or not."""
|
||||||
|
testdir.makepyfile(
|
||||||
|
**{
|
||||||
|
"tests/conftest.py": "",
|
||||||
|
"tests/foo.py": """
|
||||||
|
def foo(): return 42
|
||||||
|
""",
|
||||||
|
"tests/test_foo.py": """
|
||||||
|
def test_check():
|
||||||
|
from foo import foo
|
||||||
|
assert foo() == 42
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_modules_importable_as_side_effect(self, testdir):
|
||||||
|
"""In import-modes `prepend` and `append`, we are able to import modules from folders
|
||||||
|
containing conftest.py files due to the side effect of changing sys.path."""
|
||||||
|
self.setup_conftest_and_foo(testdir)
|
||||||
|
result = testdir.runpytest("-v", "--import-mode=prepend")
|
||||||
|
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
||||||
|
|
||||||
|
def test_modules_not_importable_as_side_effect(self, testdir):
|
||||||
|
"""In import-mode `importlib`, modules in folders containing conftest.py are not
|
||||||
|
importable, as don't change sys.path or sys.modules as side effect of importing
|
||||||
|
the conftest.py file.
|
||||||
|
"""
|
||||||
|
self.setup_conftest_and_foo(testdir)
|
||||||
|
result = testdir.runpytest("-v", "--import-mode=importlib")
|
||||||
|
exc_name = (
|
||||||
|
"ModuleNotFoundError" if sys.version_info[:2] > (3, 5) else "ImportError"
|
||||||
|
)
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*{}: No module named 'foo'".format(exc_name),
|
||||||
|
"tests?test_foo.py:2: {}".format(exc_name),
|
||||||
|
"* 1 failed in *",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
|
import enum
|
||||||
import sys
|
import sys
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import _PytestWrapper
|
from _pytest.compat import _PytestWrapper
|
||||||
|
from _pytest.compat import assert_never
|
||||||
from _pytest.compat import cached_property
|
from _pytest.compat import cached_property
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
from _pytest.compat import safe_isclass
|
from _pytest.compat import safe_isclass
|
||||||
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.outcomes import OutcomeException
|
from _pytest.outcomes import OutcomeException
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Literal
|
||||||
|
|
||||||
|
|
||||||
def test_is_generator():
|
def test_is_generator():
|
||||||
def zap():
|
def zap():
|
||||||
|
@ -205,3 +212,55 @@ def test_cached_property() -> None:
|
||||||
assert ncalls == 1
|
assert ncalls == 1
|
||||||
assert c2.prop == 2
|
assert c2.prop == 2
|
||||||
assert c1.prop == 1
|
assert c1.prop == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_assert_never_union() -> None:
|
||||||
|
x = 10 # type: Union[int, str]
|
||||||
|
|
||||||
|
if isinstance(x, int):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert_never(x) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
if isinstance(x, int):
|
||||||
|
pass
|
||||||
|
elif isinstance(x, str):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert_never(x)
|
||||||
|
|
||||||
|
|
||||||
|
def test_assert_never_enum() -> None:
|
||||||
|
E = enum.Enum("E", "a b")
|
||||||
|
x = E.a # type: E
|
||||||
|
|
||||||
|
if x is E.a:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert_never(x) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
if x is E.a:
|
||||||
|
pass
|
||||||
|
elif x is E.b:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert_never(x)
|
||||||
|
|
||||||
|
|
||||||
|
def test_assert_never_literal() -> None:
|
||||||
|
x = "a" # type: Literal["a", "b"]
|
||||||
|
|
||||||
|
if x == "a":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert_never(x) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
if x == "a":
|
||||||
|
pass
|
||||||
|
elif x == "b":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert_never(x)
|
||||||
|
|
|
@ -23,6 +23,7 @@ def conftest_setinitial(conftest, args, confcutdir=None):
|
||||||
self.confcutdir = str(confcutdir)
|
self.confcutdir = str(confcutdir)
|
||||||
self.noconftest = False
|
self.noconftest = False
|
||||||
self.pyargs = False
|
self.pyargs = False
|
||||||
|
self.importmode = "prepend"
|
||||||
|
|
||||||
conftest._set_initial_conftests(Namespace())
|
conftest._set_initial_conftests(Namespace())
|
||||||
|
|
||||||
|
@ -43,35 +44,38 @@ class TestConftestValueAccessGlobal:
|
||||||
def test_basic_init(self, basedir):
|
def test_basic_init(self, basedir):
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
p = basedir.join("adir")
|
p = basedir.join("adir")
|
||||||
assert conftest._rget_with_confmod("a", p)[1] == 1
|
assert conftest._rget_with_confmod("a", p, importmode="prepend")[1] == 1
|
||||||
|
|
||||||
def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
|
def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
assert not len(conftest._dirpath2confmods)
|
assert not len(conftest._dirpath2confmods)
|
||||||
conftest._getconftestmodules(basedir)
|
conftest._getconftestmodules(basedir, importmode="prepend")
|
||||||
snap1 = len(conftest._dirpath2confmods)
|
snap1 = len(conftest._dirpath2confmods)
|
||||||
assert snap1 == 1
|
assert snap1 == 1
|
||||||
conftest._getconftestmodules(basedir.join("adir"))
|
conftest._getconftestmodules(basedir.join("adir"), importmode="prepend")
|
||||||
assert len(conftest._dirpath2confmods) == snap1 + 1
|
assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||||
conftest._getconftestmodules(basedir.join("b"))
|
conftest._getconftestmodules(basedir.join("b"), importmode="prepend")
|
||||||
assert len(conftest._dirpath2confmods) == snap1 + 2
|
assert len(conftest._dirpath2confmods) == snap1 + 2
|
||||||
|
|
||||||
def test_value_access_not_existing(self, basedir):
|
def test_value_access_not_existing(self, basedir):
|
||||||
conftest = ConftestWithSetinitial(basedir)
|
conftest = ConftestWithSetinitial(basedir)
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
conftest._rget_with_confmod("a", basedir)
|
conftest._rget_with_confmod("a", basedir, importmode="prepend")
|
||||||
|
|
||||||
def test_value_access_by_path(self, basedir):
|
def test_value_access_by_path(self, basedir):
|
||||||
conftest = ConftestWithSetinitial(basedir)
|
conftest = ConftestWithSetinitial(basedir)
|
||||||
adir = basedir.join("adir")
|
adir = basedir.join("adir")
|
||||||
assert conftest._rget_with_confmod("a", adir)[1] == 1
|
assert conftest._rget_with_confmod("a", adir, importmode="prepend")[1] == 1
|
||||||
assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5
|
assert (
|
||||||
|
conftest._rget_with_confmod("a", adir.join("b"), importmode="prepend")[1]
|
||||||
|
== 1.5
|
||||||
|
)
|
||||||
|
|
||||||
def test_value_access_with_confmod(self, basedir):
|
def test_value_access_with_confmod(self, basedir):
|
||||||
startdir = basedir.join("adir", "b")
|
startdir = basedir.join("adir", "b")
|
||||||
startdir.ensure("xx", dir=True)
|
startdir.ensure("xx", dir=True)
|
||||||
conftest = ConftestWithSetinitial(startdir)
|
conftest = ConftestWithSetinitial(startdir)
|
||||||
mod, value = conftest._rget_with_confmod("a", startdir)
|
mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend")
|
||||||
assert value == 1.5
|
assert value == 1.5
|
||||||
path = py.path.local(mod.__file__)
|
path = py.path.local(mod.__file__)
|
||||||
assert path.dirpath() == basedir.join("adir", "b")
|
assert path.dirpath() == basedir.join("adir", "b")
|
||||||
|
@ -91,7 +95,7 @@ def test_doubledash_considered(testdir):
|
||||||
conf.ensure("conftest.py")
|
conf.ensure("conftest.py")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [conf.basename, conf.basename])
|
conftest_setinitial(conftest, [conf.basename, conf.basename])
|
||||||
values = conftest._getconftestmodules(conf)
|
values = conftest._getconftestmodules(conf, importmode="prepend")
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,13 +118,13 @@ def test_conftest_global_import(testdir):
|
||||||
import py, pytest
|
import py, pytest
|
||||||
from _pytest.config import PytestPluginManager
|
from _pytest.config import PytestPluginManager
|
||||||
conf = PytestPluginManager()
|
conf = PytestPluginManager()
|
||||||
mod = conf._importconftest(py.path.local("conftest.py"))
|
mod = conf._importconftest(py.path.local("conftest.py"), importmode="prepend")
|
||||||
assert mod.x == 3
|
assert mod.x == 3
|
||||||
import conftest
|
import conftest
|
||||||
assert conftest is mod, (conftest, mod)
|
assert conftest is mod, (conftest, mod)
|
||||||
subconf = py.path.local().ensure("sub", "conftest.py")
|
subconf = py.path.local().ensure("sub", "conftest.py")
|
||||||
subconf.write("y=4")
|
subconf.write("y=4")
|
||||||
mod2 = conf._importconftest(subconf)
|
mod2 = conf._importconftest(subconf, importmode="prepend")
|
||||||
assert mod != mod2
|
assert mod != mod2
|
||||||
assert mod2.y == 4
|
assert mod2.y == 4
|
||||||
import conftest
|
import conftest
|
||||||
|
@ -136,17 +140,17 @@ def test_conftestcutdir(testdir):
|
||||||
p = testdir.mkdir("x")
|
p = testdir.mkdir("x")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
|
conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p)
|
||||||
values = conftest._getconftestmodules(p)
|
values = conftest._getconftestmodules(p, importmode="prepend")
|
||||||
assert len(values) == 0
|
assert len(values) == 0
|
||||||
values = conftest._getconftestmodules(conf.dirpath())
|
values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend")
|
||||||
assert len(values) == 0
|
assert len(values) == 0
|
||||||
assert conf not in conftest._conftestpath2mod
|
assert conf not in conftest._conftestpath2mod
|
||||||
# but we can still import a conftest directly
|
# but we can still import a conftest directly
|
||||||
conftest._importconftest(conf)
|
conftest._importconftest(conf, importmode="prepend")
|
||||||
values = conftest._getconftestmodules(conf.dirpath())
|
values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend")
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
# and all sub paths get updated properly
|
# and all sub paths get updated properly
|
||||||
values = conftest._getconftestmodules(p)
|
values = conftest._getconftestmodules(p, importmode="prepend")
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
|
|
||||||
|
@ -155,7 +159,7 @@ def test_conftestcutdir_inplace_considered(testdir):
|
||||||
conf = testdir.makeconftest("")
|
conf = testdir.makeconftest("")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
|
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
|
||||||
values = conftest._getconftestmodules(conf.dirpath())
|
values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend")
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
|
|
||||||
|
@ -340,13 +344,13 @@ def test_conftest_import_order(testdir, monkeypatch):
|
||||||
ct2 = sub.join("conftest.py")
|
ct2 = sub.join("conftest.py")
|
||||||
ct2.write("")
|
ct2.write("")
|
||||||
|
|
||||||
def impct(p):
|
def impct(p, importmode):
|
||||||
return p
|
return p
|
||||||
|
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest._confcutdir = testdir.tmpdir
|
conftest._confcutdir = testdir.tmpdir
|
||||||
monkeypatch.setattr(conftest, "_importconftest", impct)
|
monkeypatch.setattr(conftest, "_importconftest", impct)
|
||||||
assert conftest._getconftestmodules(sub) == [ct1, ct2]
|
assert conftest._getconftestmodules(sub, importmode="prepend") == [ct1, ct2]
|
||||||
|
|
||||||
|
|
||||||
def test_fixture_dependency(testdir):
|
def test_fixture_dependency(testdir):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import unittest.mock
|
import unittest.mock
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
@ -9,11 +10,14 @@ from _pytest.pathlib import ensure_deletable
|
||||||
from _pytest.pathlib import fnmatch_ex
|
from _pytest.pathlib import fnmatch_ex
|
||||||
from _pytest.pathlib import get_extended_length_path_str
|
from _pytest.pathlib import get_extended_length_path_str
|
||||||
from _pytest.pathlib import get_lock_path
|
from _pytest.pathlib import get_lock_path
|
||||||
|
from _pytest.pathlib import import_path
|
||||||
|
from _pytest.pathlib import ImportPathMismatchError
|
||||||
from _pytest.pathlib import maybe_delete_a_numbered_dir
|
from _pytest.pathlib import maybe_delete_a_numbered_dir
|
||||||
from _pytest.pathlib import Path
|
from _pytest.pathlib import Path
|
||||||
|
from _pytest.pathlib import resolve_package_path
|
||||||
|
|
||||||
|
|
||||||
class TestPort:
|
class TestFNMatcherPort:
|
||||||
"""Test that our port of py.common.FNMatcher (fnmatch_ex) produces the same results as the
|
"""Test that our port of py.common.FNMatcher (fnmatch_ex) produces the same results as the
|
||||||
original py.path.local.fnmatch method.
|
original py.path.local.fnmatch method.
|
||||||
"""
|
"""
|
||||||
|
@ -79,6 +83,242 @@ class TestPort:
|
||||||
assert not match(pattern, path)
|
assert not match(pattern, path)
|
||||||
|
|
||||||
|
|
||||||
|
class TestImportPath:
|
||||||
|
"""
|
||||||
|
|
||||||
|
Most of the tests here were copied from py lib's tests for "py.local.path.pyimport".
|
||||||
|
|
||||||
|
Having our own pyimport-like function is inline with removing py.path dependency in the future.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.yield_fixture(scope="session")
|
||||||
|
def path1(self, tmpdir_factory):
|
||||||
|
path = tmpdir_factory.mktemp("path")
|
||||||
|
self.setuptestfs(path)
|
||||||
|
yield path
|
||||||
|
assert path.join("samplefile").check()
|
||||||
|
|
||||||
|
def setuptestfs(self, path):
|
||||||
|
# print "setting up test fs for", repr(path)
|
||||||
|
samplefile = path.ensure("samplefile")
|
||||||
|
samplefile.write("samplefile\n")
|
||||||
|
|
||||||
|
execfile = path.ensure("execfile")
|
||||||
|
execfile.write("x=42")
|
||||||
|
|
||||||
|
execfilepy = path.ensure("execfile.py")
|
||||||
|
execfilepy.write("x=42")
|
||||||
|
|
||||||
|
d = {1: 2, "hello": "world", "answer": 42}
|
||||||
|
path.ensure("samplepickle").dump(d)
|
||||||
|
|
||||||
|
sampledir = path.ensure("sampledir", dir=1)
|
||||||
|
sampledir.ensure("otherfile")
|
||||||
|
|
||||||
|
otherdir = path.ensure("otherdir", dir=1)
|
||||||
|
otherdir.ensure("__init__.py")
|
||||||
|
|
||||||
|
module_a = otherdir.ensure("a.py")
|
||||||
|
module_a.write("from .b import stuff as result\n")
|
||||||
|
module_b = otherdir.ensure("b.py")
|
||||||
|
module_b.write('stuff="got it"\n')
|
||||||
|
module_c = otherdir.ensure("c.py")
|
||||||
|
module_c.write(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
import py;
|
||||||
|
import otherdir.a
|
||||||
|
value = otherdir.a.result
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
module_d = otherdir.ensure("d.py")
|
||||||
|
module_d.write(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
import py;
|
||||||
|
from otherdir import a
|
||||||
|
value2 = a.result
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_smoke_test(self, path1):
|
||||||
|
obj = import_path(path1.join("execfile.py"))
|
||||||
|
assert obj.x == 42 # type: ignore[attr-defined]
|
||||||
|
assert obj.__name__ == "execfile"
|
||||||
|
|
||||||
|
def test_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch):
|
||||||
|
p = tmpdir.ensure("a", "test_x123.py")
|
||||||
|
import_path(p)
|
||||||
|
tmpdir.join("a").move(tmpdir.join("b"))
|
||||||
|
with pytest.raises(ImportPathMismatchError):
|
||||||
|
import_path(tmpdir.join("b", "test_x123.py"))
|
||||||
|
|
||||||
|
# Errors can be ignored.
|
||||||
|
monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1")
|
||||||
|
import_path(tmpdir.join("b", "test_x123.py"))
|
||||||
|
|
||||||
|
# PY_IGNORE_IMPORTMISMATCH=0 does not ignore error.
|
||||||
|
monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0")
|
||||||
|
with pytest.raises(ImportPathMismatchError):
|
||||||
|
import_path(tmpdir.join("b", "test_x123.py"))
|
||||||
|
|
||||||
|
def test_messy_name(self, tmpdir):
|
||||||
|
# http://bitbucket.org/hpk42/py-trunk/issue/129
|
||||||
|
path = tmpdir.ensure("foo__init__.py")
|
||||||
|
module = import_path(path)
|
||||||
|
assert module.__name__ == "foo__init__"
|
||||||
|
|
||||||
|
def test_dir(self, tmpdir):
|
||||||
|
p = tmpdir.join("hello_123")
|
||||||
|
p_init = p.ensure("__init__.py")
|
||||||
|
m = import_path(p)
|
||||||
|
assert m.__name__ == "hello_123"
|
||||||
|
m = import_path(p_init)
|
||||||
|
assert m.__name__ == "hello_123"
|
||||||
|
|
||||||
|
def test_a(self, path1):
|
||||||
|
otherdir = path1.join("otherdir")
|
||||||
|
mod = import_path(otherdir.join("a.py"))
|
||||||
|
assert mod.result == "got it" # type: ignore[attr-defined]
|
||||||
|
assert mod.__name__ == "otherdir.a"
|
||||||
|
|
||||||
|
def test_b(self, path1):
|
||||||
|
otherdir = path1.join("otherdir")
|
||||||
|
mod = import_path(otherdir.join("b.py"))
|
||||||
|
assert mod.stuff == "got it" # type: ignore[attr-defined]
|
||||||
|
assert mod.__name__ == "otherdir.b"
|
||||||
|
|
||||||
|
def test_c(self, path1):
|
||||||
|
otherdir = path1.join("otherdir")
|
||||||
|
mod = import_path(otherdir.join("c.py"))
|
||||||
|
assert mod.value == "got it" # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
def test_d(self, path1):
|
||||||
|
otherdir = path1.join("otherdir")
|
||||||
|
mod = import_path(otherdir.join("d.py"))
|
||||||
|
assert mod.value2 == "got it" # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
def test_import_after(self, tmpdir):
|
||||||
|
tmpdir.ensure("xxxpackage", "__init__.py")
|
||||||
|
mod1path = tmpdir.ensure("xxxpackage", "module1.py")
|
||||||
|
mod1 = import_path(mod1path)
|
||||||
|
assert mod1.__name__ == "xxxpackage.module1"
|
||||||
|
from xxxpackage import module1
|
||||||
|
|
||||||
|
assert module1 is mod1
|
||||||
|
|
||||||
|
def test_check_filepath_consistency(self, monkeypatch, tmpdir):
|
||||||
|
name = "pointsback123"
|
||||||
|
ModuleType = type(os)
|
||||||
|
p = tmpdir.ensure(name + ".py")
|
||||||
|
for ending in (".pyc", ".pyo"):
|
||||||
|
mod = ModuleType(name)
|
||||||
|
pseudopath = tmpdir.ensure(name + ending)
|
||||||
|
mod.__file__ = str(pseudopath)
|
||||||
|
monkeypatch.setitem(sys.modules, name, mod)
|
||||||
|
newmod = import_path(p)
|
||||||
|
assert mod == newmod
|
||||||
|
monkeypatch.undo()
|
||||||
|
mod = ModuleType(name)
|
||||||
|
pseudopath = tmpdir.ensure(name + "123.py")
|
||||||
|
mod.__file__ = str(pseudopath)
|
||||||
|
monkeypatch.setitem(sys.modules, name, mod)
|
||||||
|
with pytest.raises(ImportPathMismatchError) as excinfo:
|
||||||
|
import_path(p)
|
||||||
|
modname, modfile, orig = excinfo.value.args
|
||||||
|
assert modname == name
|
||||||
|
assert modfile == pseudopath
|
||||||
|
assert orig == p
|
||||||
|
assert issubclass(ImportPathMismatchError, ImportError)
|
||||||
|
|
||||||
|
def test_issue131_on__init__(self, tmpdir):
|
||||||
|
# __init__.py files may be namespace packages, and thus the
|
||||||
|
# __file__ of an imported module may not be ourselves
|
||||||
|
# see issue
|
||||||
|
p1 = tmpdir.ensure("proja", "__init__.py")
|
||||||
|
p2 = tmpdir.ensure("sub", "proja", "__init__.py")
|
||||||
|
m1 = import_path(p1)
|
||||||
|
m2 = import_path(p2)
|
||||||
|
assert m1 == m2
|
||||||
|
|
||||||
|
def test_ensuresyspath_append(self, tmpdir):
|
||||||
|
root1 = tmpdir.mkdir("root1")
|
||||||
|
file1 = root1.ensure("x123.py")
|
||||||
|
assert str(root1) not in sys.path
|
||||||
|
import_path(file1, mode="append")
|
||||||
|
assert str(root1) == sys.path[-1]
|
||||||
|
assert str(root1) not in sys.path[:-1]
|
||||||
|
|
||||||
|
def test_invalid_path(self, tmpdir):
|
||||||
|
with pytest.raises(ImportError):
|
||||||
|
import_path(tmpdir.join("invalid.py"))
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def simple_module(self, tmpdir):
|
||||||
|
fn = tmpdir.join("mymod.py")
|
||||||
|
fn.write(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
def foo(x): return 40 + x
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return fn
|
||||||
|
|
||||||
|
def test_importmode_importlib(self, simple_module):
|
||||||
|
"""importlib mode does not change sys.path"""
|
||||||
|
module = import_path(simple_module, mode="importlib")
|
||||||
|
assert module.foo(2) == 42 # type: ignore[attr-defined]
|
||||||
|
assert simple_module.dirname not in sys.path
|
||||||
|
|
||||||
|
def test_importmode_twice_is_different_module(self, simple_module):
|
||||||
|
"""importlib mode always returns a new module"""
|
||||||
|
module1 = import_path(simple_module, mode="importlib")
|
||||||
|
module2 = import_path(simple_module, mode="importlib")
|
||||||
|
assert module1 is not module2
|
||||||
|
|
||||||
|
def test_no_meta_path_found(self, simple_module, monkeypatch):
|
||||||
|
"""Even without any meta_path should still import module"""
|
||||||
|
monkeypatch.setattr(sys, "meta_path", [])
|
||||||
|
module = import_path(simple_module, mode="importlib")
|
||||||
|
assert module.foo(2) == 42 # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
# mode='importlib' fails if no spec is found to load the module
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
importlib.util, "spec_from_file_location", lambda *args: None
|
||||||
|
)
|
||||||
|
with pytest.raises(ImportError):
|
||||||
|
import_path(simple_module, mode="importlib")
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_package_path(tmp_path):
|
||||||
|
pkg = tmp_path / "pkg1"
|
||||||
|
pkg.mkdir()
|
||||||
|
(pkg / "__init__.py").touch()
|
||||||
|
(pkg / "subdir").mkdir()
|
||||||
|
(pkg / "subdir/__init__.py").touch()
|
||||||
|
assert resolve_package_path(pkg) == pkg
|
||||||
|
assert resolve_package_path(pkg.joinpath("subdir", "__init__.py")) == pkg
|
||||||
|
|
||||||
|
|
||||||
|
def test_package_unimportable(tmp_path):
|
||||||
|
pkg = tmp_path / "pkg1-1"
|
||||||
|
pkg.mkdir()
|
||||||
|
pkg.joinpath("__init__.py").touch()
|
||||||
|
subdir = pkg.joinpath("subdir")
|
||||||
|
subdir.mkdir()
|
||||||
|
pkg.joinpath("subdir/__init__.py").touch()
|
||||||
|
assert resolve_package_path(subdir) == subdir
|
||||||
|
xyz = subdir.joinpath("xyz.py")
|
||||||
|
xyz.touch()
|
||||||
|
assert resolve_package_path(xyz) == subdir
|
||||||
|
assert not resolve_package_path(pkg)
|
||||||
|
|
||||||
|
|
||||||
def test_access_denied_during_cleanup(tmp_path, monkeypatch):
|
def test_access_denied_during_cleanup(tmp_path, monkeypatch):
|
||||||
"""Ensure that deleting a numbered dir does not fail because of OSErrors (#4262)."""
|
"""Ensure that deleting a numbered dir does not fail because of OSErrors (#4262)."""
|
||||||
path = tmp_path / "temp-1"
|
path = tmp_path / "temp-1"
|
||||||
|
|
|
@ -37,7 +37,7 @@ class TestPytestPluginInteractions:
|
||||||
pm.hook.pytest_addhooks.call_historic(
|
pm.hook.pytest_addhooks.call_historic(
|
||||||
kwargs=dict(pluginmanager=config.pluginmanager)
|
kwargs=dict(pluginmanager=config.pluginmanager)
|
||||||
)
|
)
|
||||||
config.pluginmanager._importconftest(conf)
|
config.pluginmanager._importconftest(conf, importmode="prepend")
|
||||||
# print(config.pluginmanager.get_plugins())
|
# print(config.pluginmanager.get_plugins())
|
||||||
res = config.hook.pytest_myhook(xyz=10)
|
res = config.hook.pytest_myhook(xyz=10)
|
||||||
assert res == [11]
|
assert res == [11]
|
||||||
|
@ -64,7 +64,7 @@ class TestPytestPluginInteractions:
|
||||||
default=True)
|
default=True)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
config.pluginmanager._importconftest(p)
|
config.pluginmanager._importconftest(p, importmode="prepend")
|
||||||
assert config.option.test123
|
assert config.option.test123
|
||||||
|
|
||||||
def test_configure(self, testdir):
|
def test_configure(self, testdir):
|
||||||
|
@ -129,10 +129,10 @@ class TestPytestPluginInteractions:
|
||||||
conftest1 = testdir.tmpdir.join("tests/conftest.py")
|
conftest1 = testdir.tmpdir.join("tests/conftest.py")
|
||||||
conftest2 = testdir.tmpdir.join("tests/subdir/conftest.py")
|
conftest2 = testdir.tmpdir.join("tests/subdir/conftest.py")
|
||||||
|
|
||||||
config.pluginmanager._importconftest(conftest1)
|
config.pluginmanager._importconftest(conftest1, importmode="prepend")
|
||||||
ihook_a = session.gethookproxy(testdir.tmpdir.join("tests"))
|
ihook_a = session.gethookproxy(testdir.tmpdir.join("tests"))
|
||||||
assert ihook_a is not None
|
assert ihook_a is not None
|
||||||
config.pluginmanager._importconftest(conftest2)
|
config.pluginmanager._importconftest(conftest2, importmode="prepend")
|
||||||
ihook_b = session.gethookproxy(testdir.tmpdir.join("tests"))
|
ihook_b = session.gethookproxy(testdir.tmpdir.join("tests"))
|
||||||
assert ihook_a is not ihook_b
|
assert ihook_a is not ihook_b
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue