Skip to content

Commit

Permalink
feat: Added a new CLI flag: --snapshot-patch-pycharm-diff
Browse files Browse the repository at this point in the history
(Only relevant for Pycharm users) If the flag is passed, Syrupy will import and extend Pycharm's default diff script to provide more meaning full diffs when viewing snapshots mismatches in Pycharm's diff viewer
  • Loading branch information
UltimateLobster authored and noahnu committed Aug 23, 2024
1 parent 3c2b962 commit a4dfcf5
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 2 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,13 @@ Both options will generate equivalent snapshots but the latter is only viable wh
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. | [AmberSnapshotExtension](https://github.com/syrupy-project/syrupy/blob/main/src/syrupy/extensions/amber/__init__.py) |
| `--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. |
| `--snapshot-patch-pycharm-diff`| Override Pycharm's default diffs viewer when looking at snapshot diffs. More information in [Using Syrupy from Pycharm] | `False` |

### Assertion Options

Expand Down Expand Up @@ -470,6 +471,21 @@ The generated snapshot:
- [JPEG image extension](https://github.com/syrupy-project/syrupy/tree/main/tests/examples/test_custom_image_extension.py)
- [Built-in image extensions](https://github.com/syrupy-project/syrupy/blob/main/tests/syrupy/extensions/image/test_image_svg.py)

### Viewing Snapshot Diffs in Pycharm IDEs
Pycharm IDEs come with a built-in tool that helps you to more easily identify differences between the expected result and the actual result in a test.
However, this tool does not play nicely with syrupy snapshots by default.

Fortunately, Syrupy comes with a runtime flag that will extend Pycharm's default behavior to work nicely with snapshots.
Pass the `--snapshot-patch-pycharm-diff` flag in your pytest run configuration or create a `pytest.ini` in your project with the following content:
```ini
[pytest]
addopts = --snapshot-patch-pycharm-diff

```

Now you will be able to see snapshot diffs more easily.


## Uninstalling

```python
Expand Down
78 changes: 77 additions & 1 deletion src/syrupy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import argparse
import sys
from functools import lru_cache
import warnings
from functools import (
lru_cache,
wraps,
)
from gettext import gettext
from inspect import signature
from typing import (
Any,
ContextManager,
Iterable,
List,
Optional,
)
Expand Down Expand Up @@ -85,6 +91,13 @@ def pytest_addoption(parser: Any) -> None:
dest="no_colors",
help="Disable test results output highlighting",
)
group.addoption(
"--snapshot-patch-pycharm-diff",
action="store_true",
default=False,
dest="patch_pycharm_diff",
help="Patch Pycharm diff",
)


def __terminal_color(config: Any) -> "ContextManager[None]":
Expand Down Expand Up @@ -192,3 +205,66 @@ def snapshot(request: Any) -> "SnapshotAssertion":
test_location=PyTestLocation(request.node),
session=request.session.config._syrupy,
)


@pytest.fixture(scope="session", autouse=True)
def _patch_pycharm_diff_viewer_for_snapshots(request: Any) -> Iterable[None]:
if not request.config.option.patch_pycharm_diff:
yield
return

try:
from teamcity.diff_tools import EqualsAssertionError # type: ignore
except ImportError:
warnings.warn(

Check warning on line 219 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L216-L219

Added lines #L216 - L219 were not covered by tests
"Pycharm's diff tools have failed to be imported. "
"Snapshot diffs will not be patched.",
stacklevel=2,
)
yield
return

Check warning on line 225 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L224-L225

Added lines #L224 - L225 were not covered by tests

old_init = EqualsAssertionError.__init__
old_init_signature = signature(old_init)

Check warning on line 228 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L227-L228

Added lines #L227 - L228 were not covered by tests

@wraps(old_init)
def new_init(self: EqualsAssertionError, *args: Any, **kwargs: Any) -> None:

Check warning on line 231 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L230-L231

Added lines #L230 - L231 were not covered by tests

# Extract the __init__ arguments as originally passed in order to
# process them later
parameters = old_init_signature.bind(self, *args, **kwargs)
parameters.apply_defaults()
expected = parameters.arguments["expected"]
actual = parameters.arguments["actual"]
real_exception = parameters.arguments["real_exception"]

Check warning on line 239 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L235-L239

Added lines #L235 - L239 were not covered by tests

if isinstance(expected, SnapshotAssertion):
snapshot = expected
elif isinstance(actual, SnapshotAssertion):
snapshot = actual

Check warning on line 244 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L241-L244

Added lines #L241 - L244 were not covered by tests
else:
snapshot = None

Check warning on line 246 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L246

Added line #L246 was not covered by tests

old_init(self, *args, **kwargs)

Check warning on line 248 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L248

Added line #L248 was not covered by tests

# No snapshot was involved in the assertion. Let the old logic do its
# thing.
if snapshot is None:
return

Check warning on line 253 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L252-L253

Added lines #L252 - L253 were not covered by tests

# Although a snapshot was involved in the assertion, it seems the error
# was a result of a non-assertion exception (Ex. `assert 1/0`).
# Therefore, We will not do anything here either.
if real_exception is not None:
return

Check warning on line 259 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L258-L259

Added lines #L258 - L259 were not covered by tests

assertion_result = snapshot.executions[snapshot.num_executions - 1]
if assertion_result.exception is not None:
return

Check warning on line 263 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L261-L263

Added lines #L261 - L263 were not covered by tests

self.expected = str(assertion_result.recalled_data)
self.actual = str(assertion_result.asserted_data)

Check warning on line 266 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L265-L266

Added lines #L265 - L266 were not covered by tests

EqualsAssertionError.__init__ = new_init
yield
EqualsAssertionError.__init__ = old_init

Check warning on line 270 in src/syrupy/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/__init__.py#L268-L270

Added lines #L268 - L270 were not covered by tests

0 comments on commit a4dfcf5

Please sign in to comment.