Skip to content

Commit 2aebcd3

Browse files
alexrashedclaudebblommers
authored
fix mono-repo snapshot path lookup (#26)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Bert Blommers <info@bertblommers.nl>
1 parent c435ac3 commit 2aebcd3

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

‎localstack_snapshot/pytest/snapshot.py‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import os
3+
from pathlib import Path
34
from typing import Optional
45

56
import pytest
@@ -97,14 +98,35 @@ def pytest_runtest_call(item: Item) -> None:
9798
sm._assert_all(verify, paths)
9899

99100

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+
100122
@pytest.fixture(scope="function")
101123
def _snapshot_session(request: SubRequest):
102124
update_overwrite = os.environ.get("SNAPSHOT_UPDATE") == "1"
103125
raw_overwrite = os.environ.get("SNAPSHOT_RAW") == "1"
104126

105127
sm = SnapshotSession(
106128
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),
108130
update=update_overwrite or request.config.option.snapshot_update,
109131
raw=raw_overwrite or request.config.option.snapshot_raw,
110132
verify=False if request.config.option.snapshot_skip_all else True,

‎tests/test_pytest_snapshot.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from pathlib import Path
2+
from types import SimpleNamespace
3+
4+
from localstack_snapshot.pytest.snapshot import package_scoped_nodeid
5+
6+
7+
def _create_package(root: Path) -> Path:
8+
package = root / "my-package"
9+
(package / "tests" / "services").mkdir(parents=True)
10+
(package / "pyproject.toml").touch()
11+
return package
12+
13+
14+
def test_nodeid_from_a_package_rooted_run_is_unchanged(tmp_path):
15+
package = _create_package(tmp_path)
16+
item = SimpleNamespace(
17+
path=package / "tests" / "services" / "test_thing.py",
18+
nodeid="tests/services/test_thing.py::TestThing::test_case[param]",
19+
)
20+
21+
assert (
22+
package_scoped_nodeid(item) == "tests/services/test_thing.py::TestThing::test_case[param]"
23+
)
24+
25+
26+
def test_nodeid_from_a_repo_rooted_run_is_anchored_to_the_package(tmp_path):
27+
package = _create_package(tmp_path)
28+
item = SimpleNamespace(
29+
path=package / "tests" / "services" / "test_thing.py",
30+
nodeid="my-package/tests/services/test_thing.py::test_case",
31+
)
32+
33+
assert package_scoped_nodeid(item) == "tests/services/test_thing.py::test_case"
34+
35+
36+
def test_nearest_pyproject_wins_over_the_workspace_root(tmp_path):
37+
(tmp_path / "pyproject.toml").touch()
38+
package = _create_package(tmp_path)
39+
item = SimpleNamespace(
40+
path=package / "tests" / "services" / "test_thing.py",
41+
nodeid="my-package/tests/services/test_thing.py::test_case",
42+
)
43+
44+
assert package_scoped_nodeid(item) == "tests/services/test_thing.py::test_case"
45+
46+
47+
def test_nodeid_without_an_enclosing_package_is_unchanged(tmp_path):
48+
item = SimpleNamespace(
49+
path=tmp_path / "tests" / "test_thing.py",
50+
nodeid="tests/test_thing.py::test_case",
51+
)
52+
53+
assert package_scoped_nodeid(item) == "tests/test_thing.py::test_case"

0 commit comments

Comments
 (0)