edtlib: implement copy.deepcopy() for EDT

Just like we did for dtlib in 15e3e317f7
("dtlib: implement copy.deepcopy() for DT"), except this time it's for
EDT. This also can do no harm and will be useful for implementing
system devicetree support.

No functional changes expected under the assumption that no users are
relying on us having stashed the exact bindings_dirs list passed to
the constructor. This patch switches to making a defensive copy, which
is safer and makes implementing this a little cleaner.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Martí Bolívar 2023-03-28 22:29:20 -07:00 committed by Marti Bolivar
commit cf9cfc31bd
2 changed files with 109 additions and 5 deletions

View file

@ -199,6 +199,8 @@ class EDT:
# All instance attributes should be initialized here.
# This makes it easy to keep track of them, which makes
# implementing __deepcopy__() easier.
# If you change this, make sure to update __deepcopy__() too,
# and update the tests for that method.
# Public attributes (the rest are properties)
self.nodes = []
@ -209,7 +211,7 @@ class EDT:
self.label2node = {}
self.dep_ord2node = {}
self.dts_path = dts
self.bindings_dirs = bindings_dirs
self.bindings_dirs = list(bindings_dirs)
# Saved kwarg values for internal use
self._warn_reg_unit_address_mismatch = warn_reg_unit_address_mismatch
@ -227,10 +229,16 @@ class EDT:
for path in self._binding_paths}
self._node2enode = {} # Maps dtlib.Node to edtlib.Node
try:
self._dt = DT(dts)
except DTError as e:
raise EDTError(e) from e
if dts is not None:
try:
self._dt = DT(dts)
except DTError as e:
raise EDTError(e) from e
self._finish_init()
def _finish_init(self):
# This helper exists to make the __deepcopy__() implementation
# easier to keep in sync with __init__().
_check_dt(self._dt)
self._init_compat2binding()
@ -285,6 +293,27 @@ class EDT:
return f"<EDT for '{self.dts_path}', binding directories " \
f"'{self.bindings_dirs}'>"
def __deepcopy__(self, memo):
"""
Implements support for the standard library copy.deepcopy()
function on EDT instances.
"""
ret = EDT(
None,
self.bindings_dirs,
warn_reg_unit_address_mismatch=self._warn_reg_unit_address_mismatch,
default_prop_types=self._default_prop_types,
support_fixed_partitions_on_any_bus=self._fixed_partitions_no_bus,
infer_binding_for_paths=set(self._infer_binding_for_paths),
vendor_prefixes=dict(self._vendor_prefixes),
werror=self._werror
)
ret.dts_path = self.dts_path
ret._dt = deepcopy(self._dt, memo)
ret._finish_init()
return ret
@property
def scc_order(self):
try:

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: BSD-3-Clause
import contextlib
from copy import deepcopy
import io
from logging import WARNING
import os
@ -627,6 +628,80 @@ def test_wrong_props():
assert value_str.endswith("but no 'specifier-space' was provided.")
def test_deepcopy():
with from_here():
# We intentionally use different kwarg values than the
# defaults to make sure they're getting copied. This implies
# we have to set werror=True, so we can't use test.dts, since
# that generates warnings on purpose.
edt = edtlib.EDT("test-multidir.dts",
["test-bindings", "test-bindings-2"],
warn_reg_unit_address_mismatch=False,
default_prop_types=False,
support_fixed_partitions_on_any_bus=False,
infer_binding_for_paths=['/test-node'],
vendor_prefixes={'test-vnd': 'A test vendor'},
werror=True)
edt_copy = deepcopy(edt)
def equal_paths(list1, list2):
assert len(list1) == len(list2)
return all(elt1.path == elt2.path for elt1, elt2 in zip(list1, list2))
def equal_key2path(key2node1, key2node2):
assert len(key2node1) == len(key2node2)
return (all(key1 == key2 for (key1, key2) in
zip(key2node1, key2node2)) and
all(node1.path == node2.path for (node1, node2) in
zip(key2node1.values(), key2node2.values())))
def equal_key2paths(key2nodes1, key2nodes2):
assert len(key2nodes1) == len(key2nodes2)
return (all(key1 == key2 for (key1, key2) in
zip(key2nodes1, key2nodes2)) and
all(equal_paths(nodes1, nodes2) for (nodes1, nodes2) in
zip(key2nodes1.values(), key2nodes2.values())))
def test_equal_but_not_same(attribute, equal=None):
if equal is None:
equal = lambda a, b: a == b
copy = getattr(edt_copy, attribute)
original = getattr(edt, attribute)
assert equal(copy, original)
assert copy is not original
test_equal_but_not_same("nodes", equal_paths)
test_equal_but_not_same("compat2nodes", equal_key2paths)
test_equal_but_not_same("compat2okay", equal_key2paths)
test_equal_but_not_same("compat2vendor")
test_equal_but_not_same("compat2model")
test_equal_but_not_same("label2node", equal_key2path)
test_equal_but_not_same("dep_ord2node", equal_key2path)
assert edt_copy.dts_path == "test-multidir.dts"
assert edt_copy.bindings_dirs == ["test-bindings", "test-bindings-2"]
assert edt_copy.bindings_dirs is not edt.bindings_dirs
assert not edt_copy._warn_reg_unit_address_mismatch
assert not edt_copy._default_prop_types
assert not edt_copy._fixed_partitions_no_bus
assert edt_copy._infer_binding_for_paths == set(["/test-node"])
assert edt_copy._infer_binding_for_paths is not edt._infer_binding_for_paths
assert edt_copy._vendor_prefixes == {"test-vnd": "A test vendor"}
assert edt_copy._vendor_prefixes is not edt._vendor_prefixes
assert edt_copy._werror
test_equal_but_not_same("_compat2binding", equal_key2path)
test_equal_but_not_same("_binding_paths")
test_equal_but_not_same("_binding_fname2path")
assert len(edt_copy._node2enode) == len(edt._node2enode)
for node1, node2 in zip(edt_copy._node2enode, edt._node2enode):
enode1 = edt_copy._node2enode[node1]
enode2 = edt._node2enode[node2]
assert node1.path == node2.path
assert enode1.path == enode2.path
assert node1 is not node2
assert enode1 is not enode2
assert edt_copy._dt is not edt._dt
def verify_error(dts, dts_file, expected_err):
# Verifies that parsing a file 'dts_file' with the contents 'dts'
# (a string) raises an EDTError with the message 'expected_err'.