Skip to content

Commit 056cc6e

Browse files
committed
feat: add --snapshot-ignore-file-extensions argument to support DVC (#943)
(cherry picked from commit 8ff6c0f)
1 parent 25d37ef commit 056cc6e

File tree

11 files changed

+112
-13
lines changed

11 files changed

+112
-13
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ These are the cli options exposed to `pytest` by the plugin.
140140
| `--snapshot-no-colors` | Disable test results output highlighting. Equivalent to setting the environment variables `ANSI_COLORS_DISABLED` or `NO_COLOR` | Disabled by default if not in terminal. |
141141
| `--snapshot-patch-pycharm-diff`| Override PyCharm's default diffs viewer when looking at snapshot diffs. See [IDE Integrations](#ide-integrations) | `False` |
142142
| `--snapshot-diff-mode` | Configures how diffs are displayed on assertion failure. If working with very large snapshots, disabling the diff can improve performance. | `detailed` |
143+
| `--snapshot-ignore-file-extensions` | Comma separated list of file extensions to ignore when walking the file tree and discovering used/unused snapshots. | No extensions are ignored by default. |
143144

144145
### Assertion Options
145146

@@ -461,6 +462,22 @@ The generated snapshot:
461462
}
462463
```
463464

465+
#### Ignoring File Extensions (e.g. DVC Integration)
466+
467+
If using a tool such as [DVC](https://dvc.org/) or other tool where you need to ignore files by file extension, you can update your `pytest.ini` like so:
468+
469+
```ini
470+
[pytest]
471+
addopts = --snapshot-ignore-file-extensions dvc
472+
```
473+
474+
A comma separated list is supported, like so:
475+
476+
```ini
477+
[pytest]
478+
addopts = --snapshot-ignore-file-extensions dvc,tmp,zip
479+
```
480+
464481
### Extending Syrupy
465482

466483
- [Custom defaults](https://github.com/syrupy-project/syrupy/tree/main/tests/examples/test_custom_defaults.py)

src/syrupy/__init__.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ def pytest_addoption(parser: "pytest.Parser") -> None:
103103
dest="diff_mode",
104104
help="Controls how diffs are represented on snapshot assertion failure",
105105
)
106+
group.addoption(
107+
"--snapshot-ignore-file-extensions",
108+
dest="ignore_file_extensions",
109+
help="Comma separated list of file extensions to ignore when discovering snapshots",
110+
type=lambda v: v.split(","),
111+
)
106112

107113

108114
def __terminal_color(config: "pytest.Config") -> "ContextManager[None]":
@@ -153,7 +159,10 @@ def pytest_sessionstart(session: Any) -> None:
153159
Initialize snapshot session before tests are collected and ran.
154160
https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_sessionstart
155161
"""
156-
session.config._syrupy = SnapshotSession(pytest_session=session)
162+
session.config._syrupy = SnapshotSession(
163+
pytest_session=session,
164+
ignore_file_extensions=session.config.option.ignore_file_extensions,
165+
)
157166
global _syrupy
158167
_syrupy = session.config._syrupy
159168
session.config._syrupy.start()

src/syrupy/extensions/base.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,19 @@ def is_snapshot_location(self, *, location: str) -> bool:
108108
return location.endswith(self._file_extension)
109109

110110
def discover_snapshots(
111-
self, *, test_location: "PyTestLocation"
111+
self,
112+
*,
113+
test_location: "PyTestLocation",
114+
ignore_extensions: Optional[list[str]] = None,
112115
) -> "SnapshotCollections":
113116
"""
114117
Returns all snapshot collections in test site
115118
"""
116119
discovered = SnapshotCollections()
117-
for filepath in walk_snapshot_dir(self.dirname(test_location=test_location)):
120+
for filepath in walk_snapshot_dir(
121+
self.dirname(test_location=test_location),
122+
ignore_extensions=ignore_extensions,
123+
):
118124
if self.is_snapshot_location(location=filepath):
119125
snapshot_collection = self._read_snapshot_collection(
120126
snapshot_location=filepath

src/syrupy/extensions/json/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# noqa: A005
12
import datetime
23
import inspect
34
import json

src/syrupy/report.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ def __post_init__(self) -> None:
112112
locations_discovered[test_location].add(extension_class)
113113
self.discovered.merge(
114114
assertion.extension.discover_snapshots(
115-
test_location=assertion.test_location
115+
test_location=assertion.test_location,
116+
ignore_extensions=assertion.session.ignore_file_extensions,
116117
)
117118
)
118119

src/syrupy/session.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ class ItemStatus(Enum):
5353
@dataclass
5454
class SnapshotSession:
5555
pytest_session: "pytest.Session"
56+
57+
# List of file extensions to ignore during discovery/processing
58+
ignore_file_extensions: Optional[list[str]] = None
59+
5660
# Snapshot report generated on finish
5761
report: Optional["SnapshotReport"] = None
5862
# All the collected test items
@@ -212,7 +216,8 @@ def register_request(self, assertion: "SnapshotAssertion") -> None:
212216
discovered_extensions = {
213217
discovered.location: assertion.extension
214218
for discovered in assertion.extension.discover_snapshots(
215-
test_location=assertion.test_location
219+
test_location=assertion.test_location,
220+
ignore_extensions=self.ignore_file_extensions,
216221
)
217222
if discovered.has_snapshots
218223
}

src/syrupy/types.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# noqa: A005
12
from typing import (
23
Any,
34
Callable,

src/syrupy/utils.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
Any,
1010
Dict,
1111
Iterator,
12+
Optional,
1213
Sequence,
14+
Union,
1315
)
1416

1517
from .constants import (
1618
DIFF_LINE_COUNT_LIMIT,
1719
DIFF_LINE_WIDTH_LIMIT,
18-
SNAPSHOT_DIRNAME,
1920
SYMBOL_ELLIPSIS,
2021
)
2122
from .exceptions import FailedToLoadModuleMember
@@ -31,13 +32,15 @@ def is_xdist_controller() -> bool:
3132
return bool(worker_count and int(worker_count) > 0 and not is_xdist_worker())
3233

3334

34-
def in_snapshot_dir(path: Path) -> bool:
35-
return SNAPSHOT_DIRNAME in path.parts
35+
def walk_snapshot_dir(
36+
root: Union[str, Path], *, ignore_extensions: Optional[list[str]] = None
37+
) -> Iterator[str]:
38+
ignore_exts: set[str] = set(ignore_extensions or [])
3639

37-
38-
def walk_snapshot_dir(root: str) -> Iterator[str]:
3940
for filepath in Path(root).rglob("*"):
4041
if not filepath.name.startswith(".") and filepath.is_file():
42+
if filepath.suffixes and filepath.suffixes[-1][1:] in ignore_exts:
43+
continue
4144
yield str(filepath)
4245

4346

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from pathlib import Path
2+
3+
4+
def test_ignore_file_extensions(testdir):
5+
# Generate initial snapshot file
6+
testdir.makepyfile(
7+
test_file=(
8+
"""
9+
def test_case(snapshot):
10+
assert snapshot == "some-value"
11+
"""
12+
),
13+
)
14+
result = testdir.runpytest("-v", "--snapshot-update")
15+
result.stdout.re_match_lines((r"1 snapshot generated\.",))
16+
assert result.ret == 0
17+
18+
# Duplicate the snapshot file with "dvc" file extension
19+
snapshot_file = Path(testdir.tmpdir, "__snapshots__", "test_file.ambr")
20+
dvc_file = snapshot_file.with_suffix(".ambr.dvc")
21+
dvc_file.write_text(snapshot_file.read_text())
22+
23+
# Run with ignored file extension
24+
result = testdir.runpytest(
25+
"-v", "--snapshot-details", "--snapshot-ignore-file-extensions=dvc"
26+
)
27+
result.stdout.no_re_match_line(r".*1 snapshot unused.*")

tests/syrupy/test_location.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ def mock_pytest_item(node_id: str, method_name: str) -> "pytest.Item":
2020

2121

2222
@pytest.mark.parametrize(
23-
"node_id, method_name, expected_filename,"
24-
"expected_classname, expected_snapshotname",
23+
"node_id, method_name, expected_filename, expected_classname, expected_snapshotname",
2524
(
2625
(
2726
"/tests/module/test_file.py::TestClass::method_name",

tests/syrupy/test_utils.py

+31-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def makefiles(testdir, filetree, root=""):
1414
filepath = Path(root).joinpath(filename)
1515
if isinstance(contents, dict):
1616
testdir.mkdir(filepath)
17-
makefiles(testdir, contents, filepath)
17+
makefiles(testdir, contents, str(filepath))
1818
else:
1919
name, ext = str(filepath.with_name(filepath.stem)), filepath.suffix
2020
testdir.makefile(ext, **{name: contents})
@@ -50,6 +50,36 @@ def test_walk_dir_skips_non_snapshot_path(testfiles):
5050
}
5151

5252

53+
def test_walk_dir_ignores_ignored_extensions(testdir):
54+
filetree = {
55+
"file1.txt": "file1",
56+
"file2.txt": "file2",
57+
"__snapshots__": {
58+
"snapfile1.ambr": "",
59+
"snapfile1.ambr.dvc": "",
60+
"snapfolder": {"snapfile2.svg": "<svg></svg>"},
61+
},
62+
}
63+
makefiles(testdir, filetree)
64+
65+
discovered_files = {
66+
str(Path(p).relative_to(Path.cwd()))
67+
for p in walk_snapshot_dir(
68+
Path(testdir.tmpdir).joinpath("__snapshots__"), ignore_extensions=["dvc"]
69+
)
70+
}
71+
72+
assert discovered_files == {
73+
str(Path("__snapshots__").joinpath("snapfile1.ambr")),
74+
str(Path("__snapshots__").joinpath("snapfolder", "snapfile2.svg")),
75+
}
76+
77+
assert (
78+
str(Path("__snapshots__").joinpath("snapfile1.ambr.dvc"))
79+
not in discovered_files
80+
)
81+
82+
5383
def dummy_member():
5484
return 123
5585

0 commit comments

Comments
 (0)