From 57f1099c681c70ac93749e01b857d53256adca78 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi Date: Sun, 12 Jan 2020 19:38:54 -0500 Subject: [PATCH] fix: allow hashable types to be used as dictionary keys (#103) * test: add failing test case * fix: always sort serialized * test: add dict keys test case * chore: update changelog * refactor: use serialize as sort key --- CHANGELOG.md | 1 + src/syrupy/extensions/amber.py | 11 +---- tests/__snapshots__/test_extension_amber.ambr | 49 +++++++++++++++++-- tests/test_extension_amber.py | 40 ++++++++++----- 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e5af692..e8bc486f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ From v1.0.0 onwards, this project adheres to [Semantic Versioning](https://semve ## Master (Unreleased) +- Fix issue with using hashables as dict keys or in sets (#103) - Add support for custom objects repr (#101) - Add support for nested test classes (#99) - Remove `_snapshot_subdirectory_name` from `SnapshotFossilizer` (#99) diff --git a/src/syrupy/extensions/amber.py b/src/syrupy/extensions/amber.py index ebdccd3c..2bf0b541 100644 --- a/src/syrupy/extensions/amber.py +++ b/src/syrupy/extensions/amber.py @@ -1,4 +1,3 @@ -import hashlib import os from types import GeneratorType from typing import ( @@ -79,18 +78,10 @@ def read_file(cls, filepath: str) -> "SnapshotFossil": @classmethod def sort(cls, iterable: Iterable[Any]) -> Iterable[Any]: - def _sort_key(value: Any) -> Any: - if isinstance(value, frozenset): - h = hashlib.sha256() - for element in cls.sort(value): - h.update(str(element).encode("utf-8")) - return h.hexdigest() - return value - try: return sorted(iterable) except TypeError: - return sorted(iterable, key=_sort_key) + return sorted(iterable, key=cls.serialize) @classmethod def with_indent(cls, string: str, depth: int) -> str: diff --git a/tests/__snapshots__/test_extension_amber.ambr b/tests/__snapshots__/test_extension_amber.ambr index 029a0e02..6c6b0bef 100644 --- a/tests/__snapshots__/test_extension_amber.ambr +++ b/tests/__snapshots__/test_extension_amber.ambr @@ -102,6 +102,27 @@ ], } --- +# name: test_dict[actual2] + { + 'a': 'Some ttext.', + 1: True, + ( + 1, + 2, + 3, + 4, + ): { + 'e': False, + }, + { + '1', + '2', + }: [ + '1', + 2, + ], + } +--- # name: test_empty_snapshot None --- @@ -139,7 +160,7 @@ # name: test_reflection SnapshotAssertion(name='snapshot', num_executions=0) --- -# name: test_set +# name: test_set[actual0] { 'a', 'is', @@ -147,14 +168,36 @@ 'this', } --- -# name: test_set.1 +# name: test_set[actual1] { + 'contains', + 'frozen', { '1', '2', }, + } +--- +# name: test_set[actual2] + { 'contains', - 'frozen', + 'tuple', + ( + 1, + 2, + ), + } +--- +# name: test_set[actual3] + { + 'contains', + 'namedtuple', + ( + 1, + 2, + 3, + 4, + ), } --- # name: test_string[0] diff --git a/tests/test_extension_amber.py b/tests/test_extension_amber.py index e49187f1..17171d91 100644 --- a/tests/test_extension_amber.py +++ b/tests/test_extension_amber.py @@ -49,30 +49,44 @@ def test_multiple_snapshots(snapshot): assert snapshot == "Third." +ExampleTuple = namedtuple("ExampleTuple", ["a", "b", "c", "d"]) + + +def test_tuple(snapshot): + assert snapshot == ("this", "is", ("a", "tuple")) + assert snapshot == ExampleTuple(a="this", b="is", c="a", d={"named", "tuple"}) + + +@pytest.mark.parametrize( + "actual", + [ + {"this", "is", "a", "set"}, + {"contains", "frozen", frozenset({"1", "2"})}, + {"contains", "tuple", (1, 2)}, + {"contains", "namedtuple", ExampleTuple(a=1, b=2, c=3, d=4)}, + ], +) +def test_set(snapshot, actual): + assert snapshot == actual + + @pytest.mark.parametrize( "actual", [ {"b": True, "c": "Some text.", "d": ["1", 2], "a": {"e": False}}, {"b": True, "c": "Some ttext.", "d": ["1", 2], "a": {"e": False}}, + { + 1: True, + "a": "Some ttext.", + frozenset({"1", "2"}): ["1", 2], + ExampleTuple(a=1, b=2, c=3, d=4): {"e": False}, + }, ], ) def test_dict(snapshot, actual): assert actual == snapshot -def test_set(snapshot): - assert snapshot == {"this", "is", "a", "set"} - assert snapshot == {"contains", "frozen", frozenset({"1", "2"})} - - -ExampleTuple = namedtuple("ExampleTuple", ["a", "b", "c", "d"]) - - -def test_tuple(snapshot): - assert snapshot == ("this", "is", ("a", "tuple")) - assert snapshot == ExampleTuple(a="this", b="is", c="a", d={"named", "tuple"}) - - def test_numbers(snapshot): assert snapshot == 3.5 assert snapshot == 7