398 lines
13 KiB
Python
398 lines
13 KiB
Python
|
"""mergedeep test module"""
|
||
|
import inspect
|
||
|
import unittest
|
||
|
from collections import Counter
|
||
|
from copy import deepcopy
|
||
|
|
||
|
from mergedeep import merge, Strategy
|
||
|
|
||
|
|
||
|
class test_mergedeep(unittest.TestCase):
|
||
|
"""mergedeep function tests."""
|
||
|
|
||
|
##############################################################################################################################
|
||
|
# REPLACE
|
||
|
##############################################################################################################################
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_replace_strategy_and_only_mutate_target(self,):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"f": [4, 5, 6],
|
||
|
"g": (100, 200),
|
||
|
"h": Counter({"a": 5, "b": 1, "c": 1}),
|
||
|
"i": 2,
|
||
|
"j": Counter({"z": 2}),
|
||
|
"z": Counter({"a": 2}),
|
||
|
}
|
||
|
|
||
|
a = {
|
||
|
"a": {"b": {"c": 5}},
|
||
|
"d": 1,
|
||
|
"e": {2: 3},
|
||
|
"f": [1, 2, 3],
|
||
|
"g": (2, 4, 6),
|
||
|
"h": Counter({"a": 1, "b": 1}),
|
||
|
"j": 1,
|
||
|
}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {
|
||
|
"a": {"B": {"C": 10}},
|
||
|
"d": 2,
|
||
|
"e": 2,
|
||
|
"f": [4, 5, 6],
|
||
|
"g": (100, 200),
|
||
|
"h": Counter({"a": 5, "c": 1}),
|
||
|
"i": Counter({"a": 1}),
|
||
|
"z": Counter({"a": 2}),
|
||
|
}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {
|
||
|
"a": {"b": {"_c": 15}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"i": 2,
|
||
|
"j": Counter({"z": 2}),
|
||
|
"z": Counter({"a": 2}),
|
||
|
}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.REPLACE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
def test_should_merge_2_dicts_into_existing_dict_using_replace_strategy_and_only_mutate_target(self,):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"f": [4, 5, 6],
|
||
|
"g": (100, 200),
|
||
|
"h": Counter({"a": 1, "b": 1, "c": 1}),
|
||
|
"i": 2,
|
||
|
"j": Counter({"z": 2}),
|
||
|
}
|
||
|
|
||
|
a = {
|
||
|
"a": {"b": {"c": 5}},
|
||
|
"d": 1,
|
||
|
"e": {2: 3},
|
||
|
"f": [1, 2, 3],
|
||
|
"g": (2, 4, 6),
|
||
|
"h": Counter({"a": 1, "b": 1}),
|
||
|
"j": 1,
|
||
|
}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {
|
||
|
"a": {"B": {"C": 10}},
|
||
|
"d": 2,
|
||
|
"e": 2,
|
||
|
"f": [4, 5, 6],
|
||
|
"g": (100, 200),
|
||
|
"h": Counter({"a": 1, "c": 1}),
|
||
|
"i": Counter({"a": 1}),
|
||
|
}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}, "i": 2, "j": Counter({"z": 2})}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge(a, b, c, strategy=Strategy.REPLACE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(actual, a)
|
||
|
self.assertNotEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
def test_should_have_default_strategy_of_replace(self):
|
||
|
func_spec = inspect.getfullargspec(merge)
|
||
|
default_strategy = Strategy.REPLACE
|
||
|
|
||
|
self.assertEqual(func_spec.kwonlydefaults.get("strategy"), default_strategy)
|
||
|
|
||
|
# mock_merge.method.assert_called_with(target, source, strategy=Strategy.REPLACE)
|
||
|
|
||
|
##############################################################################################################################
|
||
|
# ADDITIVE
|
||
|
##############################################################################################################################
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_lists_and_only_mutate_target(self,):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"f": [1, 2, 3, 4, 5, 6],
|
||
|
}
|
||
|
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_sets_and_only_mutate_target(self,):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"f": {1, 2, 3, 4, 5, 6},
|
||
|
}
|
||
|
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": {1, 2, 3}}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": {4, 5, 6}}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_tuples_and_only_mutate_target(self,):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"f": (1, 2, 3, 4, 5, 6),
|
||
|
}
|
||
|
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": (1, 2, 3)}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": (4, 5, 6)}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_counters_and_only_mutate_target(self,):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"f": Counter({"a": 2, "c": 1, "b": 1}),
|
||
|
"i": 2,
|
||
|
"j": Counter({"z": 2}),
|
||
|
"z": Counter({"a": 4}),
|
||
|
}
|
||
|
|
||
|
a = {
|
||
|
"a": {"b": {"c": 5}},
|
||
|
"d": 1,
|
||
|
"e": {2: 3},
|
||
|
"f": Counter({"a": 1, "c": 1}),
|
||
|
"i": Counter({"f": 9}),
|
||
|
"j": Counter({"a": 1, "z": 4}),
|
||
|
}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {
|
||
|
"a": {"B": {"C": 10}},
|
||
|
"d": 2,
|
||
|
"e": 2,
|
||
|
"f": Counter({"a": 1, "b": 1}),
|
||
|
"j": [1, 2, 3],
|
||
|
"z": Counter({"a": 2}),
|
||
|
}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {
|
||
|
"a": {"b": {"_c": 15}},
|
||
|
"d": 3,
|
||
|
"e": {1: 2, "a": {"f": 2}},
|
||
|
"i": 2,
|
||
|
"j": Counter({"z": 2}),
|
||
|
"z": Counter({"a": 2}),
|
||
|
}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
def test_should_not_copy_references(self):
|
||
|
before = 1
|
||
|
after = 99
|
||
|
|
||
|
o1 = {"key1": before}
|
||
|
o2 = {"key2": before}
|
||
|
|
||
|
expected = {"list": deepcopy([o1, o2]), "tuple": deepcopy((o1, o2))}
|
||
|
|
||
|
a = {"list": [o1], "tuple": (o1,)}
|
||
|
b = {"list": [o2], "tuple": (o2,)}
|
||
|
|
||
|
actual = merge({}, a, b, strategy=Strategy.ADDITIVE)
|
||
|
|
||
|
o1["key1"] = after
|
||
|
o2["key2"] = after
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
|
||
|
# Copied dicts should `not` mutate
|
||
|
self.assertEqual(actual["list"][0]["key1"], before)
|
||
|
self.assertEqual(actual["list"][1]["key2"], before)
|
||
|
self.assertEqual(actual["tuple"][0]["key1"], before)
|
||
|
self.assertEqual(actual["tuple"][1]["key2"], before)
|
||
|
|
||
|
# Non-copied dicts should mutate
|
||
|
self.assertEqual(a["list"][0]["key1"], after)
|
||
|
self.assertEqual(b["list"][0]["key2"], after)
|
||
|
self.assertEqual(a["tuple"][0]["key1"], after)
|
||
|
self.assertEqual(b["tuple"][0]["key2"], after)
|
||
|
|
||
|
##############################################################################################################################
|
||
|
# TYPESAFE
|
||
|
# TYPESAFE_REPLACE
|
||
|
##############################################################################################################################
|
||
|
|
||
|
def test_should_raise_TypeError_using_typesafe_strategy_if_types_differ(self):
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
|
||
|
|
||
|
with self.assertRaises(TypeError):
|
||
|
merge({}, a, b, c, strategy=Strategy.TYPESAFE)
|
||
|
|
||
|
def test_should_raise_TypeError_using_typesafe_replace_strategy_if_types_differ(self,):
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
|
||
|
|
||
|
with self.assertRaises(TypeError):
|
||
|
merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_typesafe_strategy_and_only_mutate_target_if_types_are_compatible(
|
||
|
self,
|
||
|
):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"f": [4, 5, 6],
|
||
|
"g": {2, 3, 4},
|
||
|
"h": (1, 3),
|
||
|
"z": Counter({"a": 1, "b": 1, "c": 1}),
|
||
|
}
|
||
|
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_typesafe_replace_strategy_and_only_mutate_target_if_types_are_compatible(
|
||
|
self,
|
||
|
):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"f": [4, 5, 6],
|
||
|
"g": {2, 3, 4},
|
||
|
"h": (1, 3),
|
||
|
"z": Counter({"a": 1, "b": 1, "c": 1}),
|
||
|
}
|
||
|
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
##############################################################################################################################
|
||
|
# TYPESAFE_ADDITIVE
|
||
|
##############################################################################################################################
|
||
|
|
||
|
def test_should_raise_TypeError_using_typesafe_additive_strategy_if_types_differ(self,):
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
|
||
|
|
||
|
with self.assertRaises(TypeError):
|
||
|
merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)
|
||
|
|
||
|
def test_should_merge_3_dicts_into_new_dict_using_typesafe_additive_strategy_and_only_mutate_target_if_types_are_compatible(
|
||
|
self,
|
||
|
):
|
||
|
expected = {
|
||
|
"a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
|
||
|
"d": 3,
|
||
|
"f": [1, 2, 3, 4, 5, 6],
|
||
|
"g": {1, 2, 3, 4},
|
||
|
"h": (1, 1, 3),
|
||
|
"z": Counter({"a": 2, "b": 1, "c": 1}),
|
||
|
}
|
||
|
|
||
|
a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
|
||
|
a_copy = deepcopy(a)
|
||
|
|
||
|
b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
|
||
|
b_copy = deepcopy(b)
|
||
|
|
||
|
c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
|
||
|
c_copy = deepcopy(c)
|
||
|
|
||
|
actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)
|
||
|
|
||
|
self.assertEqual(actual, expected)
|
||
|
self.assertEqual(a, a_copy)
|
||
|
self.assertEqual(b, b_copy)
|
||
|
self.assertEqual(c, c_copy)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
unittest.main()
|