Stop importing `pytest` to avoid upcoming import cycles

Don't import `pytest` from within some `_pytest` modules since an
upcoming commit will import from them into `pytest`.

It would have been nice not to have to do it, so that internal plugins
look more like external plugins, but with the existing layout this seems
unavoidable.
This commit is contained in:
Ran Benita 2020-11-06 19:25:40 +02:00
parent 3bcd316f07
commit 1cbb0c3554
7 changed files with 79 additions and 67 deletions

View File

@ -15,7 +15,6 @@ from typing import Union
import attr
import py
import pytest
from .pathlib import resolve_from_str
from .pathlib import rm_rf
from .reports import CollectReport
@ -24,7 +23,9 @@ from _pytest._io import TerminalWriter
from _pytest.compat import final
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.python import Module
@ -182,7 +183,7 @@ class LFPluginCollWrapper:
self.lfplugin = lfplugin
self._collected_at_least_one_failure = False
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector: nodes.Collector):
if isinstance(collector, Session):
out = yield
@ -229,7 +230,7 @@ class LFPluginCollSkipfiles:
def __init__(self, lfplugin: "LFPlugin") -> None:
self.lfplugin = lfplugin
@pytest.hookimpl
@hookimpl
def pytest_make_collect_report(
self, collector: nodes.Collector
) -> Optional[CollectReport]:
@ -291,7 +292,7 @@ class LFPlugin:
else:
self.lastfailed[report.nodeid] = True
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection_modifyitems(
self, config: Config, items: List[nodes.Item]
) -> Generator[None, None, None]:
@ -363,7 +364,7 @@ class NFPlugin:
assert config.cache is not None
self.cached_nodeids = set(config.cache.get("cache/nodeids", []))
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection_modifyitems(
self, items: List[nodes.Item]
) -> Generator[None, None, None]:
@ -466,14 +467,14 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
return None
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_configure(config: Config) -> None:
config.cache = Cache.for_config(config)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
config.pluginmanager.register(NFPlugin(config), "nfplugin")
@pytest.fixture
@fixture
def cache(request: FixtureRequest) -> Cache:
"""Return a cache object that can persist state between testing sessions.

View File

@ -17,12 +17,14 @@ from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
import pytest
from _pytest.compat import final
from _pytest.config import Config
from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
from _pytest.fixtures import fixture
from _pytest.fixtures import SubRequest
from _pytest.nodes import Collector
from _pytest.nodes import File
from _pytest.nodes import Item
if TYPE_CHECKING:
@ -145,7 +147,7 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
sys.stderr = _reopen_stdio(sys.stderr, "wb")
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config: Config):
ns = early_config.known_args_namespace
if ns.capture == "fd":
@ -784,9 +786,9 @@ class CaptureManager:
# Hooks
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector: Collector):
if isinstance(collector, pytest.File):
if isinstance(collector, File):
self.resume_global_capture()
outcome = yield
self.suspend_global_capture()
@ -799,26 +801,26 @@ class CaptureManager:
else:
yield
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]:
with self.item_capture("setup", item):
yield
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]:
with self.item_capture("call", item):
yield
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]:
with self.item_capture("teardown", item):
yield
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self) -> None:
self.stop_global_capturing()
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_internalerror(self) -> None:
self.stop_global_capturing()
@ -893,7 +895,7 @@ class CaptureFixture(Generic[AnyStr]):
# The fixtures.
@pytest.fixture
@fixture
def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
@ -910,7 +912,7 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
capman.unset_fixture()
@pytest.fixture
@fixture
def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
@ -927,7 +929,7 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None,
capman.unset_fixture()
@pytest.fixture
@fixture
def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
@ -944,7 +946,7 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
capman.unset_fixture()
@pytest.fixture
@fixture
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.

View File

@ -16,7 +16,6 @@ from typing import Tuple
from typing import TypeVar
from typing import Union
import pytest
from _pytest import nodes
from _pytest._io import TerminalWriter
from _pytest.capture import CaptureManager
@ -25,7 +24,10 @@ from _pytest.compat import nullcontext
from _pytest.config import _strtobool
from _pytest.config import Config
from _pytest.config import create_terminal_writer
from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.config.argparsing import Parser
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.store import StoreKey
@ -468,7 +470,7 @@ class LogCaptureFixture:
self.handler.setLevel(handler_orig_level)
@pytest.fixture
@fixture
def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]:
"""Access and control log capturing.
@ -501,7 +503,7 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[i
return int(getattr(logging, log_level, log_level))
except ValueError as e:
# Python logging does not recognise this as a logging level
raise pytest.UsageError(
raise UsageError(
"'{}' is not recognized as a logging level name for "
"'{}'. Please consider passing the "
"logging level num instead.".format(log_level, setting_name)
@ -509,7 +511,7 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[i
# run after terminalreporter/capturemanager are configured
@pytest.hookimpl(trylast=True)
@hookimpl(trylast=True)
def pytest_configure(config: Config) -> None:
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
@ -639,7 +641,7 @@ class LoggingPlugin:
return True
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_sessionstart(self) -> Generator[None, None, None]:
self.log_cli_handler.set_when("sessionstart")
@ -647,7 +649,7 @@ class LoggingPlugin:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection(self) -> Generator[None, None, None]:
self.log_cli_handler.set_when("collection")
@ -655,7 +657,7 @@ class LoggingPlugin:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]:
if session.config.option.collectonly:
yield
@ -669,12 +671,12 @@ class LoggingPlugin:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield # Run all the tests.
@pytest.hookimpl
@hookimpl
def pytest_runtest_logstart(self) -> None:
self.log_cli_handler.reset()
self.log_cli_handler.set_when("start")
@pytest.hookimpl
@hookimpl
def pytest_runtest_logreport(self) -> None:
self.log_cli_handler.set_when("logreport")
@ -695,7 +697,7 @@ class LoggingPlugin:
log = report_handler.stream.getvalue().strip()
item.add_report_section(when, "log", log)
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]:
self.log_cli_handler.set_when("setup")
@ -703,13 +705,13 @@ class LoggingPlugin:
item._store[caplog_records_key] = empty
yield from self._runtest_for(item, "setup")
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]:
self.log_cli_handler.set_when("call")
yield from self._runtest_for(item, "call")
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]:
self.log_cli_handler.set_when("teardown")
@ -717,11 +719,11 @@ class LoggingPlugin:
del item._store[caplog_records_key]
del item._store[caplog_handler_key]
@pytest.hookimpl
@hookimpl
def pytest_runtest_logfinish(self) -> None:
self.log_cli_handler.set_when("finish")
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_sessionfinish(self) -> Generator[None, None, None]:
self.log_cli_handler.set_when("sessionfinish")
@ -729,7 +731,7 @@ class LoggingPlugin:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield
@pytest.hookimpl
@hookimpl
def pytest_unconfigure(self) -> None:
# Close the FileHandler explicitly.
# (logging.shutdown might have lost the weakref?!)

View File

@ -15,9 +15,9 @@ from typing import Tuple
from typing import TypeVar
from typing import Union
import pytest
from _pytest.compat import final
from _pytest.fixtures import fixture
from _pytest.warning_types import PytestWarning
RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$")
@ -271,7 +271,7 @@ class MonkeyPatch:
"""
if not isinstance(value, str):
warnings.warn( # type: ignore[unreachable]
pytest.PytestWarning(
PytestWarning(
"Value of environment variable {name} type should be str, but got "
"{value!r} (type: {type}); converted to str implicitly".format(
name=name, value=value, type=type(value).__name__

View File

@ -34,7 +34,6 @@ import py
from iniconfig import IniConfig
from iniconfig import SectionWrapper
import pytest
from _pytest import timing
from _pytest._code import Source
from _pytest.capture import _get_multicapture
@ -42,17 +41,24 @@ from _pytest.compat import final
from _pytest.config import _PluggyPlugin
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.config import main
from _pytest.config import PytestPluginManager
from _pytest.config.argparsing import Parser
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.outcomes import fail
from _pytest.outcomes import importorskip
from _pytest.outcomes import skip
from _pytest.pathlib import make_numbered_dir
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
from _pytest.tmpdir import TempPathFactory
from _pytest.warning_types import PytestWarning
if TYPE_CHECKING:
from typing_extensions import Literal
@ -143,7 +149,7 @@ class LsofFdLeakChecker:
else:
return True
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]:
lines1 = self.get_open_files()
yield
@ -165,13 +171,13 @@ class LsofFdLeakChecker:
"*** function %s:%s: %s " % item.location,
"See issue #2366",
]
item.warn(pytest.PytestWarning("\n".join(error)))
item.warn(PytestWarning("\n".join(error)))
# used at least by pytest-xdist plugin
@pytest.fixture
@fixture
def _pytest(request: FixtureRequest) -> "PytestArg":
"""Return a helper which offers a gethookrecorder(hook) method which
returns a HookRecorder instance which helps to make assertions about called
@ -257,7 +263,7 @@ class HookRecorder:
break
print("NONAMEMATCH", name, "with", call)
else:
pytest.fail(f"could not find {name!r} check {check!r}")
fail(f"could not find {name!r} check {check!r}")
def popcall(self, name: str) -> ParsedCall:
__tracebackhide__ = True
@ -267,7 +273,7 @@ class HookRecorder:
return call
lines = [f"could not find call {name!r}, in:"]
lines.extend([" %s" % x for x in self.calls])
pytest.fail("\n".join(lines))
fail("\n".join(lines))
def getcall(self, name: str) -> ParsedCall:
values = self.getcalls(name)
@ -417,14 +423,14 @@ class HookRecorder:
self.calls[:] = []
@pytest.fixture
@fixture
def linecomp() -> "LineComp":
"""A :class: `LineComp` instance for checking that an input linearly
contains a sequence of strings."""
return LineComp()
@pytest.fixture(name="LineMatcher")
@fixture(name="LineMatcher")
def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
"""A reference to the :class: `LineMatcher`.
@ -434,7 +440,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
return LineMatcher
@pytest.fixture
@fixture
def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester":
"""
Facilities to write tests/configuration files, execute pytest in isolation, and match
@ -449,7 +455,7 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt
return Pytester(request, tmp_path_factory)
@pytest.fixture
@fixture
def testdir(pytester: "Pytester") -> "Testdir":
"""
Identical to :fixture:`pytester`, and provides an instance whose methods return
@ -460,7 +466,7 @@ def testdir(pytester: "Pytester") -> "Testdir":
return Testdir(pytester)
@pytest.fixture
@fixture
def _sys_snapshot() -> Generator[None, None, None]:
snappaths = SysPathsSnapshot()
snapmods = SysModulesSnapshot()
@ -469,7 +475,7 @@ def _sys_snapshot() -> Generator[None, None, None]:
snappaths.restore()
@pytest.fixture
@fixture
def _config_for_test() -> Generator[Config, None, None]:
from _pytest.config import get_config
@ -495,7 +501,7 @@ class RunResult:
duration: float,
) -> None:
try:
self.ret: Union[int, ExitCode] = pytest.ExitCode(ret)
self.ret: Union[int, ExitCode] = ExitCode(ret)
"""The return value."""
except ValueError:
self.ret = ret
@ -1062,7 +1068,7 @@ class Pytester:
rec.append(self.make_hook_recorder(config.pluginmanager))
plugins.append(Collect())
ret = pytest.main([str(x) for x in args], plugins=plugins)
ret = main([str(x) for x in args], plugins=plugins)
if len(rec) == 1:
reprec = rec.pop()
else:
@ -1448,11 +1454,11 @@ class Pytester:
The pexpect child is returned.
"""
pexpect = pytest.importorskip("pexpect", "3.0")
pexpect = importorskip("pexpect", "3.0")
if hasattr(sys, "pypy_version_info") and "64" in platform.machine():
pytest.skip("pypy-64 bit not supported")
skip("pypy-64 bit not supported")
if not hasattr(pexpect, "spawn"):
pytest.skip("pexpect.spawn not available")
skip("pexpect.spawn not available")
logfile = self.path.joinpath("spawn.out").open("wb")
child = pexpect.spawn(cmd, logfile=logfile, timeout=expect_timeout)
@ -1899,7 +1905,7 @@ class LineMatcher:
__tracebackhide__ = True
log_text = self._log_text
self._log_output = []
pytest.fail(log_text)
fail(log_text)
def str(self) -> str:
"""Return the entire original text."""

View File

@ -29,7 +29,7 @@ import attr
import pluggy
import py
import pytest
import _pytest._version
from _pytest import nodes
from _pytest import timing
from _pytest._code import ExceptionInfo
@ -39,6 +39,7 @@ from _pytest.compat import final
from _pytest.config import _PluggyPlugin
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
from _pytest.nodes import Item
from _pytest.nodes import Node
@ -259,7 +260,7 @@ def getreportopt(config: Config) -> str:
return reportopts
@pytest.hookimpl(trylast=True) # after _pytest.runner
@hookimpl(trylast=True) # after _pytest.runner
def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]:
letter = "F"
if report.passed:
@ -628,7 +629,7 @@ class TerminalReporter:
self._add_stats("error", [report])
elif report.skipped:
self._add_stats("skipped", [report])
items = [x for x in report.result if isinstance(x, pytest.Item)]
items = [x for x in report.result if isinstance(x, Item)]
self._numcollected += len(items)
if self.isatty:
self.report_collect()
@ -673,7 +674,7 @@ class TerminalReporter:
else:
self.write_line(line)
@pytest.hookimpl(trylast=True)
@hookimpl(trylast=True)
def pytest_sessionstart(self, session: "Session") -> None:
self._session = session
self._sessionstarttime = timing.time()
@ -688,7 +689,7 @@ class TerminalReporter:
verinfo = ".".join(map(str, pypy_version_info[:3]))
msg += "[pypy-{}-{}]".format(verinfo, pypy_version_info[3])
msg += ", pytest-{}, py-{}, pluggy-{}".format(
pytest.__version__, py.__version__, pluggy.__version__
_pytest._version.version, py.__version__, pluggy.__version__
)
if (
self.verbosity > 0
@ -783,7 +784,7 @@ class TerminalReporter:
for line in doc.splitlines():
self._tw.line("{}{}".format(indent + " ", line))
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_sessionfinish(
self, session: "Session", exitstatus: Union[int, ExitCode]
):
@ -810,7 +811,7 @@ class TerminalReporter:
self.write_sep("!", str(session.shouldstop), red=True)
self.summary_stats()
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_terminal_summary(self) -> Generator[None, None, None]:
self.summary_errors()
self.summary_failures()

View File

@ -8,13 +8,13 @@ from typing import Optional
import attr
import py
import pytest
from .pathlib import ensure_reset_dir
from .pathlib import LOCK_TIMEOUT
from .pathlib import make_numbered_dir
from .pathlib import make_numbered_dir_with_cleanup
from _pytest.compat import final
from _pytest.config import Config
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
@ -146,14 +146,14 @@ def pytest_configure(config: Config) -> None:
mp.setattr(config, "_tmpdirhandler", t, raising=False)
@pytest.fixture(scope="session")
@fixture(scope="session")
def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
"""Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session."""
# Set dynamically by pytest_configure() above.
return request.config._tmpdirhandler # type: ignore
@pytest.fixture(scope="session")
@fixture(scope="session")
def tmp_path_factory(request: FixtureRequest) -> TempPathFactory:
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session."""
# Set dynamically by pytest_configure() above.
@ -168,7 +168,7 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path:
return factory.mktemp(name, numbered=True)
@pytest.fixture
@fixture
def tmpdir(tmp_path: Path) -> py.path.local:
"""Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
@ -181,7 +181,7 @@ def tmpdir(tmp_path: Path) -> py.path.local:
return py.path.local(tmp_path)
@pytest.fixture
@fixture
def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path:
"""Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary