1# Copyright (c) 2019 Nordic Semiconductor ASA
2# SPDX-License-Identifier: BSD-3-Clause
3
4import contextlib
5from copy import deepcopy
6import io
7from logging import WARNING
8import os
9from pathlib import Path
10
11import pytest
12
13from devicetree import edtlib
14
15# Test suite for edtlib.py.
16#
17# Run it using pytest (https://docs.pytest.org/en/stable/usage.html):
18#
19#   $ pytest testedtlib.py
20#
21# See the comment near the top of testdtlib.py for additional pytest advice.
22#
23# test.dts is the main test file. test-bindings/ and test-bindings-2/ has
24# bindings. The tests mostly use string comparisons via the various __repr__()
25# methods.
26
27HERE = os.path.dirname(__file__)
28
29@contextlib.contextmanager
30def from_here():
31    # Convenience hack to minimize diff from zephyr.
32    cwd = os.getcwd()
33    try:
34        os.chdir(HERE)
35        yield
36    finally:
37        os.chdir(cwd)
38
39def hpath(filename):
40    '''Convert 'filename' to the host path syntax.'''
41    return os.fspath(Path(filename))
42
43def test_warnings(caplog):
44    '''Tests for situations that should cause warnings.'''
45
46    with from_here(): edtlib.EDT("test.dts", ["test-bindings"])
47
48    enums_hpath = hpath('test-bindings/enums.yaml')
49    expected_warnings = [
50        f"'oldprop' is marked as deprecated in 'properties:' in {hpath('test-bindings/deprecated.yaml')} for node /test-deprecated.",
51        "unit address and first address in 'reg' (0x1) don't match for /reg-zero-size-cells/node",
52        "unit address and first address in 'reg' (0x5) don't match for /reg-ranges/parent/node",
53        "unit address and first address in 'reg' (0x30000000200000001) don't match for /reg-nested-ranges/grandparent/parent/node",
54        f"compatible 'enums' in binding '{enums_hpath}' has non-tokenizable enum for property 'string-enum': 'foo bar', 'foo_bar'",
55        f"compatible 'enums' in binding '{enums_hpath}' has enum for property 'tokenizable-lower-enum' that is only tokenizable in lowercase: 'bar', 'BAR'",
56    ]
57    assert caplog.record_tuples == [('devicetree.edtlib', WARNING, warning_message)
58                                    for warning_message in expected_warnings]
59
60def test_interrupts():
61    '''Tests for the interrupts property.'''
62    with from_here():
63        edt = edtlib.EDT("test.dts", ["test-bindings"])
64
65    node = edt.get_node("/interrupt-parent-test/node")
66    controller = edt.get_node('/interrupt-parent-test/controller')
67    assert node.interrupts == [
68        edtlib.ControllerAndData(node=node, controller=controller, data={'one': 1, 'two': 2, 'three': 3}, name='foo', basename=None),
69        edtlib.ControllerAndData(node=node, controller=controller, data={'one': 4, 'two': 5, 'three': 6}, name='bar', basename=None)
70    ]
71
72    node = edt.get_node("/interrupts-extended-test/node")
73    controller_0 = edt.get_node('/interrupts-extended-test/controller-0')
74    controller_1 = edt.get_node('/interrupts-extended-test/controller-1')
75    controller_2 = edt.get_node('/interrupts-extended-test/controller-2')
76    assert node.interrupts == [
77        edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 1}, name=None, basename=None),
78        edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 2, 'two': 3}, name=None, basename=None),
79        edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 4, 'two': 5, 'three': 6}, name=None, basename=None)
80    ]
81
82    node = edt.get_node("/interrupt-map-test/node@0")
83    controller_0 = edt.get_node('/interrupt-map-test/controller-0')
84    controller_1 = edt.get_node('/interrupt-map-test/controller-1')
85    controller_2 = edt.get_node('/interrupt-map-test/controller-2')
86
87    assert node.interrupts == [
88        edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 0}, name=None, basename=None),
89        edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 1}, name=None, basename=None),
90        edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 2}, name=None, basename=None)
91    ]
92
93    node = edt.get_node("/interrupt-map-test/node@1")
94    assert node.interrupts == [
95        edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 3}, name=None, basename=None),
96        edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 4}, name=None, basename=None),
97        edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 5}, name=None, basename=None)
98    ]
99
100    node = edt.get_node("/interrupt-map-test/node@2")
101    assert node.interrupts == [
102        edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 0}, name=None, basename=None),
103        edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 1}, name=None, basename=None),
104        edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 2}, name=None, basename=None)
105    ]
106
107    node = edt.get_node("/interrupt-map-test/node@3")
108    assert node.interrupts == [
109        edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 0}, name=None, basename=None),
110        edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 1}, name=None, basename=None),
111        edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 2}, name=None, basename=None)
112    ]
113
114    node = edt.get_node("/interrupt-map-test/node@4")
115    assert node.interrupts == [
116        edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 3}, name=None, basename=None),
117        edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 4}, name=None, basename=None),
118        edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 5}, name=None, basename=None)
119    ]
120
121    node = edt.get_node("/interrupt-map-test/node@100000004")
122    assert node.interrupts == [
123        edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 3}, name=None, basename=None),
124        edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 4}, name=None, basename=None),
125        edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 5}, name=None, basename=None)
126    ]
127
128    node = edt.get_node("/interrupt-map-bitops-test/node@70000000E")
129    assert node.interrupts == [
130        edtlib.ControllerAndData(node=node, controller=edt.get_node('/interrupt-map-bitops-test/controller'), data={'one': 3, 'two': 2}, name=None, basename=None)
131    ]
132
133def test_ranges():
134    '''Tests for the ranges property'''
135    with from_here():
136        edt = edtlib.EDT("test.dts", ["test-bindings"])
137
138    node = edt.get_node("/reg-ranges/parent")
139    assert node.ranges == [
140        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),
141        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),
142        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)
143    ]
144
145    node = edt.get_node("/reg-nested-ranges/grandparent")
146    assert node.ranges == [
147        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)
148    ]
149
150    node = edt.get_node("/reg-nested-ranges/grandparent/parent")
151    assert node.ranges == [
152        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)
153    ]
154
155    assert edt.get_node("/ranges-zero-cells/node").ranges == []
156
157    node = edt.get_node("/ranges-zero-parent-cells/node")
158    assert node.ranges == [
159        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),
160        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),
161        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)
162    ]
163
164    node = edt.get_node("/ranges-one-address-cells/node")
165    assert node.ranges == [
166        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),
167        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),
168        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)
169    ]
170
171    node = edt.get_node("/ranges-one-address-two-size-cells/node")
172    assert node.ranges == [
173        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),
174        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),
175        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)
176    ]
177
178    node = edt.get_node("/ranges-two-address-cells/node@1")
179    assert node.ranges == [
180        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),
181        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),
182        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)
183    ]
184
185    node = edt.get_node("/ranges-two-address-two-size-cells/node@1")
186    assert node.ranges == [
187        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),
188        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),
189        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)
190    ]
191
192    node = edt.get_node("/ranges-three-address-cells/node@1")
193    assert node.ranges == [
194        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),
195        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),
196        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)
197    ]
198
199    node = edt.get_node("/ranges-three-address-two-size-cells/node@1")
200    assert node.ranges == [
201        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),
202        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),
203        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)
204    ]
205
206def test_reg():
207    '''Tests for the regs property'''
208    with from_here():
209        edt = edtlib.EDT("test.dts", ["test-bindings"])
210
211    def verify_regs(node, expected_tuples):
212        regs = node.regs
213        assert len(regs) == len(expected_tuples)
214        for reg, expected_tuple in zip(regs, expected_tuples):
215            name, addr, size = expected_tuple
216            assert reg.node is node
217            assert reg.name == name
218            assert reg.addr == addr
219            assert reg.size == size
220
221    verify_regs(edt.get_node("/reg-zero-address-cells/node"),
222                [('foo', None, 0x1),
223                 ('bar', None, 0x2)])
224
225    verify_regs(edt.get_node("/reg-zero-size-cells/node"),
226                [(None, 0x1, None),
227                 (None, 0x2, None)])
228
229    verify_regs(edt.get_node("/reg-ranges/parent/node"),
230                [(None, 0x5, 0x1),
231                 (None, 0xe0000000f, 0x1),
232                 (None, 0xc0000000e, 0x1),
233                 (None, 0xc0000000d, 0x1),
234                 (None, 0xa0000000b, 0x1),
235                 (None, 0x0, 0x1)])
236
237    verify_regs(edt.get_node("/reg-nested-ranges/grandparent/parent/node"),
238                [(None, 0x30000000200000001, 0x1)])
239
240def test_pinctrl():
241    '''Test 'pinctrl-<index>'.'''
242    with from_here():
243        edt = edtlib.EDT("test.dts", ["test-bindings"])
244
245    node = edt.get_node("/pinctrl/dev")
246    state_1 = edt.get_node('/pinctrl/pincontroller/state-1')
247    state_2 = edt.get_node('/pinctrl/pincontroller/state-2')
248    assert node.pinctrls == [
249        edtlib.PinCtrl(node=node, name='zero', conf_nodes=[]),
250        edtlib.PinCtrl(node=node, name='one', conf_nodes=[state_1]),
251        edtlib.PinCtrl(node=node, name='two', conf_nodes=[state_1, state_2])
252    ]
253
254def test_hierarchy():
255    '''Test Node.parent and Node.children'''
256    with from_here():
257        edt = edtlib.EDT("test.dts", ["test-bindings"])
258
259    assert edt.get_node("/").parent is None
260
261    assert str(edt.get_node("/parent/child-1").parent) == \
262        "<Node /parent in 'test.dts', no binding>"
263
264    assert str(edt.get_node("/parent/child-2/grandchild").parent) == \
265        "<Node /parent/child-2 in 'test.dts', no binding>"
266
267    assert str(edt.get_node("/parent").children) == \
268        "{'child-1': <Node /parent/child-1 in 'test.dts', no binding>, 'child-2': <Node /parent/child-2 in 'test.dts', no binding>}"
269
270    assert edt.get_node("/parent/child-1").children == {}
271
272def test_child_index():
273    '''Test Node.child_index.'''
274    with from_here():
275        edt = edtlib.EDT("test.dts", ["test-bindings"])
276
277    parent, child_1, child_2 = [edt.get_node(path) for path in
278                                ("/parent",
279                                 "/parent/child-1",
280                                 "/parent/child-2")]
281    assert parent.child_index(child_1) == 0
282    assert parent.child_index(child_2) == 1
283    with pytest.raises(KeyError):
284        parent.child_index(parent)
285
286def test_include():
287    '''Test 'include:' and the legacy 'inherits: !include ...' in bindings'''
288    with from_here():
289        edt = edtlib.EDT("test.dts", ["test-bindings"])
290
291    binding_include = edt.get_node("/binding-include")
292
293    assert binding_include.description == "Parent binding"
294
295    verify_props(binding_include,
296                 ['foo', 'bar', 'baz', 'qaz'],
297                 ['int', 'int', 'int', 'int'],
298                 [0, 1, 2, 3])
299
300    verify_props(edt.get_node("/binding-include/child"),
301                 ['foo', 'bar', 'baz', 'qaz'],
302                 ['int', 'int', 'int', 'int'],
303                 [0, 1, 2, 3])
304
305def test_include_filters():
306    '''Test property-allowlist and property-blocklist in an include.'''
307
308    fname2path = {'include.yaml': 'test-bindings-include/include.yaml',
309                  'include-2.yaml': 'test-bindings-include/include-2.yaml'}
310
311    with pytest.raises(edtlib.EDTError) as e:
312        with from_here():
313            edtlib.Binding("test-bindings-include/allow-and-blocklist.yaml", fname2path)
314    assert ("should not specify both 'property-allowlist:' and 'property-blocklist:'"
315            in str(e.value))
316
317    with pytest.raises(edtlib.EDTError) as e:
318        with from_here():
319            edtlib.Binding("test-bindings-include/allow-and-blocklist-child.yaml", fname2path)
320    assert ("should not specify both 'property-allowlist:' and 'property-blocklist:'"
321            in str(e.value))
322
323    with pytest.raises(edtlib.EDTError) as e:
324        with from_here():
325            edtlib.Binding("test-bindings-include/allow-not-list.yaml", fname2path)
326    value_str = str(e.value)
327    assert value_str.startswith("'property-allowlist' value")
328    assert value_str.endswith("should be a list")
329
330    with pytest.raises(edtlib.EDTError) as e:
331        with from_here():
332            edtlib.Binding("test-bindings-include/block-not-list.yaml", fname2path)
333    value_str = str(e.value)
334    assert value_str.startswith("'property-blocklist' value")
335    assert value_str.endswith("should be a list")
336
337    with pytest.raises(edtlib.EDTError) as e:
338        with from_here():
339            binding = edtlib.Binding("test-bindings-include/include-invalid-keys.yaml", fname2path)
340    value_str = str(e.value)
341    assert value_str.startswith(
342        "'include:' in test-bindings-include/include-invalid-keys.yaml should not have these "
343        "unexpected contents: ")
344    assert 'bad-key-1' in value_str
345    assert 'bad-key-2' in value_str
346
347    with pytest.raises(edtlib.EDTError) as e:
348        with from_here():
349            binding = edtlib.Binding("test-bindings-include/include-invalid-type.yaml", fname2path)
350    value_str = str(e.value)
351    assert value_str.startswith(
352        "'include:' in test-bindings-include/include-invalid-type.yaml "
353        "should be a string or list, but has type ")
354
355    with pytest.raises(edtlib.EDTError) as e:
356        with from_here():
357            binding = edtlib.Binding("test-bindings-include/include-no-name.yaml", fname2path)
358    value_str = str(e.value)
359    assert value_str.startswith("'include:' element")
360    assert value_str.endswith(
361        "in test-bindings-include/include-no-name.yaml should have a 'name' key")
362
363    with from_here():
364        binding = edtlib.Binding("test-bindings-include/allowlist.yaml", fname2path)
365        assert set(binding.prop2specs.keys()) == {'x'}  # 'x' is allowed
366
367        binding = edtlib.Binding("test-bindings-include/empty-allowlist.yaml", fname2path)
368        assert set(binding.prop2specs.keys()) == set()  # nothing is allowed
369
370        binding = edtlib.Binding("test-bindings-include/blocklist.yaml", fname2path)
371        assert set(binding.prop2specs.keys()) == {'y', 'z'}  # 'x' is blocked
372
373        binding = edtlib.Binding("test-bindings-include/empty-blocklist.yaml", fname2path)
374        assert set(binding.prop2specs.keys()) == {'x', 'y', 'z'}  # nothing is blocked
375
376        binding = edtlib.Binding("test-bindings-include/intermixed.yaml", fname2path)
377        assert set(binding.prop2specs.keys()) == {'x', 'a'}
378
379        binding = edtlib.Binding("test-bindings-include/include-no-list.yaml", fname2path)
380        assert set(binding.prop2specs.keys()) == {'x', 'y', 'z'}
381
382        binding = edtlib.Binding("test-bindings-include/filter-child-bindings.yaml", fname2path)
383        child = binding.child_binding
384        grandchild = child.child_binding
385        assert set(binding.prop2specs.keys()) == {'x'}
386        assert set(child.prop2specs.keys()) == {'child-prop-2'}
387        assert set(grandchild.prop2specs.keys()) == {'grandchild-prop-1'}
388
389        binding = edtlib.Binding("test-bindings-include/allow-and-blocklist-multilevel.yaml",
390                                 fname2path)
391        assert set(binding.prop2specs.keys()) == {'x'}  # 'x' is allowed
392        child = binding.child_binding
393        assert set(child.prop2specs.keys()) == {'child-prop-1', 'child-prop-2',
394                                                'x', 'z'}  # root level 'y' is blocked
395
396def test_include_filters_inherited_bindings() -> None:
397    '''Test the basics of filtering properties inherited via an intermediary binding file.
398
399    Use-case "B includes I includes X":
400    - X is a base binding file, specifying common properties
401    - I is an intermediary binding file, which includes X without modification
402      nor filter
403    - B includes I, filtering the properties it chooses to inherit
404      with an allowlist or a blocklist
405
406    Checks that the properties inherited from X via I are actually filtered
407    as B intends to.
408    '''
409    fname2path = {
410        # Base binding file, specifies a few properties up to the grandchild-binding level.
411        "simple.yaml": "test-bindings-include/simple.yaml",
412        # 'include:'s the base file above, without modification nor filter
413        "simple_inherit.yaml": "test-bindings-include/simple_inherit.yaml",
414    }
415    with from_here():
416        binding = edtlib.Binding(
417            # Filters inherited specifications with an allowlist.
418            "test-bindings-include/simple_filter_allowlist.yaml",
419            fname2path,
420            require_compatible=False,
421            require_description=False,
422        )
423    # Only property allowed.
424    assert {"prop-1"} == set(binding.prop2specs.keys())
425
426    with from_here():
427        binding = edtlib.Binding(
428            # Filters inherited specifications with a blocklist.
429            "test-bindings-include/simple_filter_blocklist.yaml",
430            fname2path,
431            require_compatible=False,
432            require_description=False,
433        )
434    # Only non blocked property.
435    assert {"prop-1"} == set(binding.prop2specs.keys())
436
437def test_include_filters_inherited_child_bindings() -> None:
438    '''Test the basics of filtering properties inherited via an intermediary binding file
439    (child-binding level).
440
441    See also: test_include_filters_inherited_bindings()
442    '''
443    fname2path = {
444        "simple.yaml": "test-bindings-include/simple.yaml",
445        "simple_inherit.yaml": "test-bindings-include/simple_inherit.yaml",
446    }
447    with from_here():
448        binding = edtlib.Binding(
449            "test-bindings-include/simple_filter_allowlist.yaml",
450            fname2path,
451            require_compatible=False,
452            require_description=False,
453        )
454    assert binding.child_binding
455    child_binding = binding.child_binding
456    # Only property allowed.
457    assert {"child-prop-1"} == set(child_binding.prop2specs.keys())
458
459    with from_here():
460        binding = edtlib.Binding(
461            "test-bindings-include/simple_filter_blocklist.yaml",
462            fname2path,
463            require_compatible=False,
464            require_description=False,
465        )
466    # Only non blocked property.
467    assert binding.child_binding
468    child_binding = binding.child_binding
469    assert {"child-prop-1"} == set(child_binding.prop2specs.keys())
470
471def test_include_filters_inherited_grandchild_bindings() -> None:
472    '''Test the basics of filtering properties inherited via an intermediary binding file
473    (grandchild-binding level).
474
475    See also: test_include_filters_inherited_bindings()
476    '''
477    fname2path = {
478        "simple.yaml": "test-bindings-include/simple.yaml",
479        "simple_inherit.yaml": "test-bindings-include/simple_inherit.yaml",
480    }
481    with from_here():
482        binding = edtlib.Binding(
483            "test-bindings-include/simple_filter_allowlist.yaml",
484            fname2path,
485            require_compatible=False,
486            require_description=False,
487        )
488    assert binding.child_binding
489    child_binding = binding.child_binding
490    assert child_binding.child_binding
491    grandchild_binding = child_binding.child_binding
492    # Only property allowed.
493    assert {"grandchild-prop-1"} == set(grandchild_binding.prop2specs.keys())
494
495    with from_here():
496        binding = edtlib.Binding(
497            "test-bindings-include/simple_filter_blocklist.yaml",
498            fname2path,
499            require_compatible=False,
500            require_description=False,
501        )
502    assert binding.child_binding
503    child_binding = binding.child_binding
504    assert child_binding.child_binding
505    grandchild_binding = child_binding.child_binding
506    # Only non blocked property.
507    assert {"grandchild-prop-1"} == set(grandchild_binding.prop2specs.keys())
508
509def test_bus():
510    '''Test 'bus:' and 'on-bus:' in bindings'''
511    with from_here():
512        edt = edtlib.EDT("test.dts", ["test-bindings"])
513
514    assert isinstance(edt.get_node("/buses/foo-bus").buses, list)
515    assert "foo" in edt.get_node("/buses/foo-bus").buses
516
517    # foo-bus does not itself appear on a bus
518    assert isinstance(edt.get_node("/buses/foo-bus").on_buses, list)
519    assert not edt.get_node("/buses/foo-bus").on_buses
520    assert edt.get_node("/buses/foo-bus").bus_node is None
521
522    # foo-bus/node1 is not a bus node...
523    assert isinstance(edt.get_node("/buses/foo-bus/node1").buses, list)
524    assert not edt.get_node("/buses/foo-bus/node1").buses
525    # ...but is on a bus
526    assert isinstance(edt.get_node("/buses/foo-bus/node1").on_buses, list)
527    assert "foo" in edt.get_node("/buses/foo-bus/node1").on_buses
528    assert edt.get_node("/buses/foo-bus/node1").bus_node.path == \
529        "/buses/foo-bus"
530
531    # foo-bus/node2 is not a bus node...
532    assert isinstance(edt.get_node("/buses/foo-bus/node2").buses, list)
533    assert not edt.get_node("/buses/foo-bus/node2").buses
534    # ...but is on a bus
535    assert isinstance(edt.get_node("/buses/foo-bus/node2").on_buses, list)
536    assert "foo" in edt.get_node("/buses/foo-bus/node2").on_buses
537
538    # no-bus-node is not a bus node...
539    assert isinstance(edt.get_node("/buses/no-bus-node").buses, list)
540    assert not edt.get_node("/buses/no-bus-node").buses
541    # ... and is not on a bus
542    assert isinstance(edt.get_node("/buses/no-bus-node").on_buses, list)
543    assert not edt.get_node("/buses/no-bus-node").on_buses
544
545    # Same compatible string, but different bindings from being on different
546    # buses
547    assert str(edt.get_node("/buses/foo-bus/node1").binding_path) == \
548        hpath("test-bindings/device-on-foo-bus.yaml")
549    assert str(edt.get_node("/buses/foo-bus/node2").binding_path) == \
550        hpath("test-bindings/device-on-any-bus.yaml")
551    assert str(edt.get_node("/buses/bar-bus/node").binding_path) == \
552        hpath("test-bindings/device-on-bar-bus.yaml")
553    assert str(edt.get_node("/buses/no-bus-node").binding_path) == \
554        hpath("test-bindings/device-on-any-bus.yaml")
555
556    # foo-bus/node/nested also appears on the foo-bus bus
557    assert isinstance(edt.get_node("/buses/foo-bus/node1/nested").on_buses, list)
558    assert "foo" in edt.get_node("/buses/foo-bus/node1/nested").on_buses
559    assert str(edt.get_node("/buses/foo-bus/node1/nested").binding_path) == \
560        hpath("test-bindings/device-on-foo-bus.yaml")
561
562def test_binding_top_key():
563    fname2path = {'include.yaml': 'test-bindings-include/include.yaml',
564                  'include-2.yaml': 'test-bindings-include/include-2.yaml'}
565
566    with from_here():
567        binding = edtlib.Binding("test-bindings/defaults.yaml", fname2path)
568    title = binding.title
569    description = binding.description
570    compatible = binding.compatible
571
572    assert title == "Test binding"
573    assert description == "Property default value test"
574    assert compatible == "defaults"
575
576def test_child_binding():
577    '''Test 'child-binding:' in bindings'''
578    with from_here():
579        edt = edtlib.EDT("test.dts", ["test-bindings"])
580    child1 = edt.get_node("/child-binding/child-1")
581    child2 = edt.get_node("/child-binding/child-2")
582    grandchild = edt.get_node("/child-binding/child-1/grandchild")
583
584    assert str(child1.binding_path) == hpath("test-bindings/child-binding.yaml")
585    assert str(child1.description) == "child node"
586    verify_props(child1, ['child-prop'], ['int'], [1])
587
588    assert str(child2.binding_path) == hpath("test-bindings/child-binding.yaml")
589    assert str(child2.description) == "child node"
590    verify_props(child2, ['child-prop'], ['int'], [3])
591
592    assert str(grandchild.binding_path) == hpath("test-bindings/child-binding.yaml")
593    assert str(grandchild.description) == "grandchild node"
594    verify_props(grandchild, ['grandchild-prop'], ['int'], [2])
595
596    with from_here():
597        binding_file = Path("test-bindings/child-binding.yaml").resolve()
598        top = edtlib.Binding(binding_file, {})
599    child = top.child_binding
600    assert Path(top.path) == binding_file
601    assert Path(child.path) == binding_file
602    assert top.compatible == 'top-binding'
603    assert child.compatible is None
604
605    with from_here():
606        binding_file = Path("test-bindings/child-binding-with-compat.yaml").resolve()
607        top = edtlib.Binding(binding_file, {})
608    child = top.child_binding
609    assert Path(top.path) == binding_file
610    assert Path(child.path) == binding_file
611    assert top.compatible == 'top-binding-with-compat'
612    assert child.compatible == 'child-compat'
613
614def test_props():
615    '''Test Node.props (derived from DT and 'properties:' in the binding)'''
616    with from_here():
617        edt = edtlib.EDT("test.dts", ["test-bindings"])
618
619    props_node = edt.get_node('/props')
620    ctrl_1, ctrl_2 = [edt.get_node(path) for path in ['/ctrl-1', '/ctrl-2']]
621
622    verify_props(props_node,
623                 ['int',
624                  'existent-boolean', 'nonexistent-boolean',
625                  'array', 'uint8-array',
626                  'string', 'string-array',
627                  'phandle-ref', 'phandle-refs',
628                  'path'],
629                 ['int',
630                  'boolean', 'boolean',
631                  'array', 'uint8-array',
632                  'string', 'string-array',
633                  'phandle', 'phandles',
634                  'path'],
635                 [1,
636                  True, False,
637                  [1,2,3], b'\x124',
638                  'foo', ['foo','bar','baz'],
639                  ctrl_1, [ctrl_1,ctrl_2],
640                  ctrl_1])
641
642    verify_phandle_array_prop(props_node,
643                              'phandle-array-foos',
644                              [(ctrl_1, {'one': 1}),
645                               (ctrl_2, {'one': 2, 'two': 3})])
646
647    verify_phandle_array_prop(edt.get_node("/props-2"),
648                              "phandle-array-foos",
649                              [(edt.get_node('/ctrl-0-1'), {}),
650                               None,
651                               (edt.get_node('/ctrl-0-2'), {})])
652
653    verify_phandle_array_prop(props_node,
654                              'foo-gpios',
655                              [(ctrl_1, {'gpio-one': 1})])
656
657def test_nexus():
658    '''Test <prefix>-map via gpio-map (the most common case).'''
659    with from_here():
660        edt = edtlib.EDT("test.dts", ["test-bindings"])
661
662    source = edt.get_node("/gpio-map/source")
663    destination = edt.get_node('/gpio-map/destination')
664    verify_phandle_array_prop(source,
665                              'foo-gpios',
666                              [(destination, {'val': 6}),
667                               (destination, {'val': 5})])
668
669    assert source.props["foo-gpios"].val[0].basename == f"gpio"
670
671def test_prop_defaults():
672    '''Test property default values given in bindings'''
673    with from_here():
674        edt = edtlib.EDT("test.dts", ["test-bindings"])
675
676    verify_props(edt.get_node("/defaults"),
677                 ['int',
678                  'array', 'uint8-array',
679                  'string', 'string-array',
680                  'default-not-used'],
681                 ['int',
682                  'array', 'uint8-array',
683                  'string', 'string-array',
684                  'int'],
685                 [123,
686                  [1,2,3], b'\x89\xab\xcd',
687                  'hello', ['hello','there'],
688                  234])
689
690def test_prop_enums():
691    '''test properties with enum: in the binding'''
692
693    with from_here():
694        edt = edtlib.EDT("test.dts", ["test-bindings"])
695    props = edt.get_node('/enums').props
696    int_enum = props['int-enum']
697    string_enum = props['string-enum']
698    tokenizable_enum = props['tokenizable-enum']
699    tokenizable_lower_enum = props['tokenizable-lower-enum']
700    array_enum = props['array-enum']
701    string_array_enum = props['string-array-enum']
702    no_enum = props['no-enum']
703
704    assert int_enum.val == 1
705    assert int_enum.enum_indices[0] == 0
706    assert not int_enum.spec.enum_tokenizable
707    assert not int_enum.spec.enum_upper_tokenizable
708
709    assert string_enum.val == 'foo_bar'
710    assert string_enum.enum_indices[0] == 1
711    assert not string_enum.spec.enum_tokenizable
712    assert not string_enum.spec.enum_upper_tokenizable
713
714    assert tokenizable_enum.val == '123 is ok'
715    assert tokenizable_enum.val_as_tokens[0] == '123_is_ok'
716    assert tokenizable_enum.enum_indices[0] == 2
717    assert tokenizable_enum.spec.enum_tokenizable
718    assert tokenizable_enum.spec.enum_upper_tokenizable
719
720    assert tokenizable_lower_enum.val == 'bar'
721    assert tokenizable_lower_enum.val_as_tokens[0] == 'bar'
722    assert tokenizable_lower_enum.enum_indices[0] == 0
723    assert tokenizable_lower_enum.spec.enum_tokenizable
724    assert not tokenizable_lower_enum.spec.enum_upper_tokenizable
725
726    assert array_enum.val == [0, 40, 40, 10]
727    assert array_enum.enum_indices == [0, 4, 4, 1]
728    assert not array_enum.spec.enum_tokenizable
729    assert not array_enum.spec.enum_upper_tokenizable
730
731    assert string_array_enum.val == ["foo", "bar"]
732    assert string_array_enum.val_as_tokens == ["foo", "bar"]
733    assert string_array_enum.enum_indices == [1, 0]
734    assert string_array_enum.spec.enum_tokenizable
735    assert string_array_enum.spec.enum_upper_tokenizable
736
737    assert no_enum.enum_indices is None
738    assert not no_enum.spec.enum_tokenizable
739    assert not no_enum.spec.enum_upper_tokenizable
740
741def test_binding_inference():
742    '''Test inferred bindings for special zephyr-specific nodes.'''
743    warnings = io.StringIO()
744    with from_here():
745        edt = edtlib.EDT("test.dts", ["test-bindings"], warnings)
746
747    assert str(edt.get_node("/zephyr,user").props) == '{}'
748
749    with from_here():
750        edt = edtlib.EDT("test.dts", ["test-bindings"], warnings,
751                         infer_binding_for_paths=["/zephyr,user"])
752    ctrl_1 = edt.get_node('/ctrl-1')
753    ctrl_2 = edt.get_node('/ctrl-2')
754    zephyr_user = edt.get_node("/zephyr,user")
755
756    verify_props(zephyr_user,
757                 ['boolean', 'bytes', 'number',
758                  'numbers', 'string', 'strings'],
759                 ['boolean', 'uint8-array', 'int',
760                  'array', 'string', 'string-array'],
761                 [True, b'\x81\x82\x83', 23,
762                  [1,2,3], 'text', ['a','b','c']])
763
764    assert zephyr_user.props['handle'].val is ctrl_1
765
766    phandles = zephyr_user.props['phandles']
767    val = phandles.val
768    assert len(val) == 2
769    assert val[0] is ctrl_1
770    assert val[1] is ctrl_2
771
772    verify_phandle_array_prop(zephyr_user,
773                              'phandle-array-foos',
774                              [(edt.get_node('/ctrl-2'), {'one': 1, 'two': 2})])
775
776def test_multi_bindings():
777    '''Test having multiple directories with bindings'''
778    with from_here():
779        edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"])
780
781    assert str(edt.get_node("/in-dir-1").binding_path) == \
782        hpath("test-bindings/multidir.yaml")
783
784    assert str(edt.get_node("/in-dir-2").binding_path) == \
785        hpath("test-bindings-2/multidir.yaml")
786
787def test_dependencies():
788    ''''Test dependency relations'''
789    with from_here():
790        edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"])
791
792    assert edt.get_node("/").dep_ordinal == 0
793    assert edt.get_node("/in-dir-1").dep_ordinal == 1
794    assert edt.get_node("/") in edt.get_node("/in-dir-1").depends_on
795    assert edt.get_node("/in-dir-1") in edt.get_node("/").required_by
796
797def test_child_dependencies():
798    '''Test dependencies relashionship with child nodes propagated to parent'''
799    with from_here():
800        edt = edtlib.EDT("test.dts", ["test-bindings"])
801
802    dep_node = edt.get_node("/child-binding-dep")
803
804    assert dep_node in edt.get_node("/child-binding").depends_on
805    assert dep_node in edt.get_node("/child-binding/child-1/grandchild").depends_on
806    assert dep_node in edt.get_node("/child-binding/child-2").depends_on
807    assert edt.get_node("/child-binding") in dep_node.required_by
808    assert edt.get_node("/child-binding/child-1/grandchild") in dep_node.required_by
809    assert edt.get_node("/child-binding/child-2") in dep_node.required_by
810
811def test_slice_errs(tmp_path):
812    '''Test error messages from the internal _slice() helper'''
813
814    dts_file = tmp_path / "error.dts"
815
816    verify_error("""
817/dts-v1/;
818
819/ {
820	#address-cells = <1>;
821	#size-cells = <2>;
822
823	sub {
824		reg = <3>;
825	};
826};
827""",
828                 dts_file,
829                 f"'reg' property in <Node /sub in {dts_file}:8> 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').")
830
831    verify_error("""
832/dts-v1/;
833
834/ {
835	sub {
836		interrupts = <1>;
837		interrupt-parent = < &{/controller} >;
838	};
839	controller {
840		interrupt-controller;
841		#interrupt-cells = <2>;
842	};
843};
844""",
845                 dts_file,
846                 f"'interrupts' property in <Node /sub in {dts_file}:5> 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').")
847
848    verify_error("""
849/dts-v1/;
850
851/ {
852	#address-cells = <1>;
853
854	sub-1 {
855		#address-cells = <2>;
856		#size-cells = <3>;
857		ranges = <4 5>;
858
859		sub-2 {
860			reg = <1 2 3 4 5>;
861		};
862	};
863};
864""",
865                 dts_file,
866                 f"'ranges' property in <Node /sub-1 in {dts_file}:7> 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').")
867
868def test_bad_compatible(tmp_path):
869    # An invalid compatible should cause an error, even on a node with
870    # no binding.
871
872    dts_file = tmp_path / "error.dts"
873
874    verify_error("""
875/dts-v1/;
876
877/ {
878	foo {
879		compatible = "no, whitespace";
880	};
881};
882""",
883                 dts_file,
884                 r"node '/foo' compatible 'no, whitespace' must match this regular expression: '^[a-zA-Z][a-zA-Z0-9,+\-._]+$'")
885
886def test_wrong_props():
887    '''Test Node.wrong_props (derived from DT and 'properties:' in the binding)'''
888
889    with from_here():
890        with pytest.raises(edtlib.EDTError) as e:
891            edtlib.Binding("test-wrong-bindings/wrong-specifier-space-type.yaml", None)
892        assert ("'specifier-space' in 'properties: wrong-type-for-specifier-space' has type 'phandle', expected 'phandle-array'"
893            in str(e.value))
894
895        with pytest.raises(edtlib.EDTError) as e:
896            edtlib.Binding("test-wrong-bindings/wrong-phandle-array-name.yaml", None)
897        value_str = str(e.value)
898        assert value_str.startswith("'wrong-phandle-array-name' in 'properties:'")
899        assert value_str.endswith("but no 'specifier-space' was provided.")
900
901
902def test_deepcopy():
903    with from_here():
904        # We intentionally use different kwarg values than the
905        # defaults to make sure they're getting copied. This implies
906        # we have to set werror=True, so we can't use test.dts, since
907        # that generates warnings on purpose.
908        edt = edtlib.EDT("test-multidir.dts",
909                         ["test-bindings", "test-bindings-2"],
910                         warn_reg_unit_address_mismatch=False,
911                         default_prop_types=False,
912                         support_fixed_partitions_on_any_bus=False,
913                         infer_binding_for_paths=['/test-node'],
914                         vendor_prefixes={'test-vnd': 'A test vendor'},
915                         werror=True)
916        edt_copy = deepcopy(edt)
917
918    def equal_paths(list1, list2):
919        assert len(list1) == len(list2)
920        return all(elt1.path == elt2.path for elt1, elt2 in zip(list1, list2))
921
922    def equal_key2path(key2node1, key2node2):
923        assert len(key2node1) == len(key2node2)
924        return (all(key1 == key2 for (key1, key2) in
925                    zip(key2node1, key2node2)) and
926                all(node1.path == node2.path for (node1, node2) in
927                    zip(key2node1.values(), key2node2.values())))
928
929    def equal_key2paths(key2nodes1, key2nodes2):
930        assert len(key2nodes1) == len(key2nodes2)
931        return (all(key1 == key2 for (key1, key2) in
932                    zip(key2nodes1, key2nodes2)) and
933                all(equal_paths(nodes1, nodes2) for (nodes1, nodes2) in
934                    zip(key2nodes1.values(), key2nodes2.values())))
935
936    def test_equal_but_not_same(attribute, equal=None):
937        if equal is None:
938            equal = lambda a, b: a == b
939        copy = getattr(edt_copy, attribute)
940        original = getattr(edt, attribute)
941        assert equal(copy, original)
942        assert copy is not original
943
944    test_equal_but_not_same("nodes", equal_paths)
945    test_equal_but_not_same("compat2nodes", equal_key2paths)
946    test_equal_but_not_same("compat2okay", equal_key2paths)
947    test_equal_but_not_same("compat2vendor")
948    test_equal_but_not_same("compat2model")
949    test_equal_but_not_same("label2node", equal_key2path)
950    test_equal_but_not_same("dep_ord2node", equal_key2path)
951    assert edt_copy.dts_path == "test-multidir.dts"
952    assert edt_copy.bindings_dirs == ["test-bindings", "test-bindings-2"]
953    assert edt_copy.bindings_dirs is not edt.bindings_dirs
954    assert not edt_copy._warn_reg_unit_address_mismatch
955    assert not edt_copy._default_prop_types
956    assert not edt_copy._fixed_partitions_no_bus
957    assert edt_copy._infer_binding_for_paths == set(["/test-node"])
958    assert edt_copy._infer_binding_for_paths is not edt._infer_binding_for_paths
959    assert edt_copy._vendor_prefixes == {"test-vnd": "A test vendor"}
960    assert edt_copy._vendor_prefixes is not edt._vendor_prefixes
961    assert edt_copy._werror
962    test_equal_but_not_same("_compat2binding", equal_key2path)
963    test_equal_but_not_same("_binding_paths")
964    test_equal_but_not_same("_binding_fname2path")
965    assert len(edt_copy._node2enode) == len(edt._node2enode)
966    for node1, node2 in zip(edt_copy._node2enode, edt._node2enode):
967        enode1 = edt_copy._node2enode[node1]
968        enode2 = edt._node2enode[node2]
969        assert node1.path == node2.path
970        assert enode1.path == enode2.path
971        assert node1 is not node2
972        assert enode1 is not enode2
973    assert edt_copy._dt is not edt._dt
974
975
976def verify_error(dts, dts_file, expected_err):
977    # Verifies that parsing a file 'dts_file' with the contents 'dts'
978    # (a string) raises an EDTError with the message 'expected_err'.
979    #
980    # The path 'dts_file' is written with the string 'dts' before the
981    # test is run.
982
983    with open(dts_file, "w", encoding="utf-8") as f:
984        f.write(dts)
985        f.flush()  # Can't have unbuffered text IO, so flush() instead
986
987    with pytest.raises(edtlib.EDTError) as e:
988        edtlib.EDT(dts_file, [])
989
990    assert str(e.value) == expected_err
991
992
993def verify_props(node, names, types, values):
994    # Verifies that each property in 'names' has the expected
995    # value in 'values'. Property lookup is done in Node 'node'.
996
997    for name, type, value in zip(names, types, values):
998        prop = node.props[name]
999        assert prop.name == name
1000        assert prop.type == type
1001        assert prop.val == value
1002        assert prop.node is node
1003
1004def verify_phandle_array_prop(node, name, values):
1005    # Verifies 'node.props[name]' is a phandle-array, and has the
1006    # expected controller/data values in 'values'. Elements
1007    # of 'values' may be None.
1008
1009    prop = node.props[name]
1010    assert prop.type == 'phandle-array'
1011    assert prop.name == name
1012    val = prop.val
1013    assert isinstance(val, list)
1014    assert len(val) == len(values)
1015    for actual, expected in zip(val, values):
1016        if expected is not None:
1017            controller, data = expected
1018            assert isinstance(actual, edtlib.ControllerAndData)
1019            assert actual.controller is controller
1020            assert actual.data == data
1021        else:
1022            assert actual is None
1023