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