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