856 lines
36 KiB
Python
856 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
|