385 lines
13 KiB
Python
385 lines
13 KiB
Python
import os.path
|
|
import sys
|
|
import unittest.mock
|
|
from textwrap import dedent
|
|
|
|
import py
|
|
|
|
import pytest
|
|
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 Path
|
|
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()
|