From a4dfcf51df4df6d5da1afd863126ddca3f2c9b43 Mon Sep 17 00:00:00 2001 From: Nir Schulman Date: Sat, 15 Jun 2024 12:32:33 +0300 Subject: [PATCH] feat: Added a new CLI flag: --snapshot-patch-pycharm-diff (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 --- README.md | 18 +++++++++- src/syrupy/__init__.py | 78 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5177b1fb..b7c6be91 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index 3c42f7fe..1899fb2d 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -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, ) @@ -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]": @@ -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( + "Pycharm's diff tools have failed to be imported. " + "Snapshot diffs will not be patched.", + 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 + + old_init(self, *args, **kwargs) + + # No snapshot was involved in the assertion. Let the old logic do its + # thing. + if snapshot is None: + return + + # 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 + + assertion_result = snapshot.executions[snapshot.num_executions - 1] + if assertion_result.exception is not None: + return + + self.expected = str(assertion_result.recalled_data) + self.actual = str(assertion_result.asserted_data) + + EqualsAssertionError.__init__ = new_init + yield + EqualsAssertionError.__init__ = old_init