From 5e883f51959f900fcf985493ce2d09e07bad7e00 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 16 Oct 2021 10:37:02 +0300 Subject: [PATCH] Move tmpdir to legacypath plugin --- doc/en/reference/reference.rst | 2 +- src/_pytest/legacypath.py | 72 ++++++++++++++++++++++++++++++++++ src/_pytest/tmpdir.py | 52 ------------------------ src/pytest/__init__.py | 2 - testing/test_legacypath.py | 42 ++++++++++++++++++++ testing/test_tmpdir.py | 24 +----------- 6 files changed, 117 insertions(+), 77 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 304c789dea..9bd242c0b2 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -638,7 +638,7 @@ tmpdir :ref:`tmpdir and tmpdir_factory` -.. autofunction:: _pytest.tmpdir.tmpdir() +.. autofunction:: _pytest.legacypath.tmpdir() :no-auto-options: diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index cebcd1c3cf..e8a239cf28 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,10 +1,12 @@ """Add backward compatibility support for the legacy py path type.""" import subprocess +from pathlib import Path from typing import List from typing import Optional from typing import TYPE_CHECKING from typing import Union +import attr from iniconfig import SectionWrapper import pytest @@ -252,3 +254,73 @@ def testdir(pytester: pytest.Pytester) -> Testdir: New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. """ return Testdir(pytester, _ispytest=True) + + +@final +@attr.s(init=False, auto_attribs=True) +class TempdirFactory: + """Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH`` + for :class:``TempPathFactory``.""" + + _tmppath_factory: pytest.TempPathFactory + + def __init__( + self, tmppath_factory: pytest.TempPathFactory, *, _ispytest: bool = False + ) -> None: + check_ispytest(_ispytest) + self._tmppath_factory = tmppath_factory + + def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: + """Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object.""" + return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) + + def getbasetemp(self) -> LEGACY_PATH: + """Backward compat wrapper for ``_tmppath_factory.getbasetemp``.""" + return legacy_path(self._tmppath_factory.getbasetemp().resolve()) + + +pytest.TempdirFactory = TempdirFactory # type: ignore[attr-defined] + + +@pytest.fixture(scope="session") +def tmpdir_factory(request: pytest.FixtureRequest) -> TempdirFactory: + """Return a :class:`pytest.TempdirFactory` instance for the test session.""" + # Set dynamically by pytest_configure(). + return request.config._tmpdirhandler # type: ignore + + +@pytest.fixture +def tmpdir(tmp_path: Path) -> LEGACY_PATH: + """Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + By default, a new base temporary directory is created each test session, + and old bases are removed after 3 sessions, to aid in debugging. If + ``--basetemp`` is used then it is cleared each session. See :ref:`base + temporary directory`. + + The returned object is a `legacy_path`_ object. + + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html + """ + return legacy_path(tmp_path) + + +def pytest_configure(config: pytest.Config) -> None: + mp = pytest.MonkeyPatch() + config.add_cleanup(mp.undo) + + # Create TmpdirFactory and attach it to the config object. + # + # This is to comply with existing plugins which expect the handler to be + # available at pytest_configure time, but ideally should be moved entirely + # to the tmpdir_factory session fixture. + try: + tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined] + except AttributeError: + # tmpdir plugin is blocked. + pass + else: + _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True) + mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 905cf0e5a9..f901fd5727 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -13,8 +13,6 @@ from .pathlib import make_numbered_dir_with_cleanup from .pathlib import rm_rf from _pytest.compat import final -from _pytest.compat import LEGACY_PATH -from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture @@ -157,29 +155,6 @@ def getbasetemp(self) -> Path: return basetemp -@final -@attr.s(init=False, auto_attribs=True) -class TempdirFactory: - """Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH`` - for :class:``TempPathFactory``.""" - - _tmppath_factory: TempPathFactory - - def __init__( - self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False - ) -> None: - check_ispytest(_ispytest) - self._tmppath_factory = tmppath_factory - - def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object.""" - return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) - - def getbasetemp(self) -> LEGACY_PATH: - """Backward compat wrapper for ``_tmppath_factory.getbasetemp``.""" - return legacy_path(self._tmppath_factory.getbasetemp().resolve()) - - def get_user() -> Optional[str]: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" @@ -201,16 +176,7 @@ def pytest_configure(config: Config) -> None: mp = MonkeyPatch() config.add_cleanup(mp.undo) _tmp_path_factory = TempPathFactory.from_config(config, _ispytest=True) - _tmpdirhandler = TempdirFactory(_tmp_path_factory, _ispytest=True) mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False) - mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False) - - -@fixture(scope="session") -def tmpdir_factory(request: FixtureRequest) -> TempdirFactory: - """Return a :class:`pytest.TempdirFactory` instance for the test session.""" - # Set dynamically by pytest_configure() above. - return request.config._tmpdirhandler # type: ignore @fixture(scope="session") @@ -228,24 +194,6 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: return factory.mktemp(name, numbered=True) -@fixture -def tmpdir(tmp_path: Path) -> LEGACY_PATH: - """Return a temporary directory path object which is unique to each test - function invocation, created as a sub directory of the base temporary - directory. - - By default, a new base temporary directory is created each test session, - and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. - - The returned object is a `legacy_path`_ object. - - .. _legacy_path: https://py.readthedocs.io/en/latest/path.html - """ - return legacy_path(tmp_path) - - @fixture def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path: """Return a temporary directory path object which is unique to each test diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 3d19680db5..83a0df11c3 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -60,7 +60,6 @@ from _pytest.runner import CallInfo from _pytest.stash import Stash from _pytest.stash import StashKey -from _pytest.tmpdir import TempdirFactory from _pytest.tmpdir import TempPathFactory from _pytest.warning_types import PytestAssertRewriteWarning from _pytest.warning_types import PytestCacheWarning @@ -144,7 +143,6 @@ "StashKey", "version_tuple", "TempPathFactory", - "TempdirFactory", "UsageError", "WarningsRecorder", "warns", diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index 0550fe7dc7..89c4fa862f 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -1,4 +1,8 @@ +from pathlib import Path + import pytest +from _pytest.compat import LEGACY_PATH +from _pytest.legacypath import TempdirFactory from _pytest.legacypath import Testdir @@ -25,3 +29,41 @@ def test_testdir_makefile_ext_empty_string_makes_file(testdir: Testdir) -> None: """For backwards compat #8192""" p1 = testdir.makefile("", "") assert "test_testdir_makefile" in str(p1) + + +def attempt_symlink_to(path: str, to_path: str) -> None: + """Try to make a symlink from "path" to "to_path", skipping in case this platform + does not support it or we don't have sufficient privileges (common on Windows).""" + try: + Path(path).symlink_to(Path(to_path)) + except OSError: + pytest.skip("could not create symbolic link") + + +def test_tmpdir_factory( + tmpdir_factory: TempdirFactory, + tmp_path_factory: pytest.TempPathFactory, +) -> None: + assert str(tmpdir_factory.getbasetemp()) == str(tmp_path_factory.getbasetemp()) + dir = tmpdir_factory.mktemp("foo") + assert dir.exists() + + +def test_tmpdir_equals_tmp_path(tmpdir: LEGACY_PATH, tmp_path: Path) -> None: + assert Path(tmpdir) == tmp_path + + +def test_tmpdir_always_is_realpath(pytester: pytest.Pytester) -> None: + # See test_tmp_path_always_is_realpath. + realtemp = pytester.mkdir("myrealtemp") + linktemp = pytester.path.joinpath("symlinktemp") + attempt_symlink_to(str(linktemp), str(realtemp)) + p = pytester.makepyfile( + """ + def test_1(tmpdir): + import os + assert os.path.realpath(str(tmpdir)) == str(tmpdir) + """ + ) + result = pytester.runpytest("-s", p, "--basetemp=%s/bt" % linktemp) + assert not result.ret diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 4dff9dff00..4f7c538470 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -118,8 +118,8 @@ def test_abs_path(tmp_path_factory): result.stdout.fnmatch_lines("*ValueError*") -def test_tmpdir_always_is_realpath(pytester: Pytester) -> None: - # the reason why tmpdir should be a realpath is that +def test_tmp_path_always_is_realpath(pytester: Pytester, monkeypatch) -> None: + # the reason why tmp_path should be a realpath is that # when you cd to it and do "os.getcwd()" you will anyway # get the realpath. Using the symlinked path can thus # easily result in path-inequality @@ -128,22 +128,6 @@ def test_tmpdir_always_is_realpath(pytester: Pytester) -> None: realtemp = pytester.mkdir("myrealtemp") linktemp = pytester.path.joinpath("symlinktemp") attempt_symlink_to(linktemp, str(realtemp)) - p = pytester.makepyfile( - """ - def test_1(tmpdir): - import os - assert os.path.realpath(str(tmpdir)) == str(tmpdir) - """ - ) - result = pytester.runpytest("-s", p, "--basetemp=%s/bt" % linktemp) - assert not result.ret - - -def test_tmp_path_always_is_realpath(pytester: Pytester, monkeypatch) -> None: - # for reasoning see: test_tmpdir_always_is_realpath test-case - realtemp = pytester.mkdir("myrealtemp") - linktemp = pytester.path.joinpath("symlinktemp") - attempt_symlink_to(linktemp, str(realtemp)) monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(linktemp)) pytester.makepyfile( """ @@ -423,10 +407,6 @@ def attempt_symlink_to(path, to_path): pytest.skip("could not create symbolic link") -def test_tmpdir_equals_tmp_path(tmpdir, tmp_path): - assert Path(tmpdir) == tmp_path - - def test_basetemp_with_read_only_files(pytester: Pytester) -> None: """Integration test for #5524""" pytester.makepyfile(