From d283e87c2b8d1d5623ea3258653d579757bfa78c Mon Sep 17 00:00:00 2001 From: Noah Date: Sat, 23 Nov 2024 17:40:17 -0500 Subject: [PATCH] feat: add option to disable diffing (#924) For extremely large snapshot files, the diff algorithm is not very efficient. Until the algorithm can be modified to work with large files, there is now a --snapshot-diff-mode=disabled flag that can be specified to disable diffing on snapshot assertion failures. --- README.md | 1 + src/syrupy/__init__.py | 24 ++++++--- src/syrupy/assertion.py | 16 +++++- .../test_snapshot_option_diff_mode.py | 49 +++++++++++++++++++ .../syrupy/__snapshots__/test_diff_mode.ambr | 7 +++ tests/syrupy/test_diff_mode.py | 6 +++ 6 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 tests/integration/test_snapshot_option_diff_mode.py create mode 100644 tests/syrupy/__snapshots__/test_diff_mode.ambr create mode 100644 tests/syrupy/test_diff_mode.py diff --git a/README.md b/README.md index 0ba83bfc..6b5bfd76 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ These are the cli options exposed to `pytest` by the plugin. | `--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` | +| `--snapshot-diff-mode` | Configures how diffs are displayed on assertion failure. If working with very large snapshots, disabling the diff can improve performance. | `detailed` | ### Assertion Options diff --git a/src/syrupy/__init__.py b/src/syrupy/__init__.py index d4c3dc23..1ef751aa 100644 --- a/src/syrupy/__init__.py +++ b/src/syrupy/__init__.py @@ -12,7 +12,7 @@ import pytest -from .assertion import SnapshotAssertion +from .assertion import DiffMode, SnapshotAssertion from .constants import DISABLE_COLOR_ENV_VAR from .exceptions import FailedToLoadModuleMember from .extensions import DEFAULT_EXTENSION @@ -43,7 +43,7 @@ def __import_extension(value: Optional[str]) -> Any: raise argparse.ArgumentTypeError(e) from e -def pytest_addoption(parser: Any) -> None: +def pytest_addoption(parser: "pytest.Parser") -> None: """ Exposes snapshot plugin configuration to pytest. https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_addoption @@ -94,9 +94,17 @@ def pytest_addoption(parser: Any) -> None: dest="patch_pycharm_diff", help="Patch PyCharm diff", ) + group.addoption( + "--snapshot-diff-mode", + default=DiffMode.DETAILED, + choices=list(DiffMode), + type=DiffMode, + dest="diff_mode", + help="Controls how diffs are represented on snapshot assertion failure", + ) -def __terminal_color(config: Any) -> "ContextManager[None]": +def __terminal_color(config: "pytest.Config") -> "ContextManager[None]": env = {} if config.option.no_colors: env[DISABLE_COLOR_ENV_VAR] = "true" @@ -105,7 +113,7 @@ def __terminal_color(config: Any) -> "ContextManager[None]": def pytest_assertrepr_compare( - config: Any, op: str, left: Any, right: Any + config: "pytest.Config", op: str, left: Any, right: Any ) -> Optional[List[str]]: """ Return explanation for comparisons in failing assert expressions. @@ -119,10 +127,14 @@ def snapshot_name(name: str) -> str: if isinstance(left, SnapshotAssertion): assert_msg = reset(f"{snapshot_name(left.name)} {op} {received_name}") - return [assert_msg] + left.get_assert_diff() + return [assert_msg] + left.get_assert_diff( + diff_mode=config.option.diff_mode + ) elif isinstance(right, SnapshotAssertion): assert_msg = reset(f"{received_name} {op} {snapshot_name(right.name)}") - return [assert_msg] + right.get_assert_diff() + return [assert_msg] + right.get_assert_diff( + diff_mode=config.option.diff_mode + ) return None diff --git a/src/syrupy/assertion.py b/src/syrupy/assertion.py index 6c2bc7e7..3c2b89fb 100644 --- a/src/syrupy/assertion.py +++ b/src/syrupy/assertion.py @@ -4,6 +4,7 @@ dataclass, field, ) +from enum import Enum from gettext import gettext from typing import ( TYPE_CHECKING, @@ -35,6 +36,14 @@ ) +class DiffMode(Enum): + DETAILED = "detailed" + DISABLED = "disabled" + + def __str__(self) -> str: + return self.value + + @dataclass class AssertionResult: snapshot_location: str @@ -210,7 +219,9 @@ def _serialize(self, data: "SerializableData") -> "SerializedData": data, exclude=self._exclude, include=self._include, matcher=self.__matcher ) - def get_assert_diff(self) -> List[str]: + def get_assert_diff( + self, *, diff_mode: "DiffMode" = DiffMode.DETAILED + ) -> List[str]: assertion_result = self._execution_results[self.num_executions - 1] if assertion_result.exception: if isinstance(assertion_result.exception, (TaintedSnapshotError,)): @@ -247,7 +258,8 @@ def get_assert_diff(self) -> List[str]: ) if not assertion_result.success: snapshot_data = snapshot_data if snapshot_data is not None else "" - diff.extend(self.extension.diff_lines(serialized_data, snapshot_data)) + if diff_mode == DiffMode.DETAILED: + diff.extend(self.extension.diff_lines(serialized_data, snapshot_data)) return diff def __with_prop(self, prop_name: str, prop_value: Any) -> None: diff --git a/tests/integration/test_snapshot_option_diff_mode.py b/tests/integration/test_snapshot_option_diff_mode.py new file mode 100644 index 00000000..788e9e30 --- /dev/null +++ b/tests/integration/test_snapshot_option_diff_mode.py @@ -0,0 +1,49 @@ +import pytest + + +@pytest.fixture +def testfile(testdir) -> pytest.Testdir: + testdir.makepyfile( + test_file=( + """ + def test_case(snapshot): + assert snapshot == "some-value" + """ + ), + ) + return testdir + + +def test_diff_mode_disabled_does_not_print_diff( + testfile, +): + # Generate initial snapshot + result = testfile.runpytest("-v", "--snapshot-update") + result.stdout.re_match_lines((r"1 snapshot generated\.",)) + assert result.ret == 0 + + # Modify snapshot to generate diff + testfile.makepyfile( + test_file=( + """ + def test_case(snapshot): + assert snapshot == "some-other-value" + """ + ), + ) + + # With diff we expect to see "some-other-value" + result = testfile.runpytest("-v", "--snapshot-diff-mode=detailed") + result.stdout.re_match_lines( + ( + r".*- 'some-value'", + r".*\+ 'some-other-value'", + ) + ) + assert result.ret == 1 + + # Without diff we do not expect to see "some-other-value" + result = testfile.runpytest("-v", "--snapshot-diff-mode=disabled") + result.stdout.no_re_match_line(r".*- 'some-value'") + result.stdout.no_re_match_line(r".*\+ 'some-other-value'") + assert result.ret == 1 diff --git a/tests/syrupy/__snapshots__/test_diff_mode.ambr b/tests/syrupy/__snapshots__/test_diff_mode.ambr new file mode 100644 index 00000000..77ed11fc --- /dev/null +++ b/tests/syrupy/__snapshots__/test_diff_mode.ambr @@ -0,0 +1,7 @@ +# serializer version: 1 +# name: test_can_be_stringified + 'detailed' +# --- +# name: test_can_be_stringified.1 + 'disabled' +# --- diff --git a/tests/syrupy/test_diff_mode.py b/tests/syrupy/test_diff_mode.py new file mode 100644 index 00000000..3bab8009 --- /dev/null +++ b/tests/syrupy/test_diff_mode.py @@ -0,0 +1,6 @@ +from syrupy.assertion import DiffMode + + +def test_can_be_stringified(snapshot): + assert snapshot == str(DiffMode.DETAILED) + assert snapshot == str(DiffMode.DISABLED)