-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_deep_merge.py
More file actions
149 lines (125 loc) · 3.8 KB
/
test_deep_merge.py
File metadata and controls
149 lines (125 loc) · 3.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import pytest
from docbuild.config.merge import deep_merge
@pytest.mark.parametrize(
"thedicts,expected",
[
#
pytest.param([], {}, id="empty"),
#
pytest.param([{"a": 1}, {"a": 2}], {"a": 2}, id="overwrite_scalar"),
#
pytest.param(
[{"a": 1, "b": 2}, {"a": 2}],
{"a": 2, "b": 2},
id="merge_keys_overwrite_scalar",
),
#
pytest.param(
[{"db": {"host": "localhost", "port": 1234}}, {"db": {"port": 5432}}],
{"db": {"host": "localhost", "port": 5432}},
id="nested_dicts_recursive",
),
#
pytest.param(
[{"a": [1, 2]}, {"a": [3, 4]}],
{"a": [1, 2, 3, 4]},
id="list_concatenation",
),
#
pytest.param(
[{"t": (1, 2)}, {"t": (3, 4)}],
{"t": (1, 2, 3, 4)},
id="tuple_concatenation",
),
#
pytest.param(
[{"a": [1, 2]}, {"a": 3}],
{"a": 3},
id="overwrite_list_with_scalar",
),
#
pytest.param(
[{"a": 1}, {"a": [2, 3]}],
{"a": [2, 3]},
id="overwrite_scalar_with_list",
),
#
pytest.param(
[{"x": {"l": ["a"]}}, {"x": {"l": ["b"]}}],
{"x": {"l": ["a", "b"]}},
id="nested_list_concatenation",
),
#
pytest.param(
[{"a": [{"x": 1}]}, {"a": [{"y": 2}]}],
{"a": [{"x": 1}, {"y": 2}]},
id="list_of_dicts_concatenation",
),
#
pytest.param(
[{"s": {1, 2}}, {"s": {3, 4}}],
{"s": {1, 2, 3, 4}},
id="set_union",
),
#
pytest.param(
[{"s": {1, 2}}, {"s": {2, 3}}],
{"s": {1, 2, 3}},
id="set_union_overlap",
),
],
)
def test_deep_merge(thedicts, expected):
assert deep_merge(*thedicts) == expected
def test_deep_merge_is_truly_deep():
d1 = {"nested": [1, 2], "deep": {"a": 1}}
d2 = {}
merged = deep_merge(d1, d2)
# Modify result
merged["nested"].append(3)
merged["deep"]["b"] = 2
# Assert original is untouched
assert d1["nested"] == [1, 2]
assert "b" not in d1["deep"]
def test_three_way_merge():
d1 = {"a": 1}
d2 = {"b": 2}
d3 = {"c": 3}
assert deep_merge(d1, d2, d3) == {"a": 1, "b": 2, "c": 3}
def test_deep_merge_inputs_remain_independent():
# Verify that subsequent dictionaries (not just the first) are also treated as immutable sources
d1 = {}
d2 = {"a": [1, 2], "b": {"x": 1}}
merged = deep_merge(d1, d2)
# Modify result
merged["a"].append(3)
merged["b"]["y"] = 2
# Assert input d2 is untouched
assert d2["a"] == [1, 2]
assert "y" not in d2["b"]
def test_deep_merge_return_type():
class MyMap(dict):
pass
m1 = MyMap({"a": 1})
m2 = {"b": 2}
result = deep_merge(m1, m2)
assert isinstance(result, dict)
assert not isinstance(result, MyMap)
assert result == {"a": 1, "b": 2}
def test_deep_merge_converts_nested_custom_mapping_to_dict():
class MyMap(dict):
pass
# MyMap instance nested inside a standard dict
d1 = {"nested": MyMap({"a": 1})}
d2 = {"nested": {"b": 2}}
result = deep_merge(d1, d2)
# Check that merge happened correctly
assert result == {"nested": {"a": 1, "b": 2}}
# Check that the nested dictionary is a plain dict, not MyMap
# This verifies that the line `existing = dict(existing)` was executed
# and the result contains the converted dict.
assert type(result["nested"]) is dict
assert not isinstance(result["nested"], MyMap)
# Verify original d1 is untouched
assert isinstance(d1["nested"], MyMap)
assert d1["nested"] == {"a": 1}