From cf9cfc31bdfd4e66c91a802821fae9035f8e8da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 28 Mar 2023 22:29:20 -0700 Subject: [PATCH] edtlib: implement copy.deepcopy() for EDT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just like we did for dtlib in 15e3e317f73403f57d40bb99e2b981ab692b9a7a ("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 --- .../src/devicetree/edtlib.py | 39 ++++++++-- .../python-devicetree/tests/test_edtlib.py | 75 +++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index a6de3afcb2d..02c9b331138 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -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"" + 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: diff --git a/scripts/dts/python-devicetree/tests/test_edtlib.py b/scripts/dts/python-devicetree/tests/test_edtlib.py index 0653eef78ba..b1defe0c327 100644 --- a/scripts/dts/python-devicetree/tests/test_edtlib.py +++ b/scripts/dts/python-devicetree/tests/test_edtlib.py @@ -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'.