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
|
from pytest import raises
from invoke.config import merge_dicts, copy_dict, AmbiguousMergeError
class merge_dicts_:
# NOTE: don't usually like doing true unit tests of low level plumbing -
# prefer to infer it's all working by examining higher level behavior - but
# sometimes it's necessary to more easily stamp out certain bugs.
def merging_data_onto_empty_dict(self):
d1 = {}
d2 = {"foo": "bar"}
merge_dicts(d1, d2)
assert d1 == d2
def updating_with_None_acts_like_merging_empty_dict(self):
# When bug present, AttributeError is raised on a None.items()
d1 = {"my": "data"}
d2 = None
merge_dicts(d1, d2)
assert d1 == {"my": "data"}
def orthogonal_data_merges(self):
d1 = {"foo": "bar"}
d2 = {"biz": "baz"}
merge_dicts(d1, d2)
assert d1 == {"foo": "bar", "biz": "baz"}
def updates_arg_values_win(self):
d1 = {"foo": "bar"}
d2 = {"foo": "notbar"}
merge_dicts(d1, d2)
assert d1 == {"foo": "notbar"}
def non_dict_type_mismatch_overwrites_ok(self):
d1 = {"foo": "bar"}
d2 = {"foo": [1, 2, 3]}
merge_dicts(d1, d2)
assert d1 == {"foo": [1, 2, 3]}
def merging_dict_into_nondict_raises_error(self):
# TODO: or...should it?! If a user really wants to take a pre-existing
# config path and make it 'deeper' by overwriting e.g. a string with a
# dict of strings (or whatever)...should they be allowed to?
d1 = {"foo": "bar"}
d2 = {"foo": {"uh": "oh"}}
with raises(AmbiguousMergeError):
merge_dicts(d1, d2)
def merging_nondict_into_dict_raises_error(self):
d1 = {"foo": {"uh": "oh"}}
d2 = {"foo": "bar"}
with raises(AmbiguousMergeError):
merge_dicts(d1, d2)
def nested_leaf_values_merge_ok(self):
d1 = {"foo": {"bar": {"biz": "baz"}}}
d2 = {"foo": {"bar": {"biz": "notbaz"}}}
merge_dicts(d1, d2)
assert d1 == {"foo": {"bar": {"biz": "notbaz"}}}
def mixed_branch_levels_merges_ok(self):
d1 = {"foo": {"bar": {"biz": "baz"}}, "meh": 17, "myown": "ok"}
d2 = {"foo": {"bar": {"biz": "notbaz"}}, "meh": 25}
merge_dicts(d1, d2)
expected = {
"foo": {"bar": {"biz": "notbaz"}},
"meh": 25,
"myown": "ok",
}
assert d1 == expected
def dict_value_merges_are_not_references(self):
core = {}
coll = {"foo": {"bar": {"biz": "coll value"}}}
proj = {"foo": {"bar": {"biz": "proj value"}}}
# Initial merge - when bug present, this sets core['foo'] to the entire
# 'foo' dict in 'proj' as a reference - meaning it 'links' back to the
# 'proj' dict whenever other things are merged into it
merge_dicts(core, proj)
assert core == {"foo": {"bar": {"biz": "proj value"}}}
assert proj["foo"]["bar"]["biz"] == "proj value"
# Identity tests can also prove the bug early
assert (
core["foo"] is not proj["foo"]
), "Core foo is literally proj foo!" # noqa
# Subsequent merge - just overwrites leaf values this time (thus no
# real change, but this is what real config merge code does, so why
# not)
merge_dicts(core, proj)
assert core == {"foo": {"bar": {"biz": "proj value"}}}
assert proj["foo"]["bar"]["biz"] == "proj value"
# The problem merge - when bug present, core['foo'] references 'foo'
# inside 'proj', so this ends up tweaking "core" but it actually
# affects "proj" as well!
merge_dicts(core, coll)
# Expect that the core dict got the update from 'coll'...
assert core == {"foo": {"bar": {"biz": "coll value"}}}
# BUT that 'proj' remains UNTOUCHED
assert proj["foo"]["bar"]["biz"] == "proj value"
def merge_file_types_by_reference(self):
with open(__file__) as fd:
d1 = {}
d2 = {"foo": fd}
merge_dicts(d1, d2)
assert d1["foo"].closed is False
class copy_dict_:
def returns_deep_copy_of_given_dict(self):
# NOTE: not actual deepcopy...
source = {"foo": {"bar": {"biz": "baz"}}}
copy = copy_dict(source)
assert copy["foo"]["bar"] == source["foo"]["bar"]
assert copy["foo"]["bar"] is not source["foo"]["bar"]
copy["foo"]["bar"]["biz"] = "notbaz"
assert source["foo"]["bar"]["biz"] == "baz"
|