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