testing/test_pathlib: parametrize namespace package option

Test with namespace packages support even when it will not find namespace packages to ensure it will at least not give weird results or crashes.
This commit is contained in:
Bruno Oliveira 2024-03-02 12:03:08 -03:00
parent 111c0d910e
commit aac720abc9
1 changed files with 93 additions and 56 deletions

View File

@ -100,6 +100,15 @@ class TestFNMatcherPort:
assert not fnmatch_ex(pattern, path) assert not fnmatch_ex(pattern, path)
@pytest.fixture(params=[True, False])
def ns_param(request: pytest.FixtureRequest) -> bool:
"""
Simple parametrized fixture for tests which call import_path() with consider_namespace_packages
using True and False.
"""
return bool(request.param)
class TestImportPath: class TestImportPath:
""" """
@ -170,32 +179,32 @@ class TestImportPath:
encoding="utf-8", encoding="utf-8",
) )
def test_smoke_test(self, path1: Path) -> None: def test_smoke_test(self, path1: Path, ns_param: bool) -> None:
obj = import_path( obj = import_path(
path1 / "execfile.py", root=path1, consider_namespace_packages=False path1 / "execfile.py", root=path1, consider_namespace_packages=ns_param
) )
assert obj.x == 42 # type: ignore[attr-defined] assert obj.x == 42 # type: ignore[attr-defined]
assert obj.__name__ == "execfile" assert obj.__name__ == "execfile"
def test_import_path_missing_file(self, path1: Path) -> None: def test_import_path_missing_file(self, path1: Path, ns_param: bool) -> None:
with pytest.raises(ImportPathMismatchError): with pytest.raises(ImportPathMismatchError):
import_path( import_path(
path1 / "sampledir", root=path1, consider_namespace_packages=False path1 / "sampledir", root=path1, consider_namespace_packages=ns_param
) )
def test_renamed_dir_creates_mismatch( def test_renamed_dir_creates_mismatch(
self, tmp_path: Path, monkeypatch: MonkeyPatch self, tmp_path: Path, monkeypatch: MonkeyPatch, ns_param: bool
) -> None: ) -> None:
tmp_path.joinpath("a").mkdir() tmp_path.joinpath("a").mkdir()
p = tmp_path.joinpath("a", "test_x123.py") p = tmp_path.joinpath("a", "test_x123.py")
p.touch() p.touch()
import_path(p, root=tmp_path, consider_namespace_packages=False) import_path(p, root=tmp_path, consider_namespace_packages=ns_param)
tmp_path.joinpath("a").rename(tmp_path.joinpath("b")) tmp_path.joinpath("a").rename(tmp_path.joinpath("b"))
with pytest.raises(ImportPathMismatchError): with pytest.raises(ImportPathMismatchError):
import_path( import_path(
tmp_path.joinpath("b", "test_x123.py"), tmp_path.joinpath("b", "test_x123.py"),
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
# Errors can be ignored. # Errors can be ignored.
@ -203,7 +212,7 @@ class TestImportPath:
import_path( import_path(
tmp_path.joinpath("b", "test_x123.py"), tmp_path.joinpath("b", "test_x123.py"),
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
# PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error.
@ -212,69 +221,71 @@ class TestImportPath:
import_path( import_path(
tmp_path.joinpath("b", "test_x123.py"), tmp_path.joinpath("b", "test_x123.py"),
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
def test_messy_name(self, tmp_path: Path) -> None: def test_messy_name(self, tmp_path: Path, ns_param: bool) -> None:
# https://bitbucket.org/hpk42/py-trunk/issue/129 # https://bitbucket.org/hpk42/py-trunk/issue/129
path = tmp_path / "foo__init__.py" path = tmp_path / "foo__init__.py"
path.touch() path.touch()
module = import_path(path, root=tmp_path, consider_namespace_packages=False) module = import_path(path, root=tmp_path, consider_namespace_packages=ns_param)
assert module.__name__ == "foo__init__" assert module.__name__ == "foo__init__"
def test_dir(self, tmp_path: Path) -> None: def test_dir(self, tmp_path: Path, ns_param: bool) -> None:
p = tmp_path / "hello_123" p = tmp_path / "hello_123"
p.mkdir() p.mkdir()
p_init = p / "__init__.py" p_init = p / "__init__.py"
p_init.touch() p_init.touch()
m = import_path(p, root=tmp_path, consider_namespace_packages=False) m = import_path(p, root=tmp_path, consider_namespace_packages=ns_param)
assert m.__name__ == "hello_123" assert m.__name__ == "hello_123"
m = import_path(p_init, root=tmp_path, consider_namespace_packages=False) m = import_path(p_init, root=tmp_path, consider_namespace_packages=ns_param)
assert m.__name__ == "hello_123" assert m.__name__ == "hello_123"
def test_a(self, path1: Path) -> None: def test_a(self, path1: Path, ns_param: bool) -> None:
otherdir = path1 / "otherdir" otherdir = path1 / "otherdir"
mod = import_path( mod = import_path(
otherdir / "a.py", root=path1, consider_namespace_packages=False otherdir / "a.py", root=path1, consider_namespace_packages=ns_param
) )
assert mod.result == "got it" # type: ignore[attr-defined] assert mod.result == "got it" # type: ignore[attr-defined]
assert mod.__name__ == "otherdir.a" assert mod.__name__ == "otherdir.a"
def test_b(self, path1: Path) -> None: def test_b(self, path1: Path, ns_param: bool) -> None:
otherdir = path1 / "otherdir" otherdir = path1 / "otherdir"
mod = import_path( mod = import_path(
otherdir / "b.py", root=path1, consider_namespace_packages=False otherdir / "b.py", root=path1, consider_namespace_packages=ns_param
) )
assert mod.stuff == "got it" # type: ignore[attr-defined] assert mod.stuff == "got it" # type: ignore[attr-defined]
assert mod.__name__ == "otherdir.b" assert mod.__name__ == "otherdir.b"
def test_c(self, path1: Path) -> None: def test_c(self, path1: Path, ns_param: bool) -> None:
otherdir = path1 / "otherdir" otherdir = path1 / "otherdir"
mod = import_path( mod = import_path(
otherdir / "c.py", root=path1, consider_namespace_packages=False otherdir / "c.py", root=path1, consider_namespace_packages=ns_param
) )
assert mod.value == "got it" # type: ignore[attr-defined] assert mod.value == "got it" # type: ignore[attr-defined]
def test_d(self, path1: Path) -> None: def test_d(self, path1: Path, ns_param: bool) -> None:
otherdir = path1 / "otherdir" otherdir = path1 / "otherdir"
mod = import_path( mod = import_path(
otherdir / "d.py", root=path1, consider_namespace_packages=False otherdir / "d.py", root=path1, consider_namespace_packages=ns_param
) )
assert mod.value2 == "got it" # type: ignore[attr-defined] assert mod.value2 == "got it" # type: ignore[attr-defined]
def test_import_after(self, tmp_path: Path) -> None: def test_import_after(self, tmp_path: Path, ns_param: bool) -> None:
tmp_path.joinpath("xxxpackage").mkdir() tmp_path.joinpath("xxxpackage").mkdir()
tmp_path.joinpath("xxxpackage", "__init__.py").touch() tmp_path.joinpath("xxxpackage", "__init__.py").touch()
mod1path = tmp_path.joinpath("xxxpackage", "module1.py") mod1path = tmp_path.joinpath("xxxpackage", "module1.py")
mod1path.touch() mod1path.touch()
mod1 = import_path(mod1path, root=tmp_path, consider_namespace_packages=False) mod1 = import_path(
mod1path, root=tmp_path, consider_namespace_packages=ns_param
)
assert mod1.__name__ == "xxxpackage.module1" assert mod1.__name__ == "xxxpackage.module1"
from xxxpackage import module1 from xxxpackage import module1
assert module1 is mod1 assert module1 is mod1
def test_check_filepath_consistency( def test_check_filepath_consistency(
self, monkeypatch: MonkeyPatch, tmp_path: Path self, monkeypatch: MonkeyPatch, tmp_path: Path, ns_param: bool
) -> None: ) -> None:
name = "pointsback123" name = "pointsback123"
p = tmp_path.joinpath(name + ".py") p = tmp_path.joinpath(name + ".py")
@ -287,7 +298,7 @@ class TestImportPath:
mod.__file__ = str(pseudopath) mod.__file__ = str(pseudopath)
mp.setitem(sys.modules, name, mod) mp.setitem(sys.modules, name, mod)
newmod = import_path( newmod = import_path(
p, root=tmp_path, consider_namespace_packages=False p, root=tmp_path, consider_namespace_packages=ns_param
) )
assert mod == newmod assert mod == newmod
mod = ModuleType(name) mod = ModuleType(name)
@ -296,31 +307,31 @@ class TestImportPath:
mod.__file__ = str(pseudopath) mod.__file__ = str(pseudopath)
monkeypatch.setitem(sys.modules, name, mod) monkeypatch.setitem(sys.modules, name, mod)
with pytest.raises(ImportPathMismatchError) as excinfo: with pytest.raises(ImportPathMismatchError) as excinfo:
import_path(p, root=tmp_path, consider_namespace_packages=False) import_path(p, root=tmp_path, consider_namespace_packages=ns_param)
modname, modfile, orig = excinfo.value.args modname, modfile, orig = excinfo.value.args
assert modname == name assert modname == name
assert modfile == str(pseudopath) assert modfile == str(pseudopath)
assert orig == p assert orig == p
assert issubclass(ImportPathMismatchError, ImportError) assert issubclass(ImportPathMismatchError, ImportError)
def test_ensuresyspath_append(self, tmp_path: Path) -> None: def test_ensuresyspath_append(self, tmp_path: Path, ns_param: bool) -> None:
root1 = tmp_path / "root1" root1 = tmp_path / "root1"
root1.mkdir() root1.mkdir()
file1 = root1 / "x123.py" file1 = root1 / "x123.py"
file1.touch() file1.touch()
assert str(root1) not in sys.path assert str(root1) not in sys.path
import_path( import_path(
file1, mode="append", root=tmp_path, consider_namespace_packages=False file1, mode="append", root=tmp_path, consider_namespace_packages=ns_param
) )
assert str(root1) == sys.path[-1] assert str(root1) == sys.path[-1]
assert str(root1) not in sys.path[:-1] assert str(root1) not in sys.path[:-1]
def test_invalid_path(self, tmp_path: Path) -> None: def test_invalid_path(self, tmp_path: Path, ns_param: bool) -> None:
with pytest.raises(ImportError): with pytest.raises(ImportError):
import_path( import_path(
tmp_path / "invalid.py", tmp_path / "invalid.py",
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
@pytest.fixture @pytest.fixture
@ -336,14 +347,18 @@ class TestImportPath:
sys.modules.pop(module_name, None) sys.modules.pop(module_name, None)
def test_importmode_importlib( def test_importmode_importlib(
self, simple_module: Path, tmp_path: Path, request: pytest.FixtureRequest self,
simple_module: Path,
tmp_path: Path,
request: pytest.FixtureRequest,
ns_param: bool,
) -> None: ) -> None:
"""`importlib` mode does not change sys.path.""" """`importlib` mode does not change sys.path."""
module = import_path( module = import_path(
simple_module, simple_module,
mode="importlib", mode="importlib",
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert module.foo(2) == 42 # type: ignore[attr-defined] assert module.foo(2) == 42 # type: ignore[attr-defined]
assert str(simple_module.parent) not in sys.path assert str(simple_module.parent) not in sys.path
@ -353,25 +368,29 @@ class TestImportPath:
assert "_src.tests" in sys.modules assert "_src.tests" in sys.modules
def test_remembers_previous_imports( def test_remembers_previous_imports(
self, simple_module: Path, tmp_path: Path self, simple_module: Path, tmp_path: Path, ns_param: bool
) -> None: ) -> None:
"""`importlib` mode called remembers previous module (#10341, #10811).""" """`importlib` mode called remembers previous module (#10341, #10811)."""
module1 = import_path( module1 = import_path(
simple_module, simple_module,
mode="importlib", mode="importlib",
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
module2 = import_path( module2 = import_path(
simple_module, simple_module,
mode="importlib", mode="importlib",
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert module1 is module2 assert module1 is module2
def test_no_meta_path_found( def test_no_meta_path_found(
self, simple_module: Path, monkeypatch: MonkeyPatch, tmp_path: Path self,
simple_module: Path,
monkeypatch: MonkeyPatch,
tmp_path: Path,
ns_param: bool,
) -> None: ) -> None:
"""Even without any meta_path should still import module.""" """Even without any meta_path should still import module."""
monkeypatch.setattr(sys, "meta_path", []) monkeypatch.setattr(sys, "meta_path", [])
@ -379,7 +398,7 @@ class TestImportPath:
simple_module, simple_module,
mode="importlib", mode="importlib",
root=tmp_path, root=tmp_path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert module.foo(2) == 42 # type: ignore[attr-defined] assert module.foo(2) == 42 # type: ignore[attr-defined]
@ -541,7 +560,9 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
class TestImportLibMode: class TestImportLibMode:
def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None: def test_importmode_importlib_with_dataclass(
self, tmp_path: Path, ns_param: bool
) -> None:
"""Ensure that importlib mode works with a module containing dataclasses (#7856).""" """Ensure that importlib mode works with a module containing dataclasses (#7856)."""
fn = tmp_path.joinpath("_src/tests/test_dataclass.py") fn = tmp_path.joinpath("_src/tests/test_dataclass.py")
fn.parent.mkdir(parents=True) fn.parent.mkdir(parents=True)
@ -559,14 +580,16 @@ class TestImportLibMode:
) )
module = import_path( module = import_path(
fn, mode="importlib", root=tmp_path, consider_namespace_packages=False fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
) )
Data: Any = getattr(module, "Data") Data: Any = getattr(module, "Data")
data = Data(value="foo") data = Data(value="foo")
assert data.value == "foo" assert data.value == "foo"
assert data.__module__ == "_src.tests.test_dataclass" assert data.__module__ == "_src.tests.test_dataclass"
def test_importmode_importlib_with_pickle(self, tmp_path: Path) -> None: def test_importmode_importlib_with_pickle(
self, tmp_path: Path, ns_param: bool
) -> None:
"""Ensure that importlib mode works with pickle (#7859).""" """Ensure that importlib mode works with pickle (#7859)."""
fn = tmp_path.joinpath("_src/tests/test_pickle.py") fn = tmp_path.joinpath("_src/tests/test_pickle.py")
fn.parent.mkdir(parents=True) fn.parent.mkdir(parents=True)
@ -587,14 +610,14 @@ class TestImportLibMode:
) )
module = import_path( module = import_path(
fn, mode="importlib", root=tmp_path, consider_namespace_packages=False fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
) )
round_trip = getattr(module, "round_trip") round_trip = getattr(module, "round_trip")
action = round_trip() action = round_trip()
assert action() == 42 assert action() == 42
def test_importmode_importlib_with_pickle_separate_modules( def test_importmode_importlib_with_pickle_separate_modules(
self, tmp_path: Path self, tmp_path: Path, ns_param: bool
) -> None: ) -> None:
""" """
Ensure that importlib mode works can load pickles that look similar but are Ensure that importlib mode works can load pickles that look similar but are
@ -639,12 +662,12 @@ class TestImportLibMode:
return pickle.loads(s) return pickle.loads(s)
module = import_path( module = import_path(
fn1, mode="importlib", root=tmp_path, consider_namespace_packages=False fn1, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
) )
Data1 = getattr(module, "Data") Data1 = getattr(module, "Data")
module = import_path( module = import_path(
fn2, mode="importlib", root=tmp_path, consider_namespace_packages=False fn2, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
) )
Data2 = getattr(module, "Data") Data2 = getattr(module, "Data")
@ -694,7 +717,9 @@ class TestImportLibMode:
# Create the __init__.py files, it should now resolve to a proper module name. # Create the __init__.py files, it should now resolve to a proper module name.
(tmp_path / "src/app/__init__.py").touch() (tmp_path / "src/app/__init__.py").touch()
(tmp_path / "src/app/core/__init__.py").touch() (tmp_path / "src/app/core/__init__.py").touch()
assert resolve_pkg_root_and_module_name(models_py) == ( assert resolve_pkg_root_and_module_name(
models_py, consider_namespace_packages=True
) == (
tmp_path / "src", tmp_path / "src",
"app.core.models", "app.core.models",
) )
@ -707,6 +732,12 @@ class TestImportLibMode:
tmp_path, tmp_path,
"src.app.core.models", "src.app.core.models",
) )
assert resolve_pkg_root_and_module_name(
models_py, consider_namespace_packages=False
) == (
tmp_path / "src",
"app.core.models",
)
def test_insert_missing_modules( def test_insert_missing_modules(
self, monkeypatch: MonkeyPatch, tmp_path: Path self, monkeypatch: MonkeyPatch, tmp_path: Path
@ -739,7 +770,9 @@ class TestImportLibMode:
assert modules["xxx"].tests is modules["xxx.tests"] assert modules["xxx"].tests is modules["xxx.tests"]
assert modules["xxx.tests"].foo is modules["xxx.tests.foo"] assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]
def test_importlib_package(self, monkeypatch: MonkeyPatch, tmp_path: Path): def test_importlib_package(
self, monkeypatch: MonkeyPatch, tmp_path: Path, ns_param: bool
):
""" """
Importing a package using --importmode=importlib should not import the Importing a package using --importmode=importlib should not import the
package's __init__.py file more than once (#11306). package's __init__.py file more than once (#11306).
@ -780,7 +813,7 @@ class TestImportLibMode:
init, init,
root=tmp_path, root=tmp_path,
mode=ImportMode.importlib, mode=ImportMode.importlib,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert len(mod.instance.INSTANCES) == 1 assert len(mod.instance.INSTANCES) == 1
@ -889,7 +922,7 @@ class TestImportLibMode:
return (site_packages / "app/core.py"), test_path1, test_path2 return (site_packages / "app/core.py"), test_path1, test_path2
def test_import_using_normal_mechanism_first( def test_import_using_normal_mechanism_first(
self, monkeypatch: MonkeyPatch, pytester: Pytester self, monkeypatch: MonkeyPatch, pytester: Pytester, ns_param: bool
) -> None: ) -> None:
""" """
Test import_path imports from the canonical location when possible first, only Test import_path imports from the canonical location when possible first, only
@ -904,7 +937,7 @@ class TestImportLibMode:
core_py, core_py,
mode="importlib", mode="importlib",
root=pytester.path, root=pytester.path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert mod.__name__ == "app.core" assert mod.__name__ == "app.core"
assert mod.__file__ and Path(mod.__file__) == core_py assert mod.__file__ and Path(mod.__file__) == core_py
@ -916,19 +949,19 @@ class TestImportLibMode:
test_path1, test_path1,
mode="importlib", mode="importlib",
root=pytester.path, root=pytester.path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert mod.__name__ == "_tests.a.test_core" assert mod.__name__ == "_tests.a.test_core"
mod = import_path( mod = import_path(
test_path2, test_path2,
mode="importlib", mode="importlib",
root=pytester.path, root=pytester.path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert mod.__name__ == "_tests.b.test_core" assert mod.__name__ == "_tests.b.test_core"
def test_import_using_normal_mechanism_first_integration( def test_import_using_normal_mechanism_first_integration(
self, monkeypatch: MonkeyPatch, pytester: Pytester self, monkeypatch: MonkeyPatch, pytester: Pytester, ns_param: bool
) -> None: ) -> None:
""" """
Same test as above, but verify the behavior calling pytest. Same test as above, but verify the behavior calling pytest.
@ -941,6 +974,8 @@ class TestImportLibMode:
) )
result = pytester.runpytest( result = pytester.runpytest(
"--import-mode=importlib", "--import-mode=importlib",
"-o",
f"consider_namespace_packages={ns_param}",
"--doctest-modules", "--doctest-modules",
"--pyargs", "--pyargs",
"app", "app",
@ -955,7 +990,9 @@ class TestImportLibMode:
] ]
) )
def test_import_path_imports_correct_file(self, pytester: Pytester) -> None: def test_import_path_imports_correct_file(
self, pytester: Pytester, ns_param: bool
) -> None:
""" """
Import the module by the given path, even if other module with the same name Import the module by the given path, even if other module with the same name
is reachable from sys.path. is reachable from sys.path.
@ -979,7 +1016,7 @@ class TestImportLibMode:
x_in_sub_folder, x_in_sub_folder,
mode=ImportMode.importlib, mode=ImportMode.importlib,
root=pytester.path, root=pytester.path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )
assert mod.__file__ and Path(mod.__file__) == x_in_sub_folder assert mod.__file__ and Path(mod.__file__) == x_in_sub_folder
assert mod.X == "a/b/x" assert mod.X == "a/b/x"
@ -990,7 +1027,7 @@ class TestImportLibMode:
x_at_root, x_at_root,
mode=ImportMode.importlib, mode=ImportMode.importlib,
root=pytester.path, root=pytester.path,
consider_namespace_packages=False, consider_namespace_packages=ns_param,
) )