Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added a new CLI flag: --snapshot-patch-pycharm-diff #864

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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. See [IDE Integrations](#ide-integrations) | `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)

## IDE Integrations

### PyCharm

The [PyCharm](https://www.jetbrains.com/pycharm/) IDE comes with a built-in tool for visualizing differences between expected and actual results in a test. To properly render Syrupy snapshots in the PyCharm diff viewer, we need to apply a patch to the diff viewer library. To do this, use the `--snapshot-patch-pycharm-diff` flag, e.g.:

In your `pytest.ini`:

```ini
[pytest]
addopts = --snapshot-patch-pycharm-diff
```

See [#675](https://github.com/syrupy-project/syrupy/issues/675) for the original issue.

## Uninstalling

```python
Expand Down
22 changes: 20 additions & 2 deletions src/syrupy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import (
Any,
ContextManager,
Iterator,
List,
Optional,
)
Expand All @@ -16,6 +17,7 @@
from .exceptions import FailedToLoadModuleMember
from .extensions import DEFAULT_EXTENSION
from .location import PyTestLocation
from .patches.pycharm_diff import patch_pycharm_diff
from .session import SnapshotSession
from .terminal import (
received_style,
Expand Down Expand Up @@ -85,6 +87,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 @@ -185,10 +194,19 @@ def pytest_terminal_summary(


@pytest.fixture
def snapshot(request: Any) -> "SnapshotAssertion":
def snapshot(request: "pytest.FixtureRequest") -> "SnapshotAssertion":
return SnapshotAssertion(
update_snapshots=request.config.option.update_snapshots,
extension_class=__import_extension(request.config.option.default_extension),
test_location=PyTestLocation(request.node),
session=request.session.config._syrupy,
session=request.session.config._syrupy, # type: ignore
)


@pytest.fixture(scope="session", autouse=True)
def _syrupy_apply_ide_patches(request: "pytest.FixtureRequest") -> Iterator[None]:
if request.config.option.patch_pycharm_diff:
with patch_pycharm_diff():
yield
else:
yield
Empty file added src/syrupy/patches/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions src/syrupy/patches/pycharm_diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import warnings
from contextlib import contextmanager
from functools import wraps
from inspect import signature
from typing import (
Any,
Iterator,
)

from syrupy.assertion import SnapshotAssertion


@contextmanager
def patch_pycharm_diff() -> Iterator[None]:
"""
Applies PyCharm diff patch to add Syrupy snapshot support.
See: https://github.com/syrupy-project/syrupy/issues/675
"""

try:
from teamcity.diff_tools import EqualsAssertionError # type: ignore
except ImportError:
warnings.warn(
"Failed to patch PyCharm's diff tools. Skipping patch.",
stacklevel=2,
)
yield
return

old_init = EqualsAssertionError.__init__
old_init_signature = signature(old_init)

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

# 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"]

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

Check warning on line 50 in src/syrupy/patches/pycharm_diff.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/patches/pycharm_diff.py#L50

Added line #L50 was not covered by tests

old_init(self, *args, **kwargs)

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

Check warning on line 57 in src/syrupy/patches/pycharm_diff.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/patches/pycharm_diff.py#L57

Added line #L57 was 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 63 in src/syrupy/patches/pycharm_diff.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/patches/pycharm_diff.py#L63

Added line #L63 was not covered by tests

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

Check warning on line 67 in src/syrupy/patches/pycharm_diff.py

View check run for this annotation

Codecov / codecov/patch

src/syrupy/patches/pycharm_diff.py#L67

Added line #L67 was not covered by tests

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

try:
EqualsAssertionError.__init__ = new_init
yield
finally:
EqualsAssertionError.__init__ = old_init
Loading
Loading