|
3 | 3 | import unittest.mock
|
4 | 4 | from pathlib import Path
|
5 | 5 | from textwrap import dedent
|
| 6 | +from typing import Any |
6 | 7 |
|
7 | 8 | import py
|
8 | 9 |
|
|
17 | 18 | from _pytest.pathlib import import_path
|
18 | 19 | from _pytest.pathlib import ImportPathMismatchError
|
19 | 20 | from _pytest.pathlib import maybe_delete_a_numbered_dir
|
| 21 | +from _pytest.pathlib import module_name_from_path |
20 | 22 | from _pytest.pathlib import resolve_package_path
|
21 | 23 | from _pytest.pathlib import symlink_or_skip
|
22 | 24 | from _pytest.pathlib import visit
|
@@ -441,46 +443,109 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
|
441 | 443 |
|
442 | 444 |
|
443 | 445 | @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
|
444 |
| -def test_importmode_importlib_with_dataclass(tmpdir): |
445 |
| - """Ensure that importlib mode works with a module containing dataclasses""" |
446 |
| - tmpdir.join("src/tests").ensure_dir() |
447 |
| - fn = tmpdir.join("src/tests/test_dataclass.py") |
448 |
| - fn.write( |
| 446 | +def test_importmode_importlib_with_dataclass(tmp_path: Path) -> None: |
| 447 | + """Ensure that importlib mode works with a module containing dataclasses (#7856).""" |
| 448 | + fn = tmp_path.joinpath("src/tests/test_dataclass.py") |
| 449 | + fn.parent.mkdir(parents=True) |
| 450 | + fn.write_text( |
449 | 451 | dedent(
|
450 | 452 | """
|
451 | 453 | from dataclasses import dataclass
|
452 | 454 |
|
453 | 455 | @dataclass
|
454 |
| - class DataClass: |
| 456 | + class Data: |
455 | 457 | value: str
|
| 458 | + """ |
| 459 | + ) |
| 460 | + ) |
| 461 | + |
| 462 | + module = import_path(fn, mode="importlib", root=tmp_path) |
| 463 | + Data: Any = module.Data # type: ignore[attr-defined] |
| 464 | + data = Data(value="foo") |
| 465 | + assert data.value == "foo" |
| 466 | + assert data.__module__ == "src.tests.test_dataclass" |
456 | 467 |
|
457 |
| - def test_dataclass(): |
458 |
| - assert DataClass(value='test').value == 'test' |
| 468 | + |
| 469 | +def test_importmode_importlib_with_pickle(tmp_path: Path) -> None: |
| 470 | + """Ensure that importlib mode works with pickle (#7859).""" |
| 471 | + fn = tmp_path.joinpath("src/tests/test_pickle.py") |
| 472 | + fn.parent.mkdir(parents=True) |
| 473 | + fn.write_text( |
| 474 | + dedent( |
| 475 | + """ |
| 476 | + import pickle |
| 477 | +
|
| 478 | + def _action(): |
| 479 | + return 42 |
| 480 | +
|
| 481 | + def round_trip(): |
| 482 | + s = pickle.dumps(_action) |
| 483 | + return pickle.loads(s) |
459 | 484 | """
|
460 | 485 | )
|
461 | 486 | )
|
462 | 487 |
|
463 |
| - module = import_path(fn, mode="importlib", root=Path(tmpdir)) |
464 |
| - module.test_dataclass() # type: ignore[attr-defined] |
| 488 | + module = import_path(fn, mode="importlib", root=tmp_path) |
| 489 | + action: Any = module.round_trip() # type: ignore[attr-defined] |
| 490 | + assert action() == 42 |
465 | 491 |
|
466 | 492 |
|
467 |
| -def test_importmode_importlib_with_pickle(tmpdir): |
468 |
| - """Ensure that importlib mode works with pickle""" |
469 |
| - tmpdir.join("src/tests").ensure_dir() |
470 |
| - fn = tmpdir.join("src/tests/test_pickle.py") |
471 |
| - fn.write( |
| 493 | +def test_importmode_importlib_with_pickle_separate_modules(tmp_path: Path) -> None: |
| 494 | + """ |
| 495 | + Ensure that importlib mode works can load pickles that look similar but are |
| 496 | + defined in separate modules. |
| 497 | + """ |
| 498 | + fn1 = tmp_path.joinpath("src/m1/tests/test.py") |
| 499 | + fn1.parent.mkdir(parents=True) |
| 500 | + fn1.write_text( |
472 | 501 | dedent(
|
473 | 502 | """
|
| 503 | + import attr |
474 | 504 | import pickle
|
475 | 505 |
|
476 |
| - def do_action(): |
477 |
| - pass |
| 506 | + @attr.s(auto_attribs=True) |
| 507 | + class Data: |
| 508 | + x: int = 42 |
| 509 | + """ |
| 510 | + ) |
| 511 | + ) |
| 512 | + |
| 513 | + fn2 = tmp_path.joinpath("src/m2/tests/test.py") |
| 514 | + fn2.parent.mkdir(parents=True) |
| 515 | + fn2.write_text( |
| 516 | + dedent( |
| 517 | + """ |
| 518 | + import attr |
| 519 | + import pickle |
478 | 520 |
|
479 |
| - def test_pickle(): |
480 |
| - pickle.dumps(do_action) |
| 521 | + @attr.s(auto_attribs=True) |
| 522 | + class Data: |
| 523 | + x: str = "" |
481 | 524 | """
|
482 | 525 | )
|
483 | 526 | )
|
484 | 527 |
|
485 |
| - module = import_path(fn, mode="importlib", root=Path(tmpdir)) |
486 |
| - module.test_pickle() # type: ignore[attr-defined] |
| 528 | + import pickle |
| 529 | + |
| 530 | + def round_trip(obj): |
| 531 | + s = pickle.dumps(obj) |
| 532 | + return pickle.loads(s) |
| 533 | + |
| 534 | + module = import_path(fn1, mode="importlib", root=tmp_path) |
| 535 | + Data1 = module.Data # type: ignore[attr-defined] |
| 536 | + |
| 537 | + module = import_path(fn2, mode="importlib", root=tmp_path) |
| 538 | + Data2 = module.Data # type: ignore[attr-defined] |
| 539 | + |
| 540 | + assert round_trip(Data1(20)) == Data1(20) |
| 541 | + assert round_trip(Data2("hello")) == Data2("hello") |
| 542 | + assert Data1.__module__ == "src.m1.tests.test" |
| 543 | + assert Data2.__module__ == "src.m2.tests.test" |
| 544 | + |
| 545 | + |
| 546 | +def test_module_name_from_path(tmp_path: Path) -> None: |
| 547 | + result = module_name_from_path(tmp_path / "src/tests/test_foo.py", tmp_path) |
| 548 | + assert result == "src.tests.test_foo" |
| 549 | + |
| 550 | + result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar")) |
| 551 | + assert result == "home.foo.test_foo" |
0 commit comments