Skip to content

Commit

Permalink
feat: option to report details of unused snapshots (#467), close #465
Browse files Browse the repository at this point in the history
  • Loading branch information
eaculb committed Mar 5, 2021
1 parent 654470d commit 1c50db0
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ These are the cli options exposed to `pytest` by the plugin.
| Option | Description | Default |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
| `--snapshot-update` | Snapshots will be updated to match assertions and unused snapshots will be deleted. | `False` |
| `--snapshot-details` | Includes details of unused snapshots (test name and snapshot location) in the final report. | `False` |
| `--snapshot-warn-unused` | Prints a warning on unused snapshots rather than fail the test suite. | `False` |
| `--snapshot-default-extension` | Use to change the default snapshot extension class. | `syrupy.extensions.amber.AmberSnapshotExtension` |
| `--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. |
Expand Down
8 changes: 8 additions & 0 deletions src/syrupy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ def pytest_addoption(parser: Any) -> None:
dest="warn_unused_snapshots",
help="Do not fail on unused snapshots",
)
group.addoption(
"--snapshot-details",
action="store_true",
default=False,
dest="include_snapshot_details",
help="Include details of unused snapshots in the final report",
)
group.addoption(
"--snapshot-default-extension",
type=__default_extension_option,
Expand Down Expand Up @@ -112,6 +119,7 @@ def pytest_sessionstart(session: Any) -> None:
config._syrupy = SnapshotSession(
warn_unused_snapshots=config.option.warn_unused_snapshots,
update_snapshots=config.option.update_snapshots,
include_snapshot_details=config.option.include_snapshot_details,
base_dir=config.rootdir,
invocation_args=config.invocation_params.args,
)
Expand Down
12 changes: 7 additions & 5 deletions src/syrupy/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class SnapshotReport:
selected_items: Dict[str, bool] = attr.ib()
update_snapshots: bool = attr.ib()
warn_unused_snapshots: bool = attr.ib()
include_snapshot_details: bool = attr.ib()
assertions: List["SnapshotAssertion"] = attr.ib()
discovered: "SnapshotFossils" = attr.ib(factory=SnapshotFossils)
created: "SnapshotFossils" = attr.ib(factory=SnapshotFossils)
Expand Down Expand Up @@ -280,16 +281,17 @@ def lines(self) -> Iterator[str]:

if self.num_unused:
yield ""
if self.update_snapshots:
if self.update_snapshots or self.include_snapshot_details:
base_message = "Deleted" if self.update_snapshots else "Unused"
for snapshot_fossil in self.unused:
filepath = snapshot_fossil.location
snapshots = (snapshot.name for snapshot in snapshot_fossil)
path_to_file = str(Path(filepath).relative_to(self.base_dir))
deleted_snapshots = ", ".join(map(bold, sorted(snapshots)))
yield warning_style(gettext("Deleted")) + " {} ({})".format(
deleted_snapshots, path_to_file
unused_snapshots = ", ".join(map(bold, sorted(snapshots)))
yield warning_style(gettext(base_message)) + " {} ({})".format(
unused_snapshots, path_to_file
)
else:
if not self.update_snapshots:
message = gettext(
"Re-run pytest with --snapshot-update to delete unused snapshots."
)
Expand Down
2 changes: 2 additions & 0 deletions src/syrupy/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class SnapshotSession:
base_dir: str = attr.ib()
update_snapshots: bool = attr.ib()
warn_unused_snapshots: bool = attr.ib()
include_snapshot_details: bool = attr.ib()
_invocation_args: Tuple[str, ...] = attr.ib(factory=tuple)
report: Optional["SnapshotReport"] = attr.ib(default=None)
# All the collected test items
Expand Down Expand Up @@ -61,6 +62,7 @@ def finish(self) -> int:
assertions=self._assertions,
update_snapshots=self.update_snapshots,
warn_unused_snapshots=self.warn_unused_snapshots,
include_snapshot_details=self.include_snapshot_details,
invocation_args=self._invocation_args,
)
if self.report.num_unused:
Expand Down
142 changes: 142 additions & 0 deletions tests/integration/test_snapshot_option_include_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import pytest


@pytest.fixture
def testcases():
return {
"used": (
"""
def test_used(snapshot):
assert snapshot == 'used'
"""
),
"unused": (
"""
def test_unused(snapshot):
assert snapshot == 'unused'
"""
),
}


@pytest.fixture
def extra_testcases():
return {
"extra_a": (
"""
def test_extra_a(snapshot):
assert snapshot == 'extra_a'
"""
),
"extra_b": (
"""
def test_extra_b(snapshot):
assert snapshot == 'extra_b'
"""
),
}


@pytest.fixture
def run_testfiles_with_update(testdir):
def run_testfiles_with_update_impl(**testfiles):
testdir.makepyfile(
**{
filename: "\n\n".join(cases.values())
for (filename, cases) in testfiles.items()
}
)
result = testdir.runpytest("-v", "--snapshot-update")
result.stdout.re_match_lines((r"[0-9]+ snapshots generated\."))
return testdir

return run_testfiles_with_update_impl


@pytest.mark.parametrize(
(
"options",
"expected_status_code",
),
(
(("-v", "--snapshot-details"), 1),
(("-v", "--snapshot-details", "--snapshot-warn-unused"), 0),
),
)
def test_unused_snapshots_details(
options, expected_status_code, run_testfiles_with_update, testcases
):
testdir = run_testfiles_with_update(test_file=testcases)
testdir.makepyfile(test_file=testcases["used"])

result = testdir.runpytest(*options)
result.stdout.re_match_lines(
(
r"1 snapshot passed\. 1 snapshot unused\.",
r"Unused test_unused \(__snapshots__[\\/]test_file.ambr\)",
r"Re-run pytest with --snapshot-update to delete unused snapshots\.",
)
)
assert result.ret == expected_status_code


def test_unused_snapshots_details_multiple_tests(
run_testfiles_with_update, testcases, extra_testcases
):
testdir = run_testfiles_with_update(
test_file=testcases, test_second_file=extra_testcases
)
testdir.makepyfile(
test_file="\n\n".join(testcases.values()),
test_second_file="",
)

result = testdir.runpytest("-v", "--snapshot-details")
result.stdout.re_match_lines(
(
r"2 snapshots passed\. 2 snapshots unused\.",
r"Unused test_extra_a, test_extra_b "
r"\(__snapshots__[\\/]test_second_file.ambr\)",
r"Re-run pytest with --snapshot-update to delete unused snapshots\.",
)
)
assert result.ret == 1


def test_unused_snapshots_details_multiple_locations(
run_testfiles_with_update, testcases, extra_testcases
):
testdir = run_testfiles_with_update(
test_file=testcases, test_second_file=extra_testcases
)
testdir.makepyfile(
test_file=testcases["used"],
test_second_file=extra_testcases["extra_a"],
)

result = testdir.runpytest("-v", "--snapshot-details")
result.stdout.re_match_lines_random(
(
r"2 snapshots passed\. 2 snapshots unused\.",
r"Unused test_extra_b \(__snapshots__[\\/]test_second_file.ambr\)",
r"Unused test_unused \(__snapshots__[\\/]test_file.ambr\)",
r"Re-run pytest with --snapshot-update to delete unused snapshots\.",
)
)
assert result.ret == 1


def test_unused_snapshots_details_no_details_on_deletion(
run_testfiles_with_update, testcases
):
testdir = run_testfiles_with_update(test_file=testcases)
testdir.makepyfile(test_file=testcases["used"])

result = testdir.runpytest("-v", "--snapshot-details", "--snapshot-update")
result.stdout.re_match_lines(
(
r"1 snapshot passed\. 1 unused snapshot deleted\.",
r"Deleted test_unused \(__snapshots__[\\/]test_file.ambr\)",
)
)
assert result.ret == 0

0 comments on commit 1c50db0

Please sign in to comment.