Skip to content

Commit

Permalink
Improve tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoddemus committed Dec 19, 2020
1 parent 173fd6e commit ebc2622
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 23 deletions.
24 changes: 22 additions & 2 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,7 @@ def import_path(
raise ImportError(path)

if mode is ImportMode.importlib:
relative_path = path.relative_to(root)
module_name = ".".join(relative_path.with_suffix("").parts)
module_name = module_name_from_path(path, root)

for meta_importer in sys.meta_path:
spec = meta_importer.find_spec(module_name, [str(path.parent)])
Expand Down Expand Up @@ -573,6 +572,27 @@ def _is_same(f1: str, f2: str) -> bool:
return os.path.samefile(f1, f2)


def module_name_from_path(path: Path, root: Path) -> str:
"""
Return a dotted module name based on the given path, anchored on root.
For example: path="projects/src/tests/test_foo.py" and root="/projects", the
resulting module name will be "src.tests.test_foo".
"""
path = path.with_suffix("")
try:
relative_path = path.relative_to(root)
except ValueError:
# If we can't get a relative path to root, use the full path, except
# for the first part ("d:\\" or "/" depending on the platform, for example).
path_parts = path.parts[1:]
else:
# Use the parts for the relative path to the root path.
path_parts = relative_path.parts

return ".".join(path_parts)


def resolve_package_path(path: Path) -> Optional[Path]:
"""Return the Python package path by looking for the last
directory upwards which still contains an __init__.py.
Expand Down
107 changes: 86 additions & 21 deletions testing/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import unittest.mock
from pathlib import Path
from textwrap import dedent
from typing import Any

import py

Expand All @@ -17,6 +18,7 @@
from _pytest.pathlib import import_path
from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import maybe_delete_a_numbered_dir
from _pytest.pathlib import module_name_from_path
from _pytest.pathlib import resolve_package_path
from _pytest.pathlib import symlink_or_skip
from _pytest.pathlib import visit
Expand Down Expand Up @@ -441,46 +443,109 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N


@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_importmode_importlib_with_dataclass(tmpdir):
"""Ensure that importlib mode works with a module containing dataclasses"""
tmpdir.join("src/tests").ensure_dir()
fn = tmpdir.join("src/tests/test_dataclass.py")
fn.write(
def test_importmode_importlib_with_dataclass(tmp_path: Path) -> None:
"""Ensure that importlib mode works with a module containing dataclasses (#7856)."""
fn = tmp_path.joinpath("src/tests/test_dataclass.py")
fn.parent.mkdir(parents=True)
fn.write_text(
dedent(
"""
from dataclasses import dataclass
@dataclass
class DataClass:
class Data:
value: str
"""
)
)

module = import_path(fn, mode="importlib", root=tmp_path)
Data: Any = module.Data # type: ignore[attr-defined]
data = Data(value="foo")
assert data.value == "foo"
assert data.__module__ == "src.tests.test_dataclass"

def test_dataclass():
assert DataClass(value='test').value == 'test'

def test_importmode_importlib_with_pickle(tmp_path: Path) -> None:
"""Ensure that importlib mode works with pickle (#7859)."""
fn = tmp_path.joinpath("src/tests/test_pickle.py")
fn.parent.mkdir(parents=True)
fn.write_text(
dedent(
"""
import pickle
def _action():
return 42
def round_trip():
s = pickle.dumps(_action)
return pickle.loads(s)
"""
)
)

module = import_path(fn, mode="importlib", root=Path(tmpdir))
module.test_dataclass() # type: ignore[attr-defined]
module = import_path(fn, mode="importlib", root=tmp_path)
action: Any = module.round_trip() # type: ignore[attr-defined]
assert action() == 42


def test_importmode_importlib_with_pickle(tmpdir):
"""Ensure that importlib mode works with pickle"""
tmpdir.join("src/tests").ensure_dir()
fn = tmpdir.join("src/tests/test_pickle.py")
fn.write(
def test_importmode_importlib_with_pickle_separate_modules(tmp_path: Path) -> None:
"""
Ensure that importlib mode works can load pickles that look similar but are
defined in separate modules.
"""
fn1 = tmp_path.joinpath("src/m1/tests/test.py")
fn1.parent.mkdir(parents=True)
fn1.write_text(
dedent(
"""
import attr
import pickle
def do_action():
pass
@attr.s(auto_attribs=True)
class Data:
x: int = 42
"""
)
)

fn2 = tmp_path.joinpath("src/m2/tests/test.py")
fn2.parent.mkdir(parents=True)
fn2.write_text(
dedent(
"""
import attr
import pickle
def test_pickle():
pickle.dumps(do_action)
@attr.s(auto_attribs=True)
class Data:
x: str = ""
"""
)
)

module = import_path(fn, mode="importlib", root=Path(tmpdir))
module.test_pickle() # type: ignore[attr-defined]
import pickle

def round_trip(obj):
s = pickle.dumps(obj)
return pickle.loads(s)

module = import_path(fn1, mode="importlib", root=tmp_path)
Data1 = module.Data # type: ignore[attr-defined]

module = import_path(fn2, mode="importlib", root=tmp_path)
Data2 = module.Data # type: ignore[attr-defined]

assert round_trip(Data1(20)) == Data1(20)
assert round_trip(Data2("hello")) == Data2("hello")
assert Data1.__module__ == "src.m1.tests.test"
assert Data2.__module__ == "src.m2.tests.test"


def test_module_name_from_path(tmp_path: Path) -> None:
result = module_name_from_path(tmp_path / "src/tests/test_foo.py", tmp_path)
assert result == "src.tests.test_foo"

result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
assert result == "home.foo.test_foo"

0 comments on commit ebc2622

Please sign in to comment.