zephyr/scripts/dts/python-devicetree/tests/test_edtlib.py
Fabio Baltieri 403640b75e edtlib: link child nodes to parent for nodes with child-bindings
The current EDT graph logic only use properties directly under a
specific node to add dependencies. For nodes properties in
child-bindings, this means that the child phandles are only linked by
the child node itself, which does have an ordinal but no corresponding
"sturct device" in the code, causing those dependencies to be silently
ignored by gen_handles.py.

Fix that by adding the recursive logic to visit child bindings when
present, which causes all child node property handles to be linked to
the parent node.

Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
2023-10-25 18:39:31 -07:00

855 lines
36 KiB
Python

# Copyright (c) 2019 Nordic Semiconductor ASA
# SPDX-License-Identifier: BSD-3-Clause
import contextlib
from copy import deepcopy
import io
from logging import WARNING
import os
from pathlib import Path
import pytest
from devicetree import edtlib
# Test suite for edtlib.py.
#
# Run it using pytest (https://docs.pytest.org/en/stable/usage.html):
#
# $ pytest testedtlib.py
#
# See the comment near the top of testdtlib.py for additional pytest advice.
#
# test.dts is the main test file. test-bindings/ and test-bindings-2/ has
# bindings. The tests mostly use string comparisons via the various __repr__()
# methods.
HERE = os.path.dirname(__file__)
@contextlib.contextmanager
def from_here():
# Convenience hack to minimize diff from zephyr.
cwd = os.getcwd()
try:
os.chdir(HERE)
yield
finally:
os.chdir(cwd)
def hpath(filename):
'''Convert 'filename' to the host path syntax.'''
return os.fspath(Path(filename))
def test_warnings(caplog):
'''Tests for situations that should cause warnings.'''
with from_here(): edtlib.EDT("test.dts", ["test-bindings"])
enums_hpath = hpath('test-bindings/enums.yaml')
expected_warnings = [
f"'oldprop' is marked as deprecated in 'properties:' in {hpath('test-bindings/deprecated.yaml')} for node /test-deprecated.",
"unit address and first address in 'reg' (0x1) don't match for /reg-zero-size-cells/node",
"unit address and first address in 'reg' (0x5) don't match for /reg-ranges/parent/node",
"unit address and first address in 'reg' (0x30000000200000001) don't match for /reg-nested-ranges/grandparent/parent/node",
f"compatible 'enums' in binding '{enums_hpath}' has non-tokenizable enum for property 'string-enum': 'foo bar', 'foo_bar'",
f"compatible 'enums' in binding '{enums_hpath}' has enum for property 'tokenizable-lower-enum' that is only tokenizable in lowercase: 'bar', 'BAR'",
]
assert caplog.record_tuples == [('devicetree.edtlib', WARNING, warning_message)
for warning_message in expected_warnings]
def test_interrupts():
'''Tests for the interrupts property.'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
node = edt.get_node("/interrupt-parent-test/node")
controller = edt.get_node('/interrupt-parent-test/controller')
assert node.interrupts == [
edtlib.ControllerAndData(node=node, controller=controller, data={'one': 1, 'two': 2, 'three': 3}, name='foo', basename=None),
edtlib.ControllerAndData(node=node, controller=controller, data={'one': 4, 'two': 5, 'three': 6}, name='bar', basename=None)
]
node = edt.get_node("/interrupts-extended-test/node")
controller_0 = edt.get_node('/interrupts-extended-test/controller-0')
controller_1 = edt.get_node('/interrupts-extended-test/controller-1')
controller_2 = edt.get_node('/interrupts-extended-test/controller-2')
assert node.interrupts == [
edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 1}, name=None, basename=None),
edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 2, 'two': 3}, name=None, basename=None),
edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 4, 'two': 5, 'three': 6}, name=None, basename=None)
]
node = edt.get_node("/interrupt-map-test/node@0")
controller_0 = edt.get_node('/interrupt-map-test/controller-0')
controller_1 = edt.get_node('/interrupt-map-test/controller-1')
controller_2 = edt.get_node('/interrupt-map-test/controller-2')
assert node.interrupts == [
edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 0}, name=None, basename=None),
edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 1}, name=None, basename=None),
edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 2}, name=None, basename=None)
]
node = edt.get_node("/interrupt-map-test/node@1")
assert node.interrupts == [
edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 3}, name=None, basename=None),
edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 4}, name=None, basename=None),
edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 5}, name=None, basename=None)
]
node = edt.get_node("/interrupt-map-bitops-test/node@70000000E")
assert node.interrupts == [
edtlib.ControllerAndData(node=node, controller=edt.get_node('/interrupt-map-bitops-test/controller'), data={'one': 3, 'two': 2}, name=None, basename=None)
]
def test_ranges():
'''Tests for the ranges property'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
node = edt.get_node("/reg-ranges/parent")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1, parent_bus_cells=0x2, parent_bus_addr=0xa0000000b, length_cells=0x1, length=0x1),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2, parent_bus_cells=0x2, parent_bus_addr=0xc0000000d, length_cells=0x1, length=0x2),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x4, parent_bus_cells=0x2, parent_bus_addr=0xe0000000f, length_cells=0x1, length=0x1)
]
node = edt.get_node("/reg-nested-ranges/grandparent")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x0, parent_bus_cells=0x3, parent_bus_addr=0x30000000000000000, length_cells=0x2, length=0x200000002)
]
node = edt.get_node("/reg-nested-ranges/grandparent/parent")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x0, parent_bus_cells=0x2, parent_bus_addr=0x200000000, length_cells=0x1, length=0x2)
]
assert edt.get_node("/ranges-zero-cells/node").ranges == []
node = edt.get_node("/ranges-zero-parent-cells/node")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x0, length=None),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x0, length=None),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x0, length=None)
]
node = edt.get_node("/ranges-one-address-cells/node")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0xb),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0x1b),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0x2b)
]
node = edt.get_node("/ranges-one-address-two-size-cells/node")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0xb0000000c),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0x1b0000001c),
edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0x2b0000002c)
]
node = edt.get_node("/ranges-two-address-cells/node@1")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0xa0000000b, parent_bus_cells=0x1, parent_bus_addr=0xc, length_cells=0x1, length=0xd),
edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x1a0000001b, parent_bus_cells=0x1, parent_bus_addr=0x1c, length_cells=0x1, length=0x1d),
edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x2a0000002b, parent_bus_cells=0x1, parent_bus_addr=0x2c, length_cells=0x1, length=0x2d)
]
node = edt.get_node("/ranges-two-address-two-size-cells/node@1")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0xa0000000b, parent_bus_cells=0x1, parent_bus_addr=0xc, length_cells=0x2, length=0xd0000000e),
edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x1a0000001b, parent_bus_cells=0x1, parent_bus_addr=0x1c, length_cells=0x2, length=0x1d0000001e),
edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x2a0000002b, parent_bus_cells=0x1, parent_bus_addr=0x2c, length_cells=0x2, length=0x2d0000001d)
]
node = edt.get_node("/ranges-three-address-cells/node@1")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0xa0000000b0000000c, parent_bus_cells=0x2, parent_bus_addr=0xd0000000e, length_cells=0x1, length=0xf),
edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x1a0000001b0000001c, parent_bus_cells=0x2, parent_bus_addr=0x1d0000001e, length_cells=0x1, length=0x1f),
edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x2a0000002b0000002c, parent_bus_cells=0x2, parent_bus_addr=0x2d0000002e, length_cells=0x1, length=0x2f)
]
node = edt.get_node("/ranges-three-address-two-size-cells/node@1")
assert node.ranges == [
edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0xa0000000b0000000c, parent_bus_cells=0x2, parent_bus_addr=0xd0000000e, length_cells=0x2, length=0xf00000010),
edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x1a0000001b0000001c, parent_bus_cells=0x2, parent_bus_addr=0x1d0000001e, length_cells=0x2, length=0x1f00000110),
edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x2a0000002b0000002c, parent_bus_cells=0x2, parent_bus_addr=0x2d0000002e, length_cells=0x2, length=0x2f00000210)
]
def test_reg():
'''Tests for the regs property'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
def verify_regs(node, expected_tuples):
regs = node.regs
assert len(regs) == len(expected_tuples)
for reg, expected_tuple in zip(regs, expected_tuples):
name, addr, size = expected_tuple
assert reg.node is node
assert reg.name == name
assert reg.addr == addr
assert reg.size == size
verify_regs(edt.get_node("/reg-zero-address-cells/node"),
[('foo', None, 0x1),
('bar', None, 0x2)])
verify_regs(edt.get_node("/reg-zero-size-cells/node"),
[(None, 0x1, None),
(None, 0x2, None)])
verify_regs(edt.get_node("/reg-ranges/parent/node"),
[(None, 0x5, 0x1),
(None, 0xe0000000f, 0x1),
(None, 0xc0000000e, 0x1),
(None, 0xc0000000d, 0x1),
(None, 0xa0000000b, 0x1),
(None, 0x0, 0x1)])
verify_regs(edt.get_node("/reg-nested-ranges/grandparent/parent/node"),
[(None, 0x30000000200000001, 0x1)])
def test_pinctrl():
'''Test 'pinctrl-<index>'.'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
node = edt.get_node("/pinctrl/dev")
state_1 = edt.get_node('/pinctrl/pincontroller/state-1')
state_2 = edt.get_node('/pinctrl/pincontroller/state-2')
assert node.pinctrls == [
edtlib.PinCtrl(node=node, name='zero', conf_nodes=[]),
edtlib.PinCtrl(node=node, name='one', conf_nodes=[state_1]),
edtlib.PinCtrl(node=node, name='two', conf_nodes=[state_1, state_2])
]
def test_hierarchy():
'''Test Node.parent and Node.children'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
assert edt.get_node("/").parent is None
assert str(edt.get_node("/parent/child-1").parent) == \
"<Node /parent in 'test.dts', no binding>"
assert str(edt.get_node("/parent/child-2/grandchild").parent) == \
"<Node /parent/child-2 in 'test.dts', no binding>"
assert str(edt.get_node("/parent").children) == \
"{'child-1': <Node /parent/child-1 in 'test.dts', no binding>, 'child-2': <Node /parent/child-2 in 'test.dts', no binding>}"
assert edt.get_node("/parent/child-1").children == {}
def test_child_index():
'''Test Node.child_index.'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
parent, child_1, child_2 = [edt.get_node(path) for path in
("/parent",
"/parent/child-1",
"/parent/child-2")]
assert parent.child_index(child_1) == 0
assert parent.child_index(child_2) == 1
with pytest.raises(KeyError):
parent.child_index(parent)
def test_include():
'''Test 'include:' and the legacy 'inherits: !include ...' in bindings'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
binding_include = edt.get_node("/binding-include")
assert binding_include.description == "Parent binding"
verify_props(binding_include,
['foo', 'bar', 'baz', 'qaz'],
['int', 'int', 'int', 'int'],
[0, 1, 2, 3])
verify_props(edt.get_node("/binding-include/child"),
['foo', 'bar', 'baz', 'qaz'],
['int', 'int', 'int', 'int'],
[0, 1, 2, 3])
def test_include_filters():
'''Test property-allowlist and property-blocklist in an include.'''
fname2path = {'include.yaml': 'test-bindings-include/include.yaml',
'include-2.yaml': 'test-bindings-include/include-2.yaml'}
with pytest.raises(edtlib.EDTError) as e:
with from_here():
edtlib.Binding("test-bindings-include/allow-and-blocklist.yaml", fname2path)
assert ("should not specify both 'property-allowlist:' and 'property-blocklist:'"
in str(e.value))
with pytest.raises(edtlib.EDTError) as e:
with from_here():
edtlib.Binding("test-bindings-include/allow-and-blocklist-child.yaml", fname2path)
assert ("should not specify both 'property-allowlist:' and 'property-blocklist:'"
in str(e.value))
with pytest.raises(edtlib.EDTError) as e:
with from_here():
edtlib.Binding("test-bindings-include/allow-not-list.yaml", fname2path)
value_str = str(e.value)
assert value_str.startswith("'property-allowlist' value")
assert value_str.endswith("should be a list")
with pytest.raises(edtlib.EDTError) as e:
with from_here():
edtlib.Binding("test-bindings-include/block-not-list.yaml", fname2path)
value_str = str(e.value)
assert value_str.startswith("'property-blocklist' value")
assert value_str.endswith("should be a list")
with pytest.raises(edtlib.EDTError) as e:
with from_here():
binding = edtlib.Binding("test-bindings-include/include-invalid-keys.yaml", fname2path)
value_str = str(e.value)
assert value_str.startswith(
"'include:' in test-bindings-include/include-invalid-keys.yaml should not have these "
"unexpected contents: ")
assert 'bad-key-1' in value_str
assert 'bad-key-2' in value_str
with pytest.raises(edtlib.EDTError) as e:
with from_here():
binding = edtlib.Binding("test-bindings-include/include-invalid-type.yaml", fname2path)
value_str = str(e.value)
assert value_str.startswith(
"'include:' in test-bindings-include/include-invalid-type.yaml "
"should be a string or list, but has type ")
with pytest.raises(edtlib.EDTError) as e:
with from_here():
binding = edtlib.Binding("test-bindings-include/include-no-name.yaml", fname2path)
value_str = str(e.value)
assert value_str.startswith("'include:' element")
assert value_str.endswith(
"in test-bindings-include/include-no-name.yaml should have a 'name' key")
with from_here():
binding = edtlib.Binding("test-bindings-include/allowlist.yaml", fname2path)
assert set(binding.prop2specs.keys()) == {'x'} # 'x' is allowed
binding = edtlib.Binding("test-bindings-include/empty-allowlist.yaml", fname2path)
assert set(binding.prop2specs.keys()) == set() # nothing is allowed
binding = edtlib.Binding("test-bindings-include/blocklist.yaml", fname2path)
assert set(binding.prop2specs.keys()) == {'y', 'z'} # 'x' is blocked
binding = edtlib.Binding("test-bindings-include/empty-blocklist.yaml", fname2path)
assert set(binding.prop2specs.keys()) == {'x', 'y', 'z'} # nothing is blocked
binding = edtlib.Binding("test-bindings-include/intermixed.yaml", fname2path)
assert set(binding.prop2specs.keys()) == {'x', 'a'}
binding = edtlib.Binding("test-bindings-include/include-no-list.yaml", fname2path)
assert set(binding.prop2specs.keys()) == {'x', 'y', 'z'}
binding = edtlib.Binding("test-bindings-include/filter-child-bindings.yaml", fname2path)
child = binding.child_binding
grandchild = child.child_binding
assert set(binding.prop2specs.keys()) == {'x'}
assert set(child.prop2specs.keys()) == {'child-prop-2'}
assert set(grandchild.prop2specs.keys()) == {'grandchild-prop-1'}
binding = edtlib.Binding("test-bindings-include/allow-and-blocklist-multilevel.yaml",
fname2path)
assert set(binding.prop2specs.keys()) == {'x'} # 'x' is allowed
child = binding.child_binding
assert set(child.prop2specs.keys()) == {'child-prop-1', 'child-prop-2',
'x', 'z'} # root level 'y' is blocked
def test_bus():
'''Test 'bus:' and 'on-bus:' in bindings'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
assert isinstance(edt.get_node("/buses/foo-bus").buses, list)
assert "foo" in edt.get_node("/buses/foo-bus").buses
# foo-bus does not itself appear on a bus
assert isinstance(edt.get_node("/buses/foo-bus").on_buses, list)
assert not edt.get_node("/buses/foo-bus").on_buses
assert edt.get_node("/buses/foo-bus").bus_node is None
# foo-bus/node1 is not a bus node...
assert isinstance(edt.get_node("/buses/foo-bus/node1").buses, list)
assert not edt.get_node("/buses/foo-bus/node1").buses
# ...but is on a bus
assert isinstance(edt.get_node("/buses/foo-bus/node1").on_buses, list)
assert "foo" in edt.get_node("/buses/foo-bus/node1").on_buses
assert edt.get_node("/buses/foo-bus/node1").bus_node.path == \
"/buses/foo-bus"
# foo-bus/node2 is not a bus node...
assert isinstance(edt.get_node("/buses/foo-bus/node2").buses, list)
assert not edt.get_node("/buses/foo-bus/node2").buses
# ...but is on a bus
assert isinstance(edt.get_node("/buses/foo-bus/node2").on_buses, list)
assert "foo" in edt.get_node("/buses/foo-bus/node2").on_buses
# no-bus-node is not a bus node...
assert isinstance(edt.get_node("/buses/no-bus-node").buses, list)
assert not edt.get_node("/buses/no-bus-node").buses
# ... and is not on a bus
assert isinstance(edt.get_node("/buses/no-bus-node").on_buses, list)
assert not edt.get_node("/buses/no-bus-node").on_buses
# Same compatible string, but different bindings from being on different
# buses
assert str(edt.get_node("/buses/foo-bus/node1").binding_path) == \
hpath("test-bindings/device-on-foo-bus.yaml")
assert str(edt.get_node("/buses/foo-bus/node2").binding_path) == \
hpath("test-bindings/device-on-any-bus.yaml")
assert str(edt.get_node("/buses/bar-bus/node").binding_path) == \
hpath("test-bindings/device-on-bar-bus.yaml")
assert str(edt.get_node("/buses/no-bus-node").binding_path) == \
hpath("test-bindings/device-on-any-bus.yaml")
# foo-bus/node/nested also appears on the foo-bus bus
assert isinstance(edt.get_node("/buses/foo-bus/node1/nested").on_buses, list)
assert "foo" in edt.get_node("/buses/foo-bus/node1/nested").on_buses
assert str(edt.get_node("/buses/foo-bus/node1/nested").binding_path) == \
hpath("test-bindings/device-on-foo-bus.yaml")
def test_child_binding():
'''Test 'child-binding:' in bindings'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
child1 = edt.get_node("/child-binding/child-1")
child2 = edt.get_node("/child-binding/child-2")
grandchild = edt.get_node("/child-binding/child-1/grandchild")
assert str(child1.binding_path) == hpath("test-bindings/child-binding.yaml")
assert str(child1.description) == "child node"
verify_props(child1, ['child-prop'], ['int'], [1])
assert str(child2.binding_path) == hpath("test-bindings/child-binding.yaml")
assert str(child2.description) == "child node"
verify_props(child2, ['child-prop'], ['int'], [3])
assert str(grandchild.binding_path) == hpath("test-bindings/child-binding.yaml")
assert str(grandchild.description) == "grandchild node"
verify_props(grandchild, ['grandchild-prop'], ['int'], [2])
with from_here():
binding_file = Path("test-bindings/child-binding.yaml").resolve()
top = edtlib.Binding(binding_file, {})
child = top.child_binding
assert Path(top.path) == binding_file
assert Path(child.path) == binding_file
assert top.compatible == 'top-binding'
assert child.compatible is None
with from_here():
binding_file = Path("test-bindings/child-binding-with-compat.yaml").resolve()
top = edtlib.Binding(binding_file, {})
child = top.child_binding
assert Path(top.path) == binding_file
assert Path(child.path) == binding_file
assert top.compatible == 'top-binding-with-compat'
assert child.compatible == 'child-compat'
def test_props():
'''Test Node.props (derived from DT and 'properties:' in the binding)'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
props_node = edt.get_node('/props')
ctrl_1, ctrl_2 = [edt.get_node(path) for path in ['/ctrl-1', '/ctrl-2']]
verify_props(props_node,
['int',
'existent-boolean', 'nonexistent-boolean',
'array', 'uint8-array',
'string', 'string-array',
'phandle-ref', 'phandle-refs',
'path'],
['int',
'boolean', 'boolean',
'array', 'uint8-array',
'string', 'string-array',
'phandle', 'phandles',
'path'],
[1,
True, False,
[1,2,3], b'\x124',
'foo', ['foo','bar','baz'],
ctrl_1, [ctrl_1,ctrl_2],
ctrl_1])
verify_phandle_array_prop(props_node,
'phandle-array-foos',
[(ctrl_1, {'one': 1}),
(ctrl_2, {'one': 2, 'two': 3})])
verify_phandle_array_prop(edt.get_node("/props-2"),
"phandle-array-foos",
[(edt.get_node('/ctrl-0-1'), {}),
None,
(edt.get_node('/ctrl-0-2'), {})])
verify_phandle_array_prop(props_node,
'foo-gpios',
[(ctrl_1, {'gpio-one': 1})])
def test_nexus():
'''Test <prefix>-map via gpio-map (the most common case).'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
source = edt.get_node("/gpio-map/source")
destination = edt.get_node('/gpio-map/destination')
verify_phandle_array_prop(source,
'foo-gpios',
[(destination, {'val': 6}),
(destination, {'val': 5})])
assert source.props["foo-gpios"].val[0].basename == f"gpio"
def test_prop_defaults():
'''Test property default values given in bindings'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_props(edt.get_node("/defaults"),
['int',
'array', 'uint8-array',
'string', 'string-array',
'default-not-used'],
['int',
'array', 'uint8-array',
'string', 'string-array',
'int'],
[123,
[1,2,3], b'\x89\xab\xcd',
'hello', ['hello','there'],
234])
def test_prop_enums():
'''test properties with enum: in the binding'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
props = edt.get_node('/enums').props
int_enum = props['int-enum']
string_enum = props['string-enum']
tokenizable_enum = props['tokenizable-enum']
tokenizable_lower_enum = props['tokenizable-lower-enum']
no_enum = props['no-enum']
assert int_enum.val == 1
assert int_enum.enum_index == 0
assert not int_enum.spec.enum_tokenizable
assert not int_enum.spec.enum_upper_tokenizable
assert string_enum.val == 'foo_bar'
assert string_enum.enum_index == 1
assert not string_enum.spec.enum_tokenizable
assert not string_enum.spec.enum_upper_tokenizable
assert tokenizable_enum.val == '123 is ok'
assert tokenizable_enum.val_as_token == '123_is_ok'
assert tokenizable_enum.enum_index == 2
assert tokenizable_enum.spec.enum_tokenizable
assert tokenizable_enum.spec.enum_upper_tokenizable
assert tokenizable_lower_enum.val == 'bar'
assert tokenizable_lower_enum.val_as_token == 'bar'
assert tokenizable_lower_enum.enum_index == 0
assert tokenizable_lower_enum.spec.enum_tokenizable
assert not tokenizable_lower_enum.spec.enum_upper_tokenizable
assert no_enum.enum_index is None
assert not no_enum.spec.enum_tokenizable
assert not no_enum.spec.enum_upper_tokenizable
def test_binding_inference():
'''Test inferred bindings for special zephyr-specific nodes.'''
warnings = io.StringIO()
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"], warnings)
assert str(edt.get_node("/zephyr,user").props) == '{}'
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"], warnings,
infer_binding_for_paths=["/zephyr,user"])
ctrl_1 = edt.get_node('/ctrl-1')
ctrl_2 = edt.get_node('/ctrl-2')
zephyr_user = edt.get_node("/zephyr,user")
verify_props(zephyr_user,
['boolean', 'bytes', 'number',
'numbers', 'string', 'strings'],
['boolean', 'uint8-array', 'int',
'array', 'string', 'string-array'],
[True, b'\x81\x82\x83', 23,
[1,2,3], 'text', ['a','b','c']])
assert zephyr_user.props['handle'].val is ctrl_1
phandles = zephyr_user.props['phandles']
val = phandles.val
assert len(val) == 2
assert val[0] is ctrl_1
assert val[1] is ctrl_2
verify_phandle_array_prop(zephyr_user,
'phandle-array-foos',
[(edt.get_node('/ctrl-2'), {'one': 1, 'two': 2})])
def test_multi_bindings():
'''Test having multiple directories with bindings'''
with from_here():
edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"])
assert str(edt.get_node("/in-dir-1").binding_path) == \
hpath("test-bindings/multidir.yaml")
assert str(edt.get_node("/in-dir-2").binding_path) == \
hpath("test-bindings-2/multidir.yaml")
def test_dependencies():
''''Test dependency relations'''
with from_here():
edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"])
assert edt.get_node("/").dep_ordinal == 0
assert edt.get_node("/in-dir-1").dep_ordinal == 1
assert edt.get_node("/") in edt.get_node("/in-dir-1").depends_on
assert edt.get_node("/in-dir-1") in edt.get_node("/").required_by
def test_child_dependencies():
'''Test dependencies relashionship with child nodes propagated to parent'''
with from_here():
edt = edtlib.EDT("test.dts", ["test-bindings"])
dep_node = edt.get_node("/child-binding-dep")
assert dep_node in edt.get_node("/child-binding").depends_on
assert dep_node in edt.get_node("/child-binding/child-1/grandchild").depends_on
assert dep_node in edt.get_node("/child-binding/child-2").depends_on
assert edt.get_node("/child-binding") in dep_node.required_by
assert edt.get_node("/child-binding/child-1/grandchild") in dep_node.required_by
assert edt.get_node("/child-binding/child-2") in dep_node.required_by
def test_slice_errs(tmp_path):
'''Test error messages from the internal _slice() helper'''
dts_file = tmp_path / "error.dts"
verify_error("""
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <2>;
sub {
reg = <3>;
};
};
""",
dts_file,
f"'reg' property in <Node /sub in '{dts_file}'> has length 4, which is not evenly divisible by 12 (= 4*(<#address-cells> (= 1) + <#size-cells> (= 2))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
verify_error("""
/dts-v1/;
/ {
sub {
interrupts = <1>;
interrupt-parent = < &{/controller} >;
};
controller {
interrupt-controller;
#interrupt-cells = <2>;
};
};
""",
dts_file,
f"'interrupts' property in <Node /sub in '{dts_file}'> has length 4, which is not evenly divisible by 8 (= 4*<#interrupt-cells>). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
verify_error("""
/dts-v1/;
/ {
#address-cells = <1>;
sub-1 {
#address-cells = <2>;
#size-cells = <3>;
ranges = <4 5>;
sub-2 {
reg = <1 2 3 4 5>;
};
};
};
""",
dts_file,
f"'ranges' property in <Node /sub-1 in '{dts_file}'> has length 8, which is not evenly divisible by 24 (= 4*(<#address-cells> (= 2) + <#address-cells for parent> (= 1) + <#size-cells> (= 3))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
def test_bad_compatible(tmp_path):
# An invalid compatible should cause an error, even on a node with
# no binding.
dts_file = tmp_path / "error.dts"
verify_error("""
/dts-v1/;
/ {
foo {
compatible = "no, whitespace";
};
};
""",
dts_file,
r"node '/foo' compatible 'no, whitespace' must match this regular expression: '^[a-zA-Z][a-zA-Z0-9,+\-._]+$'")
def test_wrong_props():
'''Test Node.wrong_props (derived from DT and 'properties:' in the binding)'''
with from_here():
with pytest.raises(edtlib.EDTError) as e:
edtlib.Binding("test-wrong-bindings/wrong-specifier-space-type.yaml", None)
assert ("'specifier-space' in 'properties: wrong-type-for-specifier-space' has type 'phandle', expected 'phandle-array'"
in str(e.value))
with pytest.raises(edtlib.EDTError) as e:
edtlib.Binding("test-wrong-bindings/wrong-phandle-array-name.yaml", None)
value_str = str(e.value)
assert value_str.startswith("'wrong-phandle-array-name' in 'properties:'")
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'.
#
# The path 'dts_file' is written with the string 'dts' before the
# test is run.
with open(dts_file, "w", encoding="utf-8") as f:
f.write(dts)
f.flush() # Can't have unbuffered text IO, so flush() instead
with pytest.raises(edtlib.EDTError) as e:
edtlib.EDT(dts_file, [])
assert str(e.value) == expected_err
def verify_props(node, names, types, values):
# Verifies that each property in 'names' has the expected
# value in 'values'. Property lookup is done in Node 'node'.
for name, type, value in zip(names, types, values):
prop = node.props[name]
assert prop.name == name
assert prop.type == type
assert prop.val == value
assert prop.node is node
def verify_phandle_array_prop(node, name, values):
# Verifies 'node.props[name]' is a phandle-array, and has the
# expected controller/data values in 'values'. Elements
# of 'values' may be None.
prop = node.props[name]
assert prop.type == 'phandle-array'
assert prop.name == name
val = prop.val
assert isinstance(val, list)
assert len(val) == len(values)
for actual, expected in zip(val, values):
if expected is not None:
controller, data = expected
assert isinstance(actual, edtlib.ControllerAndData)
assert actual.controller is controller
assert actual.data == data
else:
assert actual is None