404 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			404 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
| import os.path
 | |
| import sys
 | |
| import unittest.mock
 | |
| from pathlib import Path
 | |
| from textwrap import dedent
 | |
| 
 | |
| import py
 | |
| 
 | |
| import pytest
 | |
| from _pytest.pathlib import bestrelpath
 | |
| from _pytest.pathlib import commonpath
 | |
| from _pytest.pathlib import ensure_deletable
 | |
| from _pytest.pathlib import fnmatch_ex
 | |
| from _pytest.pathlib import get_extended_length_path_str
 | |
| 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 resolve_package_path
 | |
| 
 | |
| 
 | |
| class TestFNMatcherPort:
 | |
|     """Test that our port of py.common.FNMatcher (fnmatch_ex) produces the
 | |
|     same results as the original py.path.local.fnmatch method."""
 | |
| 
 | |
|     @pytest.fixture(params=["pathlib", "py.path"])
 | |
|     def match(self, request):
 | |
|         if request.param == "py.path":
 | |
| 
 | |
|             def match_(pattern, path):
 | |
|                 return py.path.local(path).fnmatch(pattern)
 | |
| 
 | |
|         else:
 | |
|             assert request.param == "pathlib"
 | |
| 
 | |
|             def match_(pattern, path):
 | |
|                 return fnmatch_ex(pattern, path)
 | |
| 
 | |
|         return match_
 | |
| 
 | |
|     if sys.platform == "win32":
 | |
|         drv1 = "c:"
 | |
|         drv2 = "d:"
 | |
|     else:
 | |
|         drv1 = "/c"
 | |
|         drv2 = "/d"
 | |
| 
 | |
|     @pytest.mark.parametrize(
 | |
|         "pattern, path",
 | |
|         [
 | |
|             ("*.py", "foo.py"),
 | |
|             ("*.py", "bar/foo.py"),
 | |
|             ("test_*.py", "foo/test_foo.py"),
 | |
|             ("tests/*.py", "tests/foo.py"),
 | |
|             (drv1 + "/*.py", drv1 + "/foo.py"),
 | |
|             (drv1 + "/foo/*.py", drv1 + "/foo/foo.py"),
 | |
|             ("tests/**/test*.py", "tests/foo/test_foo.py"),
 | |
|             ("tests/**/doc/test*.py", "tests/foo/bar/doc/test_foo.py"),
 | |
|             ("tests/**/doc/**/test*.py", "tests/foo/doc/bar/test_foo.py"),
 | |
|         ],
 | |
|     )
 | |
|     def test_matching(self, match, pattern, path):
 | |
|         assert match(pattern, path)
 | |
| 
 | |
|     def test_matching_abspath(self, match):
 | |
|         abspath = os.path.abspath(os.path.join("tests/foo.py"))
 | |
|         assert match("tests/foo.py", abspath)
 | |
| 
 | |
|     @pytest.mark.parametrize(
 | |
|         "pattern, path",
 | |
|         [
 | |
|             ("*.py", "foo.pyc"),
 | |
|             ("*.py", "foo/foo.pyc"),
 | |
|             ("tests/*.py", "foo/foo.py"),
 | |
|             (drv1 + "/*.py", drv2 + "/foo.py"),
 | |
|             (drv1 + "/foo/*.py", drv2 + "/foo/foo.py"),
 | |
|             ("tests/**/test*.py", "tests/foo.py"),
 | |
|             ("tests/**/test*.py", "foo/test_foo.py"),
 | |
|             ("tests/**/doc/test*.py", "tests/foo/bar/doc/foo.py"),
 | |
|             ("tests/**/doc/test*.py", "tests/foo/bar/test_foo.py"),
 | |
|         ],
 | |
|     )
 | |
|     def test_not_matching(self, 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.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):
 | |
|     """Ensure that deleting a numbered dir does not fail because of OSErrors (#4262)."""
 | |
|     path = tmp_path / "temp-1"
 | |
|     path.mkdir()
 | |
| 
 | |
|     def renamed_failed(*args):
 | |
|         raise OSError("access denied")
 | |
| 
 | |
|     monkeypatch.setattr(Path, "rename", renamed_failed)
 | |
| 
 | |
|     lock_path = get_lock_path(path)
 | |
|     maybe_delete_a_numbered_dir(path)
 | |
|     assert not lock_path.is_file()
 | |
| 
 | |
| 
 | |
| def test_long_path_during_cleanup(tmp_path):
 | |
|     """Ensure that deleting long path works (particularly on Windows (#6775))."""
 | |
|     path = (tmp_path / ("a" * 250)).resolve()
 | |
|     if sys.platform == "win32":
 | |
|         # make sure that the full path is > 260 characters without any
 | |
|         # component being over 260 characters
 | |
|         assert len(str(path)) > 260
 | |
|         extended_path = "\\\\?\\" + str(path)
 | |
|     else:
 | |
|         extended_path = str(path)
 | |
|     os.mkdir(extended_path)
 | |
|     assert os.path.isdir(extended_path)
 | |
|     maybe_delete_a_numbered_dir(path)
 | |
|     assert not os.path.isdir(extended_path)
 | |
| 
 | |
| 
 | |
| def test_get_extended_length_path_str():
 | |
|     assert get_extended_length_path_str(r"c:\foo") == r"\\?\c:\foo"
 | |
|     assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo"
 | |
|     assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo"
 | |
|     assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo"
 | |
| 
 | |
| 
 | |
| def test_suppress_error_removing_lock(tmp_path):
 | |
|     """ensure_deletable should be resilient if lock file cannot be removed (#5456, #7491)"""
 | |
|     path = tmp_path / "dir"
 | |
|     path.mkdir()
 | |
|     lock = get_lock_path(path)
 | |
|     lock.touch()
 | |
|     mtime = lock.stat().st_mtime
 | |
| 
 | |
|     with unittest.mock.patch.object(Path, "unlink", side_effect=OSError) as m:
 | |
|         assert not ensure_deletable(
 | |
|             path, consider_lock_dead_if_created_before=mtime + 30
 | |
|         )
 | |
|         assert m.call_count == 1
 | |
|     assert lock.is_file()
 | |
| 
 | |
|     with unittest.mock.patch.object(Path, "is_file", side_effect=OSError) as m:
 | |
|         assert not ensure_deletable(
 | |
|             path, consider_lock_dead_if_created_before=mtime + 30
 | |
|         )
 | |
|         assert m.call_count == 1
 | |
|     assert lock.is_file()
 | |
| 
 | |
|     # check now that we can remove the lock file in normal circumstances
 | |
|     assert ensure_deletable(path, consider_lock_dead_if_created_before=mtime + 30)
 | |
|     assert not lock.is_file()
 | |
| 
 | |
| 
 | |
| def test_bestrelpath() -> None:
 | |
|     curdir = Path("/foo/bar/baz/path")
 | |
|     assert bestrelpath(curdir, curdir) == "."
 | |
|     assert bestrelpath(curdir, curdir / "hello" / "world") == "hello" + os.sep + "world"
 | |
|     assert bestrelpath(curdir, curdir.parent / "sister") == ".." + os.sep + "sister"
 | |
|     assert bestrelpath(curdir, curdir.parent) == ".."
 | |
|     assert bestrelpath(curdir, Path("hello")) == "hello"
 | |
| 
 | |
| 
 | |
| def test_commonpath() -> None:
 | |
|     path = Path("/foo/bar/baz/path")
 | |
|     subpath = path / "sampledir"
 | |
|     assert commonpath(path, subpath) == path
 | |
|     assert commonpath(subpath, path) == path
 | |
|     assert commonpath(Path(str(path) + "suffix"), path) == path.parent
 | |
|     assert commonpath(path, path.parent.parent) == path.parent.parent
 |