New pytester fixture (#7854)
This commit is contained in:
parent
cb578a918e
commit
69419cb700
|
@ -0,0 +1,5 @@
|
||||||
|
New :fixture:`pytester` fixture, which is identical to :fixture:`testdir` but its methods return :class:`pathlib.Path` when appropriate instead of ``py.path.local``.
|
||||||
|
|
||||||
|
This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future.
|
||||||
|
|
||||||
|
Internally, the old :class:`Testdir` is now a thin wrapper around :class:`Pytester`, preserving the old interface.
|
|
@ -499,17 +499,21 @@ monkeypatch
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
.. fixture:: testdir
|
.. fixture:: pytester
|
||||||
|
|
||||||
testdir
|
pytester
|
||||||
~~~~~~~
|
~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 6.2
|
||||||
|
|
||||||
.. currentmodule:: _pytest.pytester
|
.. currentmodule:: _pytest.pytester
|
||||||
|
|
||||||
This fixture provides a :class:`Testdir` instance useful for black-box testing of test files, making it ideal to
|
Provides a :class:`Pytester` instance that can be used to run and test pytest itself.
|
||||||
test plugins.
|
|
||||||
|
|
||||||
To use it, include in your top-most ``conftest.py`` file:
|
It provides an empty directory where pytest can be executed in isolation, and contains facilities
|
||||||
|
to write tests, configuration files, and match against expected output.
|
||||||
|
|
||||||
|
To use it, include in your topmost ``conftest.py`` file:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -517,7 +521,7 @@ To use it, include in your top-most ``conftest.py`` file:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. autoclass:: Testdir()
|
.. autoclass:: Pytester()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autoclass:: RunResult()
|
.. autoclass:: RunResult()
|
||||||
|
@ -526,6 +530,15 @@ To use it, include in your top-most ``conftest.py`` file:
|
||||||
.. autoclass:: LineMatcher()
|
.. autoclass:: LineMatcher()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. fixture:: testdir
|
||||||
|
|
||||||
|
testdir
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Identical to :fixture:`pytester`, but provides an instance whose methods return
|
||||||
|
legacy ``py.path.local`` objects instead when applicable.
|
||||||
|
|
||||||
|
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
|
||||||
|
|
||||||
.. fixture:: recwarn
|
.. fixture:: recwarn
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
"""(Disabled by default) support for testing pytest and pytest plugins."""
|
"""(Disabled by default) support for testing pytest and pytest plugins."""
|
||||||
import collections.abc
|
import collections.abc
|
||||||
|
import contextlib
|
||||||
import gc
|
import gc
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
@ -19,12 +22,14 @@ from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import overload
|
from typing import overload
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
from typing import TextIO
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from weakref import WeakKeyDictionary
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
import attr
|
||||||
import py
|
import py
|
||||||
from iniconfig import IniConfig
|
from iniconfig import IniConfig
|
||||||
|
|
||||||
|
@ -47,7 +52,7 @@ from _pytest.pathlib import make_numbered_dir
|
||||||
from _pytest.python import Module
|
from _pytest.python import Module
|
||||||
from _pytest.reports import CollectReport
|
from _pytest.reports import CollectReport
|
||||||
from _pytest.reports import TestReport
|
from _pytest.reports import TestReport
|
||||||
from _pytest.tmpdir import TempdirFactory
|
from _pytest.tmpdir import TempPathFactory
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
|
@ -176,11 +181,11 @@ def _pytest(request: FixtureRequest) -> "PytestArg":
|
||||||
|
|
||||||
class PytestArg:
|
class PytestArg:
|
||||||
def __init__(self, request: FixtureRequest) -> None:
|
def __init__(self, request: FixtureRequest) -> None:
|
||||||
self.request = request
|
self._request = request
|
||||||
|
|
||||||
def gethookrecorder(self, hook) -> "HookRecorder":
|
def gethookrecorder(self, hook) -> "HookRecorder":
|
||||||
hookrecorder = HookRecorder(hook._pm)
|
hookrecorder = HookRecorder(hook._pm)
|
||||||
self.request.addfinalizer(hookrecorder.finish_recording)
|
self._request.addfinalizer(hookrecorder.finish_recording)
|
||||||
return hookrecorder
|
return hookrecorder
|
||||||
|
|
||||||
|
|
||||||
|
@ -430,13 +435,29 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def testdir(request: FixtureRequest, tmpdir_factory: TempdirFactory) -> "Testdir":
|
def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester":
|
||||||
"""A :class: `TestDir` instance, that can be used to run and test pytest itself.
|
|
||||||
|
|
||||||
It is particularly useful for testing plugins. It is similar to the `tmpdir` fixture
|
|
||||||
but provides methods which aid in testing pytest itself.
|
|
||||||
"""
|
"""
|
||||||
return Testdir(request, tmpdir_factory)
|
Facilities to write tests/configuration files, execute pytest in isolation, and match
|
||||||
|
against expected output, perfect for black-box testing of pytest plugins.
|
||||||
|
|
||||||
|
It attempts to isolate the test run from external factors as much as possible, modifying
|
||||||
|
the current working directory to ``path`` and environment variables during initialization.
|
||||||
|
|
||||||
|
It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path`
|
||||||
|
fixture but provides methods which aid in testing pytest itself.
|
||||||
|
"""
|
||||||
|
return Pytester(request, tmp_path_factory)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def testdir(pytester: "Pytester") -> "Testdir":
|
||||||
|
"""
|
||||||
|
Identical to :fixture:`pytester`, and provides an instance whose methods return
|
||||||
|
legacy ``py.path.local`` objects instead when applicable.
|
||||||
|
|
||||||
|
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
|
||||||
|
"""
|
||||||
|
return Testdir(pytester)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -599,16 +620,17 @@ class SysPathsSnapshot:
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Testdir:
|
class Pytester:
|
||||||
"""Temporary test directory with tools to test/run pytest itself.
|
"""
|
||||||
|
Facilities to write tests/configuration files, execute pytest in isolation, and match
|
||||||
|
against expected output, perfect for black-box testing of pytest plugins.
|
||||||
|
|
||||||
This is based on the :fixture:`tmpdir` fixture but provides a number of methods
|
It attempts to isolate the test run from external factors as much as possible, modifying
|
||||||
which aid with testing pytest itself. Unless :py:meth:`chdir` is used all
|
the current working directory to ``path`` and environment variables during initialization.
|
||||||
methods will use :py:attr:`tmpdir` as their current working directory.
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
:ivar tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
|
:ivar Path path: temporary directory path used to create files/run tests from, etc.
|
||||||
|
|
||||||
:ivar plugins:
|
:ivar plugins:
|
||||||
A list of plugins to use with :py:meth:`parseconfig` and
|
A list of plugins to use with :py:meth:`parseconfig` and
|
||||||
|
@ -624,8 +646,10 @@ class Testdir:
|
||||||
class TimeoutExpired(Exception):
|
class TimeoutExpired(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, request: FixtureRequest, tmpdir_factory: TempdirFactory) -> None:
|
def __init__(
|
||||||
self.request = request
|
self, request: FixtureRequest, tmp_path_factory: TempPathFactory
|
||||||
|
) -> None:
|
||||||
|
self._request = request
|
||||||
self._mod_collections: WeakKeyDictionary[
|
self._mod_collections: WeakKeyDictionary[
|
||||||
Module, List[Union[Item, Collector]]
|
Module, List[Union[Item, Collector]]
|
||||||
] = (WeakKeyDictionary())
|
] = (WeakKeyDictionary())
|
||||||
|
@ -634,37 +658,40 @@ class Testdir:
|
||||||
else:
|
else:
|
||||||
name = request.node.name
|
name = request.node.name
|
||||||
self._name = name
|
self._name = name
|
||||||
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
|
self._path: Path = tmp_path_factory.mktemp(name, numbered=True)
|
||||||
self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
|
|
||||||
self.plugins: List[Union[str, _PluggyPlugin]] = []
|
self.plugins: List[Union[str, _PluggyPlugin]] = []
|
||||||
self._cwd_snapshot = CwdSnapshot()
|
self._cwd_snapshot = CwdSnapshot()
|
||||||
self._sys_path_snapshot = SysPathsSnapshot()
|
self._sys_path_snapshot = SysPathsSnapshot()
|
||||||
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
|
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
|
||||||
self.chdir()
|
self.chdir()
|
||||||
self.request.addfinalizer(self.finalize)
|
self._request.addfinalizer(self._finalize)
|
||||||
self._method = self.request.config.getoption("--runpytest")
|
self._method = self._request.config.getoption("--runpytest")
|
||||||
|
self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)
|
||||||
|
|
||||||
mp = self.monkeypatch = MonkeyPatch()
|
self._monkeypatch = mp = MonkeyPatch()
|
||||||
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self.test_tmproot))
|
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
|
||||||
# Ensure no unexpected caching via tox.
|
# Ensure no unexpected caching via tox.
|
||||||
mp.delenv("TOX_ENV_DIR", raising=False)
|
mp.delenv("TOX_ENV_DIR", raising=False)
|
||||||
# Discard outer pytest options.
|
# Discard outer pytest options.
|
||||||
mp.delenv("PYTEST_ADDOPTS", raising=False)
|
mp.delenv("PYTEST_ADDOPTS", raising=False)
|
||||||
# Ensure no user config is used.
|
# Ensure no user config is used.
|
||||||
tmphome = str(self.tmpdir)
|
tmphome = str(self.path)
|
||||||
mp.setenv("HOME", tmphome)
|
mp.setenv("HOME", tmphome)
|
||||||
mp.setenv("USERPROFILE", tmphome)
|
mp.setenv("USERPROFILE", tmphome)
|
||||||
# Do not use colors for inner runs by default.
|
# Do not use colors for inner runs by default.
|
||||||
mp.setenv("PY_COLORS", "0")
|
mp.setenv("PY_COLORS", "0")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self) -> Path:
|
||||||
|
"""Temporary directory where files are created and pytest is executed."""
|
||||||
|
return self._path
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Testdir {self.tmpdir!r}>"
|
return f"<Pytester {self.path!r}>"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def _finalize(self) -> None:
|
||||||
return str(self.tmpdir)
|
"""
|
||||||
|
Clean up global state artifacts.
|
||||||
def finalize(self) -> None:
|
|
||||||
"""Clean up global state artifacts.
|
|
||||||
|
|
||||||
Some methods modify the global interpreter state and this tries to
|
Some methods modify the global interpreter state and this tries to
|
||||||
clean this up. It does not remove the temporary directory however so
|
clean this up. It does not remove the temporary directory however so
|
||||||
|
@ -673,7 +700,7 @@ class Testdir:
|
||||||
self._sys_modules_snapshot.restore()
|
self._sys_modules_snapshot.restore()
|
||||||
self._sys_path_snapshot.restore()
|
self._sys_path_snapshot.restore()
|
||||||
self._cwd_snapshot.restore()
|
self._cwd_snapshot.restore()
|
||||||
self.monkeypatch.undo()
|
self._monkeypatch.undo()
|
||||||
|
|
||||||
def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
|
def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
|
||||||
# Some zope modules used by twisted-related tests keep internal state
|
# Some zope modules used by twisted-related tests keep internal state
|
||||||
|
@ -687,7 +714,7 @@ class Testdir:
|
||||||
def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
|
def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
|
||||||
"""Create a new :py:class:`HookRecorder` for a PluginManager."""
|
"""Create a new :py:class:`HookRecorder` for a PluginManager."""
|
||||||
pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
|
pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
|
||||||
self.request.addfinalizer(reprec.finish_recording)
|
self._request.addfinalizer(reprec.finish_recording)
|
||||||
return reprec
|
return reprec
|
||||||
|
|
||||||
def chdir(self) -> None:
|
def chdir(self) -> None:
|
||||||
|
@ -695,12 +722,18 @@ class Testdir:
|
||||||
|
|
||||||
This is done automatically upon instantiation.
|
This is done automatically upon instantiation.
|
||||||
"""
|
"""
|
||||||
self.tmpdir.chdir()
|
os.chdir(self.path)
|
||||||
|
|
||||||
def _makefile(self, ext: str, lines, files, encoding: str = "utf-8"):
|
def _makefile(
|
||||||
|
self,
|
||||||
|
ext: str,
|
||||||
|
lines: Sequence[Union[Any, bytes]],
|
||||||
|
files: Dict[str, str],
|
||||||
|
encoding: str = "utf-8",
|
||||||
|
) -> Path:
|
||||||
items = list(files.items())
|
items = list(files.items())
|
||||||
|
|
||||||
def to_text(s):
|
def to_text(s: Union[Any, bytes]) -> str:
|
||||||
return s.decode(encoding) if isinstance(s, bytes) else str(s)
|
return s.decode(encoding) if isinstance(s, bytes) else str(s)
|
||||||
|
|
||||||
if lines:
|
if lines:
|
||||||
|
@ -710,17 +743,18 @@ class Testdir:
|
||||||
|
|
||||||
ret = None
|
ret = None
|
||||||
for basename, value in items:
|
for basename, value in items:
|
||||||
p = self.tmpdir.join(basename).new(ext=ext)
|
p = self.path.joinpath(basename).with_suffix(ext)
|
||||||
p.dirpath().ensure_dir()
|
p.parent.mkdir(parents=True, exist_ok=True)
|
||||||
source_ = Source(value)
|
source_ = Source(value)
|
||||||
source = "\n".join(to_text(line) for line in source_.lines)
|
source = "\n".join(to_text(line) for line in source_.lines)
|
||||||
p.write(source.strip().encode(encoding), "wb")
|
p.write_text(source.strip(), encoding=encoding)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
ret = p
|
ret = p
|
||||||
|
assert ret is not None
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def makefile(self, ext: str, *args: str, **kwargs):
|
def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
|
||||||
r"""Create new file(s) in the testdir.
|
r"""Create new file(s) in the test directory.
|
||||||
|
|
||||||
:param str ext:
|
:param str ext:
|
||||||
The extension the file(s) should use, including the dot, e.g. `.py`.
|
The extension the file(s) should use, including the dot, e.g. `.py`.
|
||||||
|
@ -743,27 +777,27 @@ class Testdir:
|
||||||
"""
|
"""
|
||||||
return self._makefile(ext, args, kwargs)
|
return self._makefile(ext, args, kwargs)
|
||||||
|
|
||||||
def makeconftest(self, source):
|
def makeconftest(self, source: str) -> Path:
|
||||||
"""Write a contest.py file with 'source' as contents."""
|
"""Write a contest.py file with 'source' as contents."""
|
||||||
return self.makepyfile(conftest=source)
|
return self.makepyfile(conftest=source)
|
||||||
|
|
||||||
def makeini(self, source):
|
def makeini(self, source: str) -> Path:
|
||||||
"""Write a tox.ini file with 'source' as contents."""
|
"""Write a tox.ini file with 'source' as contents."""
|
||||||
return self.makefile(".ini", tox=source)
|
return self.makefile(".ini", tox=source)
|
||||||
|
|
||||||
def getinicfg(self, source) -> IniConfig:
|
def getinicfg(self, source: str) -> IniConfig:
|
||||||
"""Return the pytest section from the tox.ini config file."""
|
"""Return the pytest section from the tox.ini config file."""
|
||||||
p = self.makeini(source)
|
p = self.makeini(source)
|
||||||
return IniConfig(p)["pytest"]
|
return IniConfig(p)["pytest"]
|
||||||
|
|
||||||
def makepyprojecttoml(self, source):
|
def makepyprojecttoml(self, source: str) -> Path:
|
||||||
"""Write a pyproject.toml file with 'source' as contents.
|
"""Write a pyproject.toml file with 'source' as contents.
|
||||||
|
|
||||||
.. versionadded:: 6.0
|
.. versionadded:: 6.0
|
||||||
"""
|
"""
|
||||||
return self.makefile(".toml", pyproject=source)
|
return self.makefile(".toml", pyproject=source)
|
||||||
|
|
||||||
def makepyfile(self, *args, **kwargs):
|
def makepyfile(self, *args, **kwargs) -> Path:
|
||||||
r"""Shortcut for .makefile() with a .py extension.
|
r"""Shortcut for .makefile() with a .py extension.
|
||||||
|
|
||||||
Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting
|
Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting
|
||||||
|
@ -783,7 +817,7 @@ class Testdir:
|
||||||
"""
|
"""
|
||||||
return self._makefile(".py", args, kwargs)
|
return self._makefile(".py", args, kwargs)
|
||||||
|
|
||||||
def maketxtfile(self, *args, **kwargs):
|
def maketxtfile(self, *args, **kwargs) -> Path:
|
||||||
r"""Shortcut for .makefile() with a .txt extension.
|
r"""Shortcut for .makefile() with a .txt extension.
|
||||||
|
|
||||||
Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting
|
Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting
|
||||||
|
@ -803,74 +837,77 @@ class Testdir:
|
||||||
"""
|
"""
|
||||||
return self._makefile(".txt", args, kwargs)
|
return self._makefile(".txt", args, kwargs)
|
||||||
|
|
||||||
def syspathinsert(self, path=None) -> None:
|
def syspathinsert(
|
||||||
|
self, path: Optional[Union[str, "os.PathLike[str]"]] = None
|
||||||
|
) -> None:
|
||||||
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
|
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
|
||||||
|
|
||||||
This is undone automatically when this object dies at the end of each
|
This is undone automatically when this object dies at the end of each
|
||||||
test.
|
test.
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
path = self.tmpdir
|
path = self.path
|
||||||
|
|
||||||
self.monkeypatch.syspath_prepend(str(path))
|
self._monkeypatch.syspath_prepend(str(path))
|
||||||
|
|
||||||
def mkdir(self, name) -> py.path.local:
|
def mkdir(self, name: str) -> Path:
|
||||||
"""Create a new (sub)directory."""
|
"""Create a new (sub)directory."""
|
||||||
return self.tmpdir.mkdir(name)
|
p = self.path / name
|
||||||
|
p.mkdir()
|
||||||
|
return p
|
||||||
|
|
||||||
def mkpydir(self, name) -> py.path.local:
|
def mkpydir(self, name: str) -> Path:
|
||||||
"""Create a new Python package.
|
"""Create a new python package.
|
||||||
|
|
||||||
This creates a (sub)directory with an empty ``__init__.py`` file so it
|
This creates a (sub)directory with an empty ``__init__.py`` file so it
|
||||||
gets recognised as a Python package.
|
gets recognised as a Python package.
|
||||||
"""
|
"""
|
||||||
p = self.mkdir(name)
|
p = self.path / name
|
||||||
p.ensure("__init__.py")
|
p.mkdir()
|
||||||
|
p.joinpath("__init__.py").touch()
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def copy_example(self, name=None) -> py.path.local:
|
def copy_example(self, name: Optional[str] = None) -> Path:
|
||||||
"""Copy file from project's directory into the testdir.
|
"""Copy file from project's directory into the testdir.
|
||||||
|
|
||||||
:param str name: The name of the file to copy.
|
:param str name: The name of the file to copy.
|
||||||
:returns: Path to the copied directory (inside ``self.tmpdir``).
|
:return: path to the copied directory (inside ``self.path``).
|
||||||
"""
|
|
||||||
import warnings
|
|
||||||
from _pytest.warning_types import PYTESTER_COPY_EXAMPLE
|
|
||||||
|
|
||||||
warnings.warn(PYTESTER_COPY_EXAMPLE, stacklevel=2)
|
"""
|
||||||
example_dir = self.request.config.getini("pytester_example_dir")
|
example_dir = self._request.config.getini("pytester_example_dir")
|
||||||
if example_dir is None:
|
if example_dir is None:
|
||||||
raise ValueError("pytester_example_dir is unset, can't copy examples")
|
raise ValueError("pytester_example_dir is unset, can't copy examples")
|
||||||
example_dir = self.request.config.rootdir.join(example_dir)
|
example_dir = Path(str(self._request.config.rootdir)) / example_dir
|
||||||
|
|
||||||
for extra_element in self.request.node.iter_markers("pytester_example_path"):
|
for extra_element in self._request.node.iter_markers("pytester_example_path"):
|
||||||
assert extra_element.args
|
assert extra_element.args
|
||||||
example_dir = example_dir.join(*extra_element.args)
|
example_dir = example_dir.joinpath(*extra_element.args)
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
func_name = self._name
|
func_name = self._name
|
||||||
maybe_dir = example_dir / func_name
|
maybe_dir = example_dir / func_name
|
||||||
maybe_file = example_dir / (func_name + ".py")
|
maybe_file = example_dir / (func_name + ".py")
|
||||||
|
|
||||||
if maybe_dir.isdir():
|
if maybe_dir.is_dir():
|
||||||
example_path = maybe_dir
|
example_path = maybe_dir
|
||||||
elif maybe_file.isfile():
|
elif maybe_file.is_file():
|
||||||
example_path = maybe_file
|
example_path = maybe_file
|
||||||
else:
|
else:
|
||||||
raise LookupError(
|
raise LookupError(
|
||||||
"{} cant be found as module or package in {}".format(
|
f"{func_name} can't be found as module or package in {example_dir}"
|
||||||
func_name, example_dir.bestrelpath(self.request.config.rootdir)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
example_path = example_dir.join(name)
|
example_path = example_dir.joinpath(name)
|
||||||
|
|
||||||
if example_path.isdir() and not example_path.join("__init__.py").isfile():
|
if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file():
|
||||||
example_path.copy(self.tmpdir)
|
# TODO: py.path.local.copy can copy files to existing directories,
|
||||||
return self.tmpdir
|
# while with shutil.copytree the destination directory cannot exist,
|
||||||
elif example_path.isfile():
|
# we will need to roll our own in order to drop py.path.local completely
|
||||||
result = self.tmpdir.join(example_path.basename)
|
py.path.local(example_path).copy(py.path.local(self.path))
|
||||||
example_path.copy(result)
|
return self.path
|
||||||
|
elif example_path.is_file():
|
||||||
|
result = self.path.joinpath(example_path.name)
|
||||||
|
shutil.copy(example_path, result)
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
raise LookupError(
|
raise LookupError(
|
||||||
|
@ -879,7 +916,9 @@ class Testdir:
|
||||||
|
|
||||||
Session = Session
|
Session = Session
|
||||||
|
|
||||||
def getnode(self, config: Config, arg):
|
def getnode(
|
||||||
|
self, config: Config, arg: Union[str, "os.PathLike[str]"]
|
||||||
|
) -> Optional[Union[Collector, Item]]:
|
||||||
"""Return the collection node of a file.
|
"""Return the collection node of a file.
|
||||||
|
|
||||||
:param _pytest.config.Config config:
|
:param _pytest.config.Config config:
|
||||||
|
@ -896,7 +935,7 @@ class Testdir:
|
||||||
config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
|
config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getpathnode(self, path):
|
def getpathnode(self, path: Union[str, "os.PathLike[str]"]):
|
||||||
"""Return the collection node of a file.
|
"""Return the collection node of a file.
|
||||||
|
|
||||||
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
|
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
|
||||||
|
@ -904,6 +943,7 @@ class Testdir:
|
||||||
|
|
||||||
:param py.path.local path: Path to the file.
|
:param py.path.local path: Path to the file.
|
||||||
"""
|
"""
|
||||||
|
path = py.path.local(path)
|
||||||
config = self.parseconfigure(path)
|
config = self.parseconfigure(path)
|
||||||
session = Session.from_config(config)
|
session = Session.from_config(config)
|
||||||
x = session.fspath.bestrelpath(path)
|
x = session.fspath.bestrelpath(path)
|
||||||
|
@ -924,7 +964,7 @@ class Testdir:
|
||||||
result.extend(session.genitems(colitem))
|
result.extend(session.genitems(colitem))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def runitem(self, source):
|
def runitem(self, source: str) -> Any:
|
||||||
"""Run the "test_func" Item.
|
"""Run the "test_func" Item.
|
||||||
|
|
||||||
The calling test instance (class containing the test method) must
|
The calling test instance (class containing the test method) must
|
||||||
|
@ -935,11 +975,11 @@ class Testdir:
|
||||||
# used from runner functional tests
|
# used from runner functional tests
|
||||||
item = self.getitem(source)
|
item = self.getitem(source)
|
||||||
# the test class where we are called from wants to provide the runner
|
# the test class where we are called from wants to provide the runner
|
||||||
testclassinstance = self.request.instance
|
testclassinstance = self._request.instance
|
||||||
runner = testclassinstance.getrunner()
|
runner = testclassinstance.getrunner()
|
||||||
return runner(item)
|
return runner(item)
|
||||||
|
|
||||||
def inline_runsource(self, source, *cmdlineargs) -> HookRecorder:
|
def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder:
|
||||||
"""Run a test module in process using ``pytest.main()``.
|
"""Run a test module in process using ``pytest.main()``.
|
||||||
|
|
||||||
This run writes "source" into a temporary file and runs
|
This run writes "source" into a temporary file and runs
|
||||||
|
@ -968,7 +1008,10 @@ class Testdir:
|
||||||
return items, rec
|
return items, rec
|
||||||
|
|
||||||
def inline_run(
|
def inline_run(
|
||||||
self, *args, plugins=(), no_reraise_ctrlc: bool = False
|
self,
|
||||||
|
*args: Union[str, "os.PathLike[str]"],
|
||||||
|
plugins=(),
|
||||||
|
no_reraise_ctrlc: bool = False,
|
||||||
) -> HookRecorder:
|
) -> HookRecorder:
|
||||||
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
|
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
|
||||||
|
|
||||||
|
@ -1016,7 +1059,7 @@ class Testdir:
|
||||||
rec.append(self.make_hook_recorder(config.pluginmanager))
|
rec.append(self.make_hook_recorder(config.pluginmanager))
|
||||||
|
|
||||||
plugins.append(Collect())
|
plugins.append(Collect())
|
||||||
ret = pytest.main(list(args), plugins=plugins)
|
ret = pytest.main([str(x) for x in args], plugins=plugins)
|
||||||
if len(rec) == 1:
|
if len(rec) == 1:
|
||||||
reprec = rec.pop()
|
reprec = rec.pop()
|
||||||
else:
|
else:
|
||||||
|
@ -1024,7 +1067,7 @@ class Testdir:
|
||||||
class reprec: # type: ignore
|
class reprec: # type: ignore
|
||||||
pass
|
pass
|
||||||
|
|
||||||
reprec.ret = ret
|
reprec.ret = ret # type: ignore
|
||||||
|
|
||||||
# Typically we reraise keyboard interrupts from the child run
|
# Typically we reraise keyboard interrupts from the child run
|
||||||
# because it's our user requesting interruption of the testing.
|
# because it's our user requesting interruption of the testing.
|
||||||
|
@ -1037,7 +1080,9 @@ class Testdir:
|
||||||
for finalizer in finalizers:
|
for finalizer in finalizers:
|
||||||
finalizer()
|
finalizer()
|
||||||
|
|
||||||
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
|
def runpytest_inprocess(
|
||||||
|
self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any
|
||||||
|
) -> RunResult:
|
||||||
"""Return result of running pytest in-process, providing a similar
|
"""Return result of running pytest in-process, providing a similar
|
||||||
interface to what self.runpytest() provides."""
|
interface to what self.runpytest() provides."""
|
||||||
syspathinsert = kwargs.pop("syspathinsert", False)
|
syspathinsert = kwargs.pop("syspathinsert", False)
|
||||||
|
@ -1079,26 +1124,30 @@ class Testdir:
|
||||||
res.reprec = reprec # type: ignore
|
res.reprec = reprec # type: ignore
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def runpytest(self, *args, **kwargs) -> RunResult:
|
def runpytest(
|
||||||
|
self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any
|
||||||
|
) -> RunResult:
|
||||||
"""Run pytest inline or in a subprocess, depending on the command line
|
"""Run pytest inline or in a subprocess, depending on the command line
|
||||||
option "--runpytest" and return a :py:class:`RunResult`."""
|
option "--runpytest" and return a :py:class:`RunResult`."""
|
||||||
args = self._ensure_basetemp(args)
|
new_args = self._ensure_basetemp(args)
|
||||||
if self._method == "inprocess":
|
if self._method == "inprocess":
|
||||||
return self.runpytest_inprocess(*args, **kwargs)
|
return self.runpytest_inprocess(*new_args, **kwargs)
|
||||||
elif self._method == "subprocess":
|
elif self._method == "subprocess":
|
||||||
return self.runpytest_subprocess(*args, **kwargs)
|
return self.runpytest_subprocess(*new_args, **kwargs)
|
||||||
raise RuntimeError(f"Unrecognized runpytest option: {self._method}")
|
raise RuntimeError(f"Unrecognized runpytest option: {self._method}")
|
||||||
|
|
||||||
def _ensure_basetemp(self, args):
|
def _ensure_basetemp(
|
||||||
args = list(args)
|
self, args: Sequence[Union[str, "os.PathLike[str]"]]
|
||||||
for x in args:
|
) -> List[Union[str, "os.PathLike[str]"]]:
|
||||||
|
new_args = list(args)
|
||||||
|
for x in new_args:
|
||||||
if str(x).startswith("--basetemp"):
|
if str(x).startswith("--basetemp"):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
args.append("--basetemp=%s" % self.tmpdir.dirpath("basetemp"))
|
new_args.append("--basetemp=%s" % self.path.parent.joinpath("basetemp"))
|
||||||
return args
|
return new_args
|
||||||
|
|
||||||
def parseconfig(self, *args) -> Config:
|
def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
|
||||||
"""Return a new pytest Config instance from given commandline args.
|
"""Return a new pytest Config instance from given commandline args.
|
||||||
|
|
||||||
This invokes the pytest bootstrapping code in _pytest.config to create
|
This invokes the pytest bootstrapping code in _pytest.config to create
|
||||||
|
@ -1109,18 +1158,19 @@ class Testdir:
|
||||||
If :py:attr:`plugins` has been populated they should be plugin modules
|
If :py:attr:`plugins` has been populated they should be plugin modules
|
||||||
to be registered with the PluginManager.
|
to be registered with the PluginManager.
|
||||||
"""
|
"""
|
||||||
args = self._ensure_basetemp(args)
|
|
||||||
|
|
||||||
import _pytest.config
|
import _pytest.config
|
||||||
|
|
||||||
config = _pytest.config._prepareconfig(args, self.plugins) # type: ignore[arg-type]
|
new_args = self._ensure_basetemp(args)
|
||||||
|
new_args = [str(x) for x in new_args]
|
||||||
|
|
||||||
|
config = _pytest.config._prepareconfig(new_args, self.plugins) # type: ignore[arg-type]
|
||||||
# we don't know what the test will do with this half-setup config
|
# we don't know what the test will do with this half-setup config
|
||||||
# object and thus we make sure it gets unconfigured properly in any
|
# object and thus we make sure it gets unconfigured properly in any
|
||||||
# case (otherwise capturing could still be active, for example)
|
# case (otherwise capturing could still be active, for example)
|
||||||
self.request.addfinalizer(config._ensure_unconfigure)
|
self._request.addfinalizer(config._ensure_unconfigure)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def parseconfigure(self, *args) -> Config:
|
def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
|
||||||
"""Return a new pytest configured Config instance.
|
"""Return a new pytest configured Config instance.
|
||||||
|
|
||||||
Returns a new :py:class:`_pytest.config.Config` instance like
|
Returns a new :py:class:`_pytest.config.Config` instance like
|
||||||
|
@ -1130,7 +1180,7 @@ class Testdir:
|
||||||
config._do_configure()
|
config._do_configure()
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def getitem(self, source, funcname: str = "test_func") -> Item:
|
def getitem(self, source: str, funcname: str = "test_func") -> Item:
|
||||||
"""Return the test item for a test function.
|
"""Return the test item for a test function.
|
||||||
|
|
||||||
Writes the source to a python file and runs pytest's collection on
|
Writes the source to a python file and runs pytest's collection on
|
||||||
|
@ -1150,7 +1200,7 @@ class Testdir:
|
||||||
funcname, source, items
|
funcname, source, items
|
||||||
)
|
)
|
||||||
|
|
||||||
def getitems(self, source) -> List[Item]:
|
def getitems(self, source: str) -> List[Item]:
|
||||||
"""Return all test items collected from the module.
|
"""Return all test items collected from the module.
|
||||||
|
|
||||||
Writes the source to a Python file and runs pytest's collection on
|
Writes the source to a Python file and runs pytest's collection on
|
||||||
|
@ -1159,7 +1209,9 @@ class Testdir:
|
||||||
modcol = self.getmodulecol(source)
|
modcol = self.getmodulecol(source)
|
||||||
return self.genitems([modcol])
|
return self.genitems([modcol])
|
||||||
|
|
||||||
def getmodulecol(self, source, configargs=(), withinit: bool = False):
|
def getmodulecol(
|
||||||
|
self, source: Union[str, Path], configargs=(), *, withinit: bool = False
|
||||||
|
):
|
||||||
"""Return the module collection node for ``source``.
|
"""Return the module collection node for ``source``.
|
||||||
|
|
||||||
Writes ``source`` to a file using :py:meth:`makepyfile` and then
|
Writes ``source`` to a file using :py:meth:`makepyfile` and then
|
||||||
|
@ -1177,10 +1229,10 @@ class Testdir:
|
||||||
directory to ensure it is a package.
|
directory to ensure it is a package.
|
||||||
"""
|
"""
|
||||||
if isinstance(source, Path):
|
if isinstance(source, Path):
|
||||||
path = self.tmpdir.join(str(source))
|
path = self.path.joinpath(source)
|
||||||
assert not withinit, "not supported for paths"
|
assert not withinit, "not supported for paths"
|
||||||
else:
|
else:
|
||||||
kw = {self._name: Source(source).strip()}
|
kw = {self._name: str(source)}
|
||||||
path = self.makepyfile(**kw)
|
path = self.makepyfile(**kw)
|
||||||
if withinit:
|
if withinit:
|
||||||
self.makepyfile(__init__="#")
|
self.makepyfile(__init__="#")
|
||||||
|
@ -1208,8 +1260,8 @@ class Testdir:
|
||||||
def popen(
|
def popen(
|
||||||
self,
|
self,
|
||||||
cmdargs,
|
cmdargs,
|
||||||
stdout=subprocess.PIPE,
|
stdout: Union[int, TextIO] = subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr: Union[int, TextIO] = subprocess.PIPE,
|
||||||
stdin=CLOSE_STDIN,
|
stdin=CLOSE_STDIN,
|
||||||
**kw,
|
**kw,
|
||||||
):
|
):
|
||||||
|
@ -1244,14 +1296,18 @@ class Testdir:
|
||||||
return popen
|
return popen
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
self, *cmdargs, timeout: Optional[float] = None, stdin=CLOSE_STDIN
|
self,
|
||||||
|
*cmdargs: Union[str, "os.PathLike[str]"],
|
||||||
|
timeout: Optional[float] = None,
|
||||||
|
stdin=CLOSE_STDIN,
|
||||||
) -> RunResult:
|
) -> RunResult:
|
||||||
"""Run a command with arguments.
|
"""Run a command with arguments.
|
||||||
|
|
||||||
Run a process using subprocess.Popen saving the stdout and stderr.
|
Run a process using subprocess.Popen saving the stdout and stderr.
|
||||||
|
|
||||||
:param args:
|
:param cmdargs:
|
||||||
The sequence of arguments to pass to `subprocess.Popen()`.
|
The sequence of arguments to pass to `subprocess.Popen()`, with path-like objects
|
||||||
|
being converted to ``str`` automatically.
|
||||||
:param timeout:
|
:param timeout:
|
||||||
The period in seconds after which to timeout and raise
|
The period in seconds after which to timeout and raise
|
||||||
:py:class:`Testdir.TimeoutExpired`.
|
:py:class:`Testdir.TimeoutExpired`.
|
||||||
|
@ -1266,15 +1322,14 @@ class Testdir:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
|
|
||||||
cmdargs = tuple(
|
cmdargs = tuple(
|
||||||
str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
|
os.fspath(arg) if isinstance(arg, os.PathLike) else arg for arg in cmdargs
|
||||||
)
|
)
|
||||||
p1 = self.tmpdir.join("stdout")
|
p1 = self.path.joinpath("stdout")
|
||||||
p2 = self.tmpdir.join("stderr")
|
p2 = self.path.joinpath("stderr")
|
||||||
print("running:", *cmdargs)
|
print("running:", *cmdargs)
|
||||||
print(" in:", py.path.local())
|
print(" in:", Path.cwd())
|
||||||
f1 = open(str(p1), "w", encoding="utf8")
|
|
||||||
f2 = open(str(p2), "w", encoding="utf8")
|
with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2:
|
||||||
try:
|
|
||||||
now = timing.time()
|
now = timing.time()
|
||||||
popen = self.popen(
|
popen = self.popen(
|
||||||
cmdargs,
|
cmdargs,
|
||||||
|
@ -1305,23 +1360,16 @@ class Testdir:
|
||||||
ret = popen.wait(timeout)
|
ret = popen.wait(timeout)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
handle_timeout()
|
handle_timeout()
|
||||||
finally:
|
|
||||||
f1.close()
|
with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2:
|
||||||
f2.close()
|
|
||||||
f1 = open(str(p1), encoding="utf8")
|
|
||||||
f2 = open(str(p2), encoding="utf8")
|
|
||||||
try:
|
|
||||||
out = f1.read().splitlines()
|
out = f1.read().splitlines()
|
||||||
err = f2.read().splitlines()
|
err = f2.read().splitlines()
|
||||||
finally:
|
|
||||||
f1.close()
|
|
||||||
f2.close()
|
|
||||||
self._dump_lines(out, sys.stdout)
|
self._dump_lines(out, sys.stdout)
|
||||||
self._dump_lines(err, sys.stderr)
|
self._dump_lines(err, sys.stderr)
|
||||||
try:
|
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
ret = ExitCode(ret)
|
ret = ExitCode(ret)
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return RunResult(ret, out, err, timing.time() - now)
|
return RunResult(ret, out, err, timing.time() - now)
|
||||||
|
|
||||||
def _dump_lines(self, lines, fp):
|
def _dump_lines(self, lines, fp):
|
||||||
|
@ -1366,7 +1414,7 @@ class Testdir:
|
||||||
:rtype: RunResult
|
:rtype: RunResult
|
||||||
"""
|
"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
p = make_numbered_dir(root=Path(str(self.tmpdir)), prefix="runpytest-")
|
p = make_numbered_dir(root=self.path, prefix="runpytest-")
|
||||||
args = ("--basetemp=%s" % p,) + args
|
args = ("--basetemp=%s" % p,) + args
|
||||||
plugins = [x for x in self.plugins if isinstance(x, str)]
|
plugins = [x for x in self.plugins if isinstance(x, str)]
|
||||||
if plugins:
|
if plugins:
|
||||||
|
@ -1384,7 +1432,8 @@ class Testdir:
|
||||||
|
|
||||||
The pexpect child is returned.
|
The pexpect child is returned.
|
||||||
"""
|
"""
|
||||||
basetemp = self.tmpdir.mkdir("temp-pexpect")
|
basetemp = self.path / "temp-pexpect"
|
||||||
|
basetemp.mkdir()
|
||||||
invoke = " ".join(map(str, self._getpytestargs()))
|
invoke = " ".join(map(str, self._getpytestargs()))
|
||||||
cmd = f"{invoke} --basetemp={basetemp} {string}"
|
cmd = f"{invoke} --basetemp={basetemp} {string}"
|
||||||
return self.spawn(cmd, expect_timeout=expect_timeout)
|
return self.spawn(cmd, expect_timeout=expect_timeout)
|
||||||
|
@ -1399,10 +1448,10 @@ class Testdir:
|
||||||
pytest.skip("pypy-64 bit not supported")
|
pytest.skip("pypy-64 bit not supported")
|
||||||
if not hasattr(pexpect, "spawn"):
|
if not hasattr(pexpect, "spawn"):
|
||||||
pytest.skip("pexpect.spawn not available")
|
pytest.skip("pexpect.spawn not available")
|
||||||
logfile = self.tmpdir.join("spawn.out").open("wb")
|
logfile = self.path.joinpath("spawn.out").open("wb")
|
||||||
|
|
||||||
child = pexpect.spawn(cmd, logfile=logfile)
|
child = pexpect.spawn(cmd, logfile=logfile)
|
||||||
self.request.addfinalizer(logfile.close)
|
self._request.addfinalizer(logfile.close)
|
||||||
child.timeout = expect_timeout
|
child.timeout = expect_timeout
|
||||||
return child
|
return child
|
||||||
|
|
||||||
|
@ -1425,6 +1474,178 @@ class LineComp:
|
||||||
LineMatcher(lines1).fnmatch_lines(lines2)
|
LineMatcher(lines1).fnmatch_lines(lines2)
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
@attr.s(repr=False, str=False)
|
||||||
|
class Testdir:
|
||||||
|
"""
|
||||||
|
Similar to :class:`Pytester`, but this class works with legacy py.path.local objects instead.
|
||||||
|
|
||||||
|
All methods just forward to an internal :class:`Pytester` instance, converting results
|
||||||
|
to `py.path.local` objects as necessary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__test__ = False
|
||||||
|
|
||||||
|
CLOSE_STDIN = Pytester.CLOSE_STDIN
|
||||||
|
TimeoutExpired = Pytester.TimeoutExpired
|
||||||
|
Session = Pytester.Session
|
||||||
|
|
||||||
|
_pytester: Pytester = attr.ib()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tmpdir(self) -> py.path.local:
|
||||||
|
return py.path.local(self._pytester.path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def test_tmproot(self) -> py.path.local:
|
||||||
|
return py.path.local(self._pytester._test_tmproot)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request(self):
|
||||||
|
return self._pytester._request
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugins(self):
|
||||||
|
return self._pytester.plugins
|
||||||
|
|
||||||
|
@plugins.setter
|
||||||
|
def plugins(self, plugins):
|
||||||
|
self._pytester.plugins = plugins
|
||||||
|
|
||||||
|
@property
|
||||||
|
def monkeypatch(self) -> MonkeyPatch:
|
||||||
|
return self._pytester._monkeypatch
|
||||||
|
|
||||||
|
def make_hook_recorder(self, pluginmanager) -> HookRecorder:
|
||||||
|
return self._pytester.make_hook_recorder(pluginmanager)
|
||||||
|
|
||||||
|
def chdir(self) -> None:
|
||||||
|
return self._pytester.chdir()
|
||||||
|
|
||||||
|
def finalize(self) -> None:
|
||||||
|
return self._pytester._finalize()
|
||||||
|
|
||||||
|
def makefile(self, ext, *args, **kwargs) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.makefile(ext, *args, **kwargs)))
|
||||||
|
|
||||||
|
def makeconftest(self, source) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.makeconftest(source)))
|
||||||
|
|
||||||
|
def makeini(self, source) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.makeini(source)))
|
||||||
|
|
||||||
|
def getinicfg(self, source) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.getinicfg(source)))
|
||||||
|
|
||||||
|
def makepyprojecttoml(self, source) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.makepyprojecttoml(source)))
|
||||||
|
|
||||||
|
def makepyfile(self, *args, **kwargs) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.makepyfile(*args, **kwargs)))
|
||||||
|
|
||||||
|
def maketxtfile(self, *args, **kwargs) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.maketxtfile(*args, **kwargs)))
|
||||||
|
|
||||||
|
def syspathinsert(self, path=None) -> None:
|
||||||
|
return self._pytester.syspathinsert(path)
|
||||||
|
|
||||||
|
def mkdir(self, name) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.mkdir(name)))
|
||||||
|
|
||||||
|
def mkpydir(self, name) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.mkpydir(name)))
|
||||||
|
|
||||||
|
def copy_example(self, name=None) -> py.path.local:
|
||||||
|
return py.path.local(str(self._pytester.copy_example(name)))
|
||||||
|
|
||||||
|
def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
|
||||||
|
return self._pytester.getnode(config, arg)
|
||||||
|
|
||||||
|
def getpathnode(self, path):
|
||||||
|
return self._pytester.getpathnode(path)
|
||||||
|
|
||||||
|
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
|
||||||
|
return self._pytester.genitems(colitems)
|
||||||
|
|
||||||
|
def runitem(self, source):
|
||||||
|
return self._pytester.runitem(source)
|
||||||
|
|
||||||
|
def inline_runsource(self, source, *cmdlineargs):
|
||||||
|
return self._pytester.inline_runsource(source, *cmdlineargs)
|
||||||
|
|
||||||
|
def inline_genitems(self, *args):
|
||||||
|
return self._pytester.inline_genitems(*args)
|
||||||
|
|
||||||
|
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
|
||||||
|
return self._pytester.inline_run(
|
||||||
|
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
|
||||||
|
)
|
||||||
|
|
||||||
|
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
|
||||||
|
return self._pytester.runpytest_inprocess(*args, **kwargs)
|
||||||
|
|
||||||
|
def runpytest(self, *args, **kwargs) -> RunResult:
|
||||||
|
return self._pytester.runpytest(*args, **kwargs)
|
||||||
|
|
||||||
|
def parseconfig(self, *args) -> Config:
|
||||||
|
return self._pytester.parseconfig(*args)
|
||||||
|
|
||||||
|
def parseconfigure(self, *args) -> Config:
|
||||||
|
return self._pytester.parseconfigure(*args)
|
||||||
|
|
||||||
|
def getitem(self, source, funcname="test_func"):
|
||||||
|
return self._pytester.getitem(source, funcname)
|
||||||
|
|
||||||
|
def getitems(self, source):
|
||||||
|
return self._pytester.getitems(source)
|
||||||
|
|
||||||
|
def getmodulecol(self, source, configargs=(), withinit=False):
|
||||||
|
return self._pytester.getmodulecol(
|
||||||
|
source, configargs=configargs, withinit=withinit
|
||||||
|
)
|
||||||
|
|
||||||
|
def collect_by_name(
|
||||||
|
self, modcol: Module, name: str
|
||||||
|
) -> Optional[Union[Item, Collector]]:
|
||||||
|
return self._pytester.collect_by_name(modcol, name)
|
||||||
|
|
||||||
|
def popen(
|
||||||
|
self,
|
||||||
|
cmdargs,
|
||||||
|
stdout: Union[int, TextIO] = subprocess.PIPE,
|
||||||
|
stderr: Union[int, TextIO] = subprocess.PIPE,
|
||||||
|
stdin=CLOSE_STDIN,
|
||||||
|
**kw,
|
||||||
|
):
|
||||||
|
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
|
||||||
|
|
||||||
|
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
|
||||||
|
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
|
||||||
|
|
||||||
|
def runpython(self, script) -> RunResult:
|
||||||
|
return self._pytester.runpython(script)
|
||||||
|
|
||||||
|
def runpython_c(self, command):
|
||||||
|
return self._pytester.runpython_c(command)
|
||||||
|
|
||||||
|
def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
|
||||||
|
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
|
||||||
|
|
||||||
|
def spawn_pytest(
|
||||||
|
self, string: str, expect_timeout: float = 10.0
|
||||||
|
) -> "pexpect.spawn":
|
||||||
|
return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
|
||||||
|
|
||||||
|
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
|
||||||
|
return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<Testdir {self.tmpdir!r}>"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.tmpdir)
|
||||||
|
|
||||||
|
|
||||||
class LineMatcher:
|
class LineMatcher:
|
||||||
"""Flexible matching of text.
|
"""Flexible matching of text.
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,3 @@ class UnformattedWarning(Generic[_W]):
|
||||||
def format(self, **kwargs: Any) -> _W:
|
def format(self, **kwargs: Any) -> _W:
|
||||||
"""Return an instance of the warning category, formatted with given kwargs."""
|
"""Return an instance of the warning category, formatted with given kwargs."""
|
||||||
return self.category(self.template.format(**kwargs))
|
return self.category(self.template.format(**kwargs))
|
||||||
|
|
||||||
|
|
||||||
PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example")
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import pytest
|
||||||
from _pytest.compat import importlib_metadata
|
from _pytest.compat import importlib_metadata
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.pathlib import symlink_or_skip
|
from _pytest.pathlib import symlink_or_skip
|
||||||
from _pytest.pytester import Testdir
|
from _pytest.pytester import Pytester
|
||||||
|
|
||||||
|
|
||||||
def prepend_pythonpath(*dirs):
|
def prepend_pythonpath(*dirs):
|
||||||
|
@ -1276,14 +1276,14 @@ def test_tee_stdio_captures_and_live_prints(testdir):
|
||||||
sys.platform == "win32",
|
sys.platform == "win32",
|
||||||
reason="Windows raises `OSError: [Errno 22] Invalid argument` instead",
|
reason="Windows raises `OSError: [Errno 22] Invalid argument` instead",
|
||||||
)
|
)
|
||||||
def test_no_brokenpipeerror_message(testdir: Testdir) -> None:
|
def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
|
||||||
"""Ensure that the broken pipe error message is supressed.
|
"""Ensure that the broken pipe error message is supressed.
|
||||||
|
|
||||||
In some Python versions, it reaches sys.unraisablehook, in others
|
In some Python versions, it reaches sys.unraisablehook, in others
|
||||||
a BrokenPipeError exception is propagated, but either way it prints
|
a BrokenPipeError exception is propagated, but either way it prints
|
||||||
to stderr on shutdown, so checking nothing is printed is enough.
|
to stderr on shutdown, so checking nothing is printed is enough.
|
||||||
"""
|
"""
|
||||||
popen = testdir.popen((*testdir._getpytestargs(), "--help"))
|
popen = pytester.popen((*pytester._getpytestargs(), "--help"))
|
||||||
popen.stdout.close()
|
popen.stdout.close()
|
||||||
ret = popen.wait()
|
ret = popen.wait()
|
||||||
assert popen.stderr.read() == b""
|
assert popen.stderr.read() == b""
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -801,9 +801,10 @@ def test_parse_summary_line_always_plural():
|
||||||
|
|
||||||
def test_makefile_joins_absolute_path(testdir: Testdir) -> None:
|
def test_makefile_joins_absolute_path(testdir: Testdir) -> None:
|
||||||
absfile = testdir.tmpdir / "absfile"
|
absfile = testdir.tmpdir / "absfile"
|
||||||
if sys.platform == "win32":
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
testdir.makepyfile(**{str(absfile): ""})
|
|
||||||
else:
|
|
||||||
p1 = testdir.makepyfile(**{str(absfile): ""})
|
p1 = testdir.makepyfile(**{str(absfile): ""})
|
||||||
assert str(p1) == (testdir.tmpdir / absfile) + ".py"
|
assert str(p1) == str(testdir.tmpdir / "absfile.py")
|
||||||
|
|
||||||
|
|
||||||
|
def test_testtmproot(testdir):
|
||||||
|
"""Check test_tmproot is a py.path attribute for backward compatibility."""
|
||||||
|
assert testdir.test_tmproot.check(dir=1)
|
||||||
|
|
Loading…
Reference in New Issue