diff --git a/changelog.d/20241108_192001_15r10nk-git_fix_cli.md b/changelog.d/20241108_192001_15r10nk-git_fix_cli.md new file mode 100644 index 00000000..a4df405c --- /dev/null +++ b/changelog.d/20241108_192001_15r10nk-git_fix_cli.md @@ -0,0 +1,40 @@ + + + +### Added + +- command line shortcuts can be defined to simplify your workflows. `--review` and `--fix` are defined by default. See the [documentation](https://15r10nk.github.io/inline-snapshot/configuration/) for details. + + + + + diff --git a/changelog.d/20241109_074441_15r10nk-git_fix_cli.md b/changelog.d/20241109_074441_15r10nk-git_fix_cli.md new file mode 100644 index 00000000..e4cd527b --- /dev/null +++ b/changelog.d/20241109_074441_15r10nk-git_fix_cli.md @@ -0,0 +1,42 @@ + + + + + +### Changed + +- `--inline-snapshot=create/fix/trim/update` will no longer show reports for other categories. + You can use `--inline-snapshot=create,report` if you want to use the old behaviour. + + + + diff --git a/docs/categories.md b/docs/categories.md index 8145aa02..dd124799 100644 --- a/docs/categories.md +++ b/docs/categories.md @@ -58,7 +58,7 @@ The result of each comparison is `True` if you change something from this catego
- + ``` python def test_something(): assert 8 == snapshot(5) diff --git a/docs/configuration.md b/docs/configuration.md index c3f86efd..76f269ef 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,15 +1,20 @@ - - Default configuration: ``` toml [tool.inline-snapshot] hash-length=15 default-flags=["short-report"] + +[tool.inline-snapshot.shortcuts] +review=["review"] +fix=["create","fix"] ``` -* *hash-length:* specifies the length of the hash used by `external()` in the code representation. +* **hash-length:** specifies the length of the hash used by `external()` in the code representation. This does not affect the hash length used to store the data. The hash should be long enough to avoid hash collisions. -* *default-flags:* defines which flags should be used if there are no flags specified with `--inline-snapshot=...`. +* **default-flags:** defines which flags should be used if there are no flags specified with `--inline-snapshot=...`. You can also use the environment variable `INLINE_SNAPSHOT_DEFAULT_FLAGS=...` to specify the flags and to override those in the configuration file. + +* **shortcuts:** allows you to define custom commands to simplify your workflows. + `--fix` and `--review` are defined by default, but this configuration can be changed to fit your needs. diff --git a/docs/eq_snapshot.md b/docs/eq_snapshot.md index 899ba13e..94b02f5f 100644 --- a/docs/eq_snapshot.md +++ b/docs/eq_snapshot.md @@ -21,7 +21,7 @@ Example: ``` === "value changed" - + ``` python hl_lines="2" def test_something(): assert 2 + 40 == snapshot(4) @@ -111,7 +111,7 @@ Example: ``` === "changed payload" - + ``` python hl_lines="9" from inline_snapshot import snapshot from dirty_equals import IsDatetime diff --git a/docs/index.md b/docs/index.md index d17d9c16..c8f8c957 100644 --- a/docs/index.md +++ b/docs/index.md @@ -55,7 +55,7 @@ Maybe that is correct and you should fix your code, or your code is correct and you want to update your test results. === "changed code" - + ``` python hl_lines="2" def something(): return (1548 * 18489) // 18 diff --git a/docs/limitations.md b/docs/limitations.md new file mode 100644 index 00000000..1435e501 --- /dev/null +++ b/docs/limitations.md @@ -0,0 +1,8 @@ + +## pytest assert rewriting is disabled + +inline-snapshot must disable pytest assert-rewriting if you use report/review/create/fix/trim/update flags. + +## xdist is not supported + +You can not use inline-snapshot in combination with `pytest-xdist`. The use of `-n=...` implies `--inline-snapshot=disable`. diff --git a/docs/testing.md b/docs/testing.md index b712b7aa..9f6b1a2b 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -59,7 +59,7 @@ The following example shows how you can use the `Example` class to test what inl report=snapshot( """\ Error: one snapshot is missing a value (--inline-snapshot=create) - You can also use --inline-snapshot=review to approve the changes interactiv\ + You can also use --inline-snapshot=review to approve the changes interactively\ """ ), ).run_pytest( # run with create flag and check the changed files diff --git a/mkdocs.yml b/mkdocs.yml index 3158094f..82efbc38 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,6 +46,7 @@ nav: - Configuration: configuration.md - Code generation: code_generation.md - Testing: testing.md +- Limitations: limitations.md - Contributing: contributing.md - Changelog: changelog.md diff --git a/pyproject.toml b/pyproject.toml index 95026fd4..fdf30424 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,3 +163,7 @@ venvPath = ".nox" [tool.scriv] format = "md" version = "command: cz bump --get-next" + +[tool.inline-snapshot.shortcuts] +sfix="create,fix" +review="create,review" diff --git a/src/inline_snapshot/_config.py b/src/inline_snapshot/_config.py index 4215b84e..32b27108 100644 --- a/src/inline_snapshot/_config.py +++ b/src/inline_snapshot/_config.py @@ -3,8 +3,10 @@ from dataclasses import dataclass from dataclasses import field from pathlib import Path +from typing import Dict from typing import List + if sys.version_info >= (3, 11): from tomllib import loads else: @@ -15,6 +17,7 @@ class Config: hash_length: int = 12 default_flags: List[str] = field(default_factory=lambda: ["short-report"]) + shortcuts: Dict[str, List[str]] = field(default_factory=dict) config = Config() @@ -35,11 +38,16 @@ def read_config(path: Path) -> Config: result.hash_length = config["hash-length"] except KeyError: pass + try: result.default_flags = config["default-flags"] except KeyError: pass + result.shortcuts = config.get( + "shortcuts", {"fix": ["create", "fix"], "review": ["review"]} + ) + env_var = "INLINE_SNAPSHOT_DEFAULT_FLAGS" if env_var in os.environ: result.default_flags = os.environ[env_var].split(",") diff --git a/src/inline_snapshot/_inline_snapshot.py b/src/inline_snapshot/_inline_snapshot.py index 586965a4..26c98478 100644 --- a/src/inline_snapshot/_inline_snapshot.py +++ b/src/inline_snapshot/_inline_snapshot.py @@ -44,6 +44,14 @@ class NotImplementedYet(Exception): _files_with_snapshots: Set[str] = set() _missing_values = 0 +_incorrect_values = 0 + + +def _return(result): + global _incorrect_values + if not result: + _incorrect_values += 1 + return result class InlineSnapshotSyntaxWarning(Warning): @@ -328,8 +336,11 @@ def use_valid_old_values(old_value, new_value): if self._new_value is undefined: self._new_value = use_valid_old_values(self._old_value, clone(other)) - - return self._visible_value() == other + if self._old_value is undefined or ignore_old_value(): + return True + return _return(self._old_value == other) + else: + return _return(self._new_value == other) def _new_code(self): return self._value_to_code(self._new_value) @@ -514,12 +525,14 @@ def _generic_cmp(self, other): if self._new_value is undefined: self._new_value = clone(other) + if self._old_value is undefined or ignore_old_value(): + return True + return _return(self.cmp(self._old_value, other)) else: - self._new_value = ( - self._new_value if self.cmp(self._new_value, other) else clone(other) - ) + if not self.cmp(self._new_value, other): + self._new_value = clone(other) - return self.cmp(self._visible_value(), other) + return _return(self.cmp(self._visible_value(), other)) def _new_code(self): return self._value_to_code(self._new_value) @@ -609,7 +622,7 @@ def __contains__(self, item): if ignore_old_value() or self._old_value is undefined: return True else: - return item in self._old_value + return _return(item in self._old_value) def _new_code(self): return self._value_to_code(self._new_value) diff --git a/src/inline_snapshot/pytest_plugin.py b/src/inline_snapshot/pytest_plugin.py index d97a4407..ae8235a2 100644 --- a/src/inline_snapshot/pytest_plugin.py +++ b/src/inline_snapshot/pytest_plugin.py @@ -21,7 +21,7 @@ from ._rewrite_code import ChangeRecorder -def pytest_addoption(parser): +def pytest_addoption(parser, pluginmanager): group = parser.getgroup("inline-snapshot") group.addoption( @@ -39,9 +39,22 @@ def pytest_addoption(parser): "fix: change snapshots which currently break your tests\n", ) + config_path = Path("pyproject.toml") + if config_path.exists(): + config = _config.read_config(Path("pyproject.toml")) + for name, value in config.shortcuts.items(): + value = ",".join(value) + group.addoption( + f"--{name}", + action="store_const", + const=value, + dest="inline_snapshot", + help=f'shortcut for "--inline-snapshot={value}"', + ) + categories = {"create", "update", "trim", "fix"} -flags = {} +flags = set() def xdist_running(config): @@ -104,20 +117,28 @@ def pytest_configure(config): @pytest.fixture(autouse=True) def snapshot_check(): _inline_snapshot._missing_values = 0 + _inline_snapshot._incorrect_values = 0 yield missing_values = _inline_snapshot._missing_values + incorrect_values = _inline_snapshot._incorrect_values if missing_values != 0 and not _inline_snapshot._update_flags.create: pytest.fail( ( - f"your snapshot is missing one value run pytest with --inline-snapshot=create to create it" + f"your snapshot is missing one value." if missing_values == 1 - else f"your snapshot is missing {missing_values} values run pytest with --inline-snapshot=create to create them" + else f"your snapshot is missing {missing_values} values." ), pytrace=False, ) + if incorrect_values != 0 and not _inline_snapshot._update_flags.fix: + pytest.fail( + "some snapshots in this test have incorrect values.", + pytrace=False, + ) + def pytest_assertrepr_compare(config, op, left, right): results = [] @@ -264,7 +285,7 @@ def report(flag, message, message_n): if sum(snapshot_changes.values()) != 0: console.print( - "\nYou can also use [b]--inline-snapshot=review[/] to approve the changes interactiv", + "\nYou can also use [b]--inline-snapshot=review[/] to approve the changes interactively", highlight=False, ) @@ -279,6 +300,9 @@ def report(flag, message, message_n): if not changes[flag]: continue + if not {"review", "report", flag} & flags: + continue + console.rule(f"[yellow bold]{flag.capitalize()} snapshots") with ChangeRecorder().activate() as cr: diff --git a/src/inline_snapshot/testing/_example.py b/src/inline_snapshot/testing/_example.py index a52ae0b0..6ed4e7a3 100644 --- a/src/inline_snapshot/testing/_example.py +++ b/src/inline_snapshot/testing/_example.py @@ -32,6 +32,7 @@ def snapshot_env(): external.storage, inline_snapshot._files_with_snapshots, inline_snapshot._missing_values, + inline_snapshot._incorrect_values, ) inline_snapshot.snapshots = {} @@ -40,6 +41,7 @@ def snapshot_env(): external.storage = None inline_snapshot._files_with_snapshots = set() inline_snapshot._missing_values = 0 + inline_snapshot._incorrect_values = 0 try: yield @@ -51,6 +53,7 @@ def snapshot_env(): external.storage, inline_snapshot._files_with_snapshots, inline_snapshot._missing_values, + inline_snapshot._incorrect_values, ) = current diff --git a/tests/test_config.py b/tests/test_config.py index 85d8e76d..98fcb518 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -41,3 +41,16 @@ def test_config_env(): Example(file_to_trim).run_pytest( env={"INLINE_SNAPSHOT_DEFAULT_FLAGS": "trim"}, changed_files=trimmed_files ) + + +def test_shortcuts(): + + Example( + { + **file_to_trim, + "pyproject.toml": """ +[tool.inline-snapshot.shortcuts] +strim=["trim"] + """, + } + ).run_pytest(["--strim"], changed_files=trimmed_files) diff --git a/tests/test_external.py b/tests/test_external.py index acab3b7f..05958108 100644 --- a/tests/test_external.py +++ b/tests/test_external.py @@ -87,20 +87,6 @@ def test_something(): assert result.report == snapshot( """\ --------------------------------- Fix snapshots --------------------------------- -+-------------------------------- test_file.py --------------------------------+ -| @@ -4,5 +4,5 @@ | -| | -| from inline_snapshot import external | -| | -| def test_something(): | -| - assert "hello" == snapshot(external("bbbbb*.txt")) | -| + assert "hello" == snapshot("hello") | -| assert 2 == snapshot(1+1) | -+------------------------------------------------------------------------------+ -These changes are not applied. -Use --inline-snapshot=fix to apply them, or use the interactive mode with ---inline-snapshot=review ------------------------------- Update snapshots ------------------------------- +-------------------------------- test_file.py --------------------------------+ | @@ -5,4 +5,4 @@ | diff --git a/tests/test_pytest_plugin.py b/tests/test_pytest_plugin.py index 14413c96..896eec95 100644 --- a/tests/test_pytest_plugin.py +++ b/tests/test_pytest_plugin.py @@ -23,7 +23,7 @@ def test_a(): assert result.report == snapshot( """\ Error: one snapshot is missing a value (--inline-snapshot=create) -You can also use --inline-snapshot=review to approve the changes interactiv +You can also use --inline-snapshot=review to approve the changes interactively """ ) @@ -65,12 +65,12 @@ def test_a(): result = project.run() - result.assert_outcomes(failed=1) + result.assert_outcomes(failed=1, errors=1) assert result.report == snapshot( """\ Error: one snapshot has incorrect values (--inline-snapshot=fix) -You can also use --inline-snapshot=review to approve the changes interactiv +You can also use --inline-snapshot=review to approve the changes interactively """ ) @@ -157,7 +157,7 @@ def test_a(): assert result.report == snapshot( """\ Info: one snapshot can be trimmed (--inline-snapshot=trim) -You can also use --inline-snapshot=review to approve the changes interactiv +You can also use --inline-snapshot=review to approve the changes interactively """ ) @@ -199,13 +199,13 @@ def test_a(): result = project.run() - result.assert_outcomes(failed=1) + result.assert_outcomes(failed=1, errors=1) assert result.report == snapshot( """\ Error: one snapshot has incorrect values (--inline-snapshot=fix) Info: one snapshot can be trimmed (--inline-snapshot=trim) -You can also use --inline-snapshot=review to approve the changes interactiv +You can also use --inline-snapshot=review to approve the changes interactively """ ) @@ -236,21 +236,6 @@ def test_a(): | assert 5 == snapshot(5) | +------------------------------------------------------------------------------+ These changes will be applied, because you used --inline-snapshot=trim -------------------------------- Update snapshots ------------------------------- -+-------------------------------- test_file.py --------------------------------+ -| @@ -4,6 +4,6 @@ | -| | -| | -| | -| def test_a(): | -| - assert "5" == snapshot('''5''') | -| + assert "5" == snapshot("5") | -| assert 5 <= snapshot(5) | -| assert 5 == snapshot(5) | -+------------------------------------------------------------------------------+ -These changes are not applied. -Use --inline-snapshot=update to apply them, or use the interactive mode with ---inline-snapshot=review """ ) @@ -283,6 +268,73 @@ def test_a(): ) +def test_multiple_report(project): + project.setup( + """\ +def test_a(): + assert "5" == snapshot('''5''') + assert 5 <= snapshot(8) + assert 5 == snapshot(4) +""" + ) + + result = project.run("--inline-snapshot=trim,report") + + assert result.report == snapshot( + """\ +-------------------------------- Fix snapshots --------------------------------- ++-------------------------------- test_file.py --------------------------------+ +| @@ -6,4 +6,4 @@ | +| | +| def test_a(): | +| assert "5" == snapshot('''5''') | +| assert 5 <= snapshot(8) | +| - assert 5 == snapshot(4) | +| + assert 5 == snapshot(5) | ++------------------------------------------------------------------------------+ +These changes are not applied. +Use --inline-snapshot=fix to apply them, or use the interactive mode with +--inline-snapshot=review +-------------------------------- Trim snapshots -------------------------------- ++-------------------------------- test_file.py --------------------------------+ +| @@ -5,5 +5,5 @@ | +| | +| | +| def test_a(): | +| assert "5" == snapshot('''5''') | +| - assert 5 <= snapshot(8) | +| + assert 5 <= snapshot(5) | +| assert 5 == snapshot(4) | ++------------------------------------------------------------------------------+ +These changes will be applied, because you used --inline-snapshot=trim +------------------------------- Update snapshots ------------------------------- ++-------------------------------- test_file.py --------------------------------+ +| @@ -4,6 +4,6 @@ | +| | +| | +| | +| def test_a(): | +| - assert "5" == snapshot('''5''') | +| + assert "5" == snapshot("5") | +| assert 5 <= snapshot(5) | +| assert 5 == snapshot(4) | ++------------------------------------------------------------------------------+ +These changes are not applied. +Use --inline-snapshot=update to apply them, or use the interactive mode with +--inline-snapshot=review +""" + ) + + assert project.source == snapshot( + """\ +def test_a(): + assert "5" == snapshot('''5''') + assert 5 <= snapshot(5) + assert 5 == snapshot(4) +""" + ) + + def test_black_config(project): project.setup( """\ @@ -492,12 +544,12 @@ def test_sub_snapshot(): """\ ============================================================================ ERRORS ============================================================================ ____________________________________________________________ ERROR at teardown of test_sub_snapshot ____________________________________________________________ -your snapshot is missing one value run pytest with --inline-snapshot=create to create it +your snapshot is missing one value. ======================================================================= inline snapshot ======================================================================== Error: one snapshot is missing a value (--inline-snapshot=create) -You can also use --inline-snapshot=review to approve the changes interactiv +You can also use --inline-snapshot=review to approve the changes interactively =================================================================== short test summary info ==================================================================== -ERROR test_file.py::test_sub_snapshot - Failed: your snapshot is missing one value run pytest with --inline-snapshot=create to create it +ERROR test_file.py::test_sub_snapshot - Failed: your snapshot is missing one value. ================================================================== 1 passed, 1 error in