Skip to content

Commit ef118b8

Browse files
iamogbznoahnu
authored andcommitted
feat: fail unused snapshots (#77)
* docs: update contributing * wip: fail when unused snapshots * test: unused warn flag * fix: clarify why unused snapshot was deleted * cr: fix docs typo Co-Authored-By: Noah <[email protected]> * cr: more explicit option * docs: add options docs * cr: make instructions clearer * refactor: option name * refactor: xor exit status * refactor: xor in place * cr: fix typo * docs: update table formatting Co-authored-by: Noah <[email protected]>
1 parent 9f2f396 commit ef118b8

File tree

7 files changed

+75
-30
lines changed

7 files changed

+75
-30
lines changed

CONTRIBUTING.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ These are mostly guidelines, not rules. Use your best judgment, and feel free to
2727

2828
- [Commit Messages](#commit-messages)
2929
- [Code Styleguide](#code-styleguide)
30-
- [Specs Styleguide](#specs-styleguide)
31-
- [Docs Styleguide](#docs-styleguide)
3230

3331
[Additional Notes](#additional-notes)
3432

@@ -40,14 +38,14 @@ This project and everyone participating in it is governed by our [Code of Conduc
4038

4139
## What should I know before I get started
4240

43-
### Snapshot Testing
44-
45-
- Javascript snapshot testing [jest](https:/a/jestjs.io/docs/en/snapshot-testing)
46-
4741
### Python 3
4842

4943
- Python typing and hints: [typing](https://docs.python.org/3/library/typing.html)
5044

45+
### Snapshot Testing
46+
47+
- Javascript snapshot testing [jest](https://jestjs.io/docs/en/snapshot-testing)
48+
5149
### Releases
5250

5351
- Semantic versioning: [semver](https://semver.org/spec/v2.0.0.html)
@@ -91,12 +89,12 @@ Fill in the relevant sections, clearly linking the issue the change is attemping
9189

9290
## Styleguides
9391

94-
### Git Commit Messages
92+
### Commit Messages
9593

9694
Provide semantic commit messages following this [convention](https://www.conventionalcommits.org/en/v1.0.0/#summary).
9795
This informs the semantic versioning we use to control our [releases](#releases).
9896

99-
### Specs Styleguide
97+
### Code Styleguide
10098

10199
A linter is available to catch most of our styling concerns.
102100
This is provided in a pre-commit hook when setting up [local development](#local-development).

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ pytest --snapshot-update
4646

4747
A snapshot file should be generated under a `__snapshots__` directory in the same directory as `test_file.py`. The `__snapshots__` directory and all its children should be committed along with your test code.
4848

49+
### Options
50+
51+
These are the cli options exposed to `pytest` by the plugin.
52+
53+
| Option | Description |
54+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
55+
| `--snapshot-update` | When supplied updates existing snapshots of any run tests, as well as deleting unused and generating new snapshots. |
56+
| `--snapshot-warn-unused` | Syrupy default behaviour is to fail the test session when there any unused snapshots. This instructs the plugin not to fail. |
57+
4958
### Serializers
5059

5160
Syrupy comes with a few built-in serializers for you to choose from. You should also feel free to extend the AbstractSnapshotSerializer if your project has a need not captured by one our built-ins.

src/syrupy/__init__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ def pytest_addoption(parser: Any) -> None:
2525
dest="update_snapshots",
2626
help="Update snapshots",
2727
)
28+
group.addoption(
29+
"--snapshot-warn-unused",
30+
action="store_true",
31+
default=False,
32+
dest="warn_unused_snapshots",
33+
help="Do not fail on unused snapshots",
34+
)
2835

2936

3037
def pytest_assertrepr_compare(op: str, left: Any, right: Any) -> Optional[List[str]]:
@@ -48,7 +55,9 @@ def pytest_sessionstart(session: Any) -> None:
4855
"""
4956
config = session.config
5057
session._syrupy = SnapshotSession(
51-
update_snapshots=config.option.update_snapshots, base_dir=config.rootdir
58+
warn_unused_snapshots=config.option.warn_unused_snapshots,
59+
update_snapshots=config.option.update_snapshots,
60+
base_dir=config.rootdir,
5261
)
5362
session._syrupy.start()
5463

@@ -69,15 +78,16 @@ def pytest_collection_finish(session: Any) -> None:
6978
session._syrupy._ran_items.update(session.items)
7079

7180

72-
def pytest_sessionfinish(session: Any) -> None:
81+
def pytest_sessionfinish(session: Any, exitstatus: int) -> None:
7382
"""
7483
Add syrupy report to pytest after whole test run finished, before exiting.
7584
https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_sessionfinish
7685
"""
7786
reporter = session.config.pluginmanager.get_plugin("terminalreporter")
78-
session._syrupy.finish()
87+
syrupy_exitstatus = session._syrupy.finish()
7988
for line in session._syrupy.report:
8089
reporter.write_line(line)
90+
session.exitstatus |= syrupy_exitstatus
8191

8292

8393
@pytest.fixture

src/syrupy/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
SNAPSHOT_DIRNAME = "__snapshots__"
22
SNAPSHOT_EMPTY_FILE = {"empty snapshot file"}
33
SNAPSHOT_UNKNOWN_FILE = {"unknown snapshot file"}
4+
5+
EXIT_STATUS_FAIL_UNUSED = 1

src/syrupy/session.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
Set,
1313
)
1414

15-
from .constants import SNAPSHOT_UNKNOWN_FILE
15+
from .constants import (
16+
EXIT_STATUS_FAIL_UNUSED,
17+
SNAPSHOT_UNKNOWN_FILE,
18+
)
1619
from .location import TestLocation
1720
from .terminal import (
1821
bold,
@@ -42,7 +45,10 @@ def empty_snapshot_groups() -> "SnapshotGroups":
4245

4346

4447
class SnapshotSession:
45-
def __init__(self, *, update_snapshots: bool, base_dir: str):
48+
def __init__(
49+
self, *, warn_unused_snapshots: bool, update_snapshots: bool, base_dir: str
50+
):
51+
self.warn_unused_snapshots = warn_unused_snapshots
4652
self.update_snapshots = update_snapshots
4753
self.base_dir = base_dir
4854
self.report: List[str] = []
@@ -60,7 +66,8 @@ def start(self) -> None:
6066
self._serializers = {}
6167
self._snapshot_groups = empty_snapshot_groups()
6268

63-
def finish(self) -> None:
69+
def finish(self) -> int:
70+
exitstatus = 0
6471
self._collate_snapshots()
6572
n_unused = self._count_snapshots(self._snapshot_groups.unused)
6673
n_written = self._count_snapshots(self._snapshot_groups.created)
@@ -97,12 +104,15 @@ def finish(self) -> None:
97104
]
98105
if n_unused:
99106
if self.update_snapshots:
100-
text_singular = "{} snapshot deleted."
101-
text_plural = "{} snapshots deleted."
107+
text_singular = "{} unused snapshot deleted."
108+
text_plural = "{} unused snapshots deleted."
102109
else:
103110
text_singular = "{} snapshot unused."
104111
text_plural = "{} snapshots unused."
105-
text_count = warning_style(n_unused)
112+
if self.update_snapshots or self.warn_unused_snapshots:
113+
text_count = warning_style(n_unused)
114+
else:
115+
text_count = error_style(n_unused)
106116
summary_lines += [
107117
ngettext(text_singular, text_plural, n_unused).format(text_count)
108118
]
@@ -118,15 +128,20 @@ def finish(self) -> None:
118128
path_to_file = os.path.relpath(filepath, self.base_dir)
119129
deleted_snapshots = ", ".join(map(bold, sorted(snapshots)))
120130
self.add_report_line(
121-
f"Deleted {deleted_snapshots} ({path_to_file})"
131+
gettext(f"Deleted {deleted_snapshots} ({path_to_file})")
122132
)
123133
else:
124-
self.add_report_line(
125-
gettext(
126-
"Re-run pytest with --snapshot-update"
127-
" to delete the unused snapshots."
128-
)
134+
message = gettext(
135+
"Re-run pytest with --snapshot-update"
136+
" to delete the unused snapshots."
129137
)
138+
if self.warn_unused_snapshots:
139+
message = warning_style(message)
140+
else:
141+
message = error_style(message)
142+
exitstatus |= EXIT_STATUS_FAIL_UNUSED
143+
self.add_report_line(message)
144+
return exitstatus
130145

131146
def add_report_line(self, line: str = "") -> None:
132147
self.report += [line]

tests/test_integration.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def test_collected(snapshot):
4141
result_stdout = clean_output(result.stdout.str())
4242
assert "1 snapshot passed" in result_stdout
4343
assert "1 snapshot updated" in result_stdout
44-
assert "1 snapshot deleted" in result_stdout
44+
assert "1 unused snapshot deleted" in result_stdout
4545

4646

4747
def test_collection_parametrized(testdir):
@@ -83,7 +83,7 @@ def test_collected(snapshot, actual):
8383
result_stdout = clean_output(result.stdout.str())
8484
assert "2 snapshots passed" in result_stdout
8585
assert "snapshot updated" not in result_stdout
86-
assert "1 snapshot deleted" in result_stdout
86+
assert "1 unused snapshot deleted" in result_stdout
8787

8888

8989
@pytest.fixture
@@ -227,6 +227,17 @@ def test_unused_snapshots(stubs):
227227
assert "snapshots generated" not in result_stdout
228228
assert "4 snapshots passed" in result_stdout
229229
assert "1 snapshot unused" in result_stdout
230+
assert result.ret == 1
231+
232+
233+
def test_unused_snapshots_warning(stubs):
234+
_, testdir, tests, _ = stubs
235+
testdir.makepyfile(test_file="\n\n".join(tests[k] for k in tests if k != "unused"))
236+
result = testdir.runpytest("-v", "--snapshot-warn-unused")
237+
result_stdout = clean_output(result.stdout.str())
238+
assert "snapshots generated" not in result_stdout
239+
assert "4 snapshots passed" in result_stdout
240+
assert "1 snapshot unused" in result_stdout
230241
assert result.ret == 0
231242

232243

@@ -237,7 +248,7 @@ def test_removed_snapshots(stubs):
237248
result = testdir.runpytest("-v", "--snapshot-update")
238249
result_stdout = clean_output(result.stdout.str())
239250
assert "snapshot unused" not in result_stdout
240-
assert "1 snapshot deleted" in result_stdout
251+
assert "1 unused snapshot deleted" in result_stdout
241252
assert result.ret == 0
242253
assert os.path.isfile(filepath)
243254

@@ -249,7 +260,7 @@ def test_removed_snapshot_file(stubs):
249260
result = testdir.runpytest("-v", "--snapshot-update")
250261
result_stdout = clean_output(result.stdout.str())
251262
assert "snapshots unused" not in result_stdout
252-
assert "5 snapshots deleted" in result_stdout
263+
assert "5 unused snapshots deleted" in result_stdout
253264
assert result.ret == 0
254265
assert not os.path.isfile(filepath)
255266

@@ -263,7 +274,7 @@ def test_removed_empty_snapshot_file_only(stubs):
263274
result = testdir.runpytest("-v", "--snapshot-update")
264275
result_stdout = clean_output(result.stdout.str())
265276
assert os.path.relpath(filepath) not in result_stdout
266-
assert "1 snapshot deleted" in result_stdout
277+
assert "1 unused snapshot deleted" in result_stdout
267278
assert "empty snapshot file" in result_stdout
268279
assert os.path.relpath(empty_filepath) in result_stdout
269280
assert result.ret == 0
@@ -280,7 +291,7 @@ def test_removed_hanging_snapshot_file(stubs):
280291
result = testdir.runpytest("-v", "--snapshot-update")
281292
result_stdout = clean_output(result.stdout.str())
282293
assert os.path.relpath(filepath) not in result_stdout
283-
assert "1 snapshot deleted" in result_stdout
294+
assert "1 unused snapshot deleted" in result_stdout
284295
assert "unknown snapshot file" in result_stdout
285296
assert os.path.relpath(hanging_filepath) in result_stdout
286297
assert result.ret == 0

tests/test_single_serializer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,5 @@ def test_updated_snapshots(stubs, testcases_updated):
108108
result = testdir.runpytest("-v", "--snapshot-update")
109109
result_stdout = clean_output(result.stdout.str())
110110
assert "1 snapshot updated" in result_stdout
111-
assert "1 snapshot deleted" in result_stdout
111+
assert "1 unused snapshot deleted" in result_stdout
112112
assert result.ret == 0

0 commit comments

Comments
 (0)