|
1 | 1 | import json |
2 | 2 | import os |
| 3 | +from pathlib import Path |
3 | 4 | from typing import Optional |
4 | 5 |
|
5 | 6 | import pytest |
@@ -97,14 +98,35 @@ def pytest_runtest_call(item: Item) -> None: |
97 | 98 | sm._assert_all(verify, paths) |
98 | 99 |
|
99 | 100 |
|
| 101 | +def package_scoped_nodeid(item: Item) -> str: |
| 102 | + """The item's nodeid relative to its enclosing package, independent of pytest's rootdir. |
| 103 | +
|
| 104 | + Snapshot entries are keyed by the pytest nodeid, which is relative to pytest's rootdir. |
| 105 | + The committed ``*.snapshot.json`` / ``*.validation.json`` files assume the rootdir is the |
| 106 | + package containing the test (``tests/...::<test>``), but pytest can be invoked with a |
| 107 | + different rootdir — VS Code's test runner, for example, pins ``--rootdir=<workspace folder>``, |
| 108 | + which prefixes every nodeid with the package directory and makes every lookup of a recorded |
| 109 | + entry miss. Keying by the nodeid relative to the nearest enclosing ``pyproject.toml`` keeps |
| 110 | + the entries stable no matter where pytest was started from. |
| 111 | + """ |
| 112 | + path = Path(item.path) |
| 113 | + for parent in path.parents: |
| 114 | + if (parent / "pyproject.toml").is_file(): |
| 115 | + # Partition splits by the first occurance and also returns the partition itself |
| 116 | + # "tests/test_thing.py::TestThing::test_case".partition("::") == ("tests/test_thing.py", "::", "TestThing::test_case") |
| 117 | + _, separator, remainder = item.nodeid.partition("::") |
| 118 | + return path.relative_to(parent).as_posix() + separator + remainder |
| 119 | + return item.nodeid |
| 120 | + |
| 121 | + |
100 | 122 | @pytest.fixture(scope="function") |
101 | 123 | def _snapshot_session(request: SubRequest): |
102 | 124 | update_overwrite = os.environ.get("SNAPSHOT_UPDATE") == "1" |
103 | 125 | raw_overwrite = os.environ.get("SNAPSHOT_RAW") == "1" |
104 | 126 |
|
105 | 127 | sm = SnapshotSession( |
106 | 128 | base_file_path=os.path.join(request.fspath.dirname, request.fspath.purebasename), |
107 | | - scope_key=request.node.nodeid, |
| 129 | + scope_key=package_scoped_nodeid(request.node), |
108 | 130 | update=update_overwrite or request.config.option.snapshot_update, |
109 | 131 | raw=raw_overwrite or request.config.option.snapshot_raw, |
110 | 132 | verify=False if request.config.option.snapshot_skip_all else True, |
|
0 commit comments