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