1# SPDX-License-Identifier: Apache-2.0 2# Copyright (c) 2024 Christophe Dufaza 3 4"""Unit tests dedicated to edtlib.Binding objects initialization. 5 6Running the assumption that any (valid) YAML binding file is 7something we can make a Binding instance with: 8- check which properties are defined at which level (binding, child-binding, 9 grandchild-binding, etc) and their specifications once the binding 10 is initialized 11- check how including bindings are permitted to specialize 12 the specifications of inherited properties 13- check the rules applied when overwriting a binding's description 14 or compatible string (at the binding, child-binding, etc, levels) 15 16At any level, an including binding is permitted to: 17- filter the properties it chooses to inherit with either "property:allowlist" 18 or "property:blocklist" but not both 19- extend inherited properties: 20 - override (implicit or) explicit "required: false" with "required: true" 21 - add constraints to the possible value(s) of a property with "const:" 22 or "enum:" 23 - add a "default:" value to a property 24 - add or overwrite a property's "description:"; when overwritten multiple 25 times by several included binding files, "include:"ed first "wins" 26- define new properties 27 28At any level, an including binding is NOT permitted to: 29- remove a requirement by overriding "required: true" with "required: false" 30- change existing constrains applied to the possible values of 31 an inherited property with "const:" or "enum:" 32- change the "default:" value of an inherited property 33- change the "type:" of an inherited property 34 35Rules applying to bindings' descriptions and compatible strings: 36- included files can't overwrite the description or compatible string set 37 by the including binding (despite that "description:" appears before 38 "include:"): this seems consistent, the top-level binding file "wins" 39- an including binding can overwrite descriptions and compatible strings 40 inherited at the child-binding levels: this seems consistent, 41 the top-level binding file "wins" 42- when we include multiple files overwriting a description or compatible 43 string inherited at the child-binding levels, order of inclusion matters, 44 the first "wins"; this is consistent with property descriptions 45 46For all tests, the entry point is a Binding instance initialized 47by loading the YAML file which represents the test case: our focus here 48really is what happens when we (recursively) call Binding's constructor, 49independently of any actual devicetree model (edtlib.EDT instance). 50""" 51 52# pylint: disable=too-many-statements 53 54import contextlib 55import os 56from collections.abc import Generator 57from typing import Any 58 59import pytest 60from devicetree import edtlib 61 62YAML_KNOWN_BASE_BINDINGS: dict[str, str] = { 63 # Base properties, bottom of the diamond test case. 64 "base.yaml": "test-bindings-init/base.yaml", 65 # Amended properties, left (first) "include:" in the diamond test case. 66 "base_amend.yaml": "test-bindings-init/base_amend.yaml", 67 # Amended properties, right (last) "include:" in the diamond test case. 68 "thing.yaml": "test-bindings-init/thing.yaml", 69 # Used for testing property filters when "A includes B includes C". 70 "simple.yaml": "test-bindings-init/simple.yaml", 71 "simple_inherit.yaml": "test-bindings-init/simple_inherit.yaml", 72 "simple_allowlist.yaml": "test-bindings-init/simple_allowlist.yaml", 73 "simple_blocklist.yaml": "test-bindings-init/simple_blocklist.yaml", 74 # Test applied rules for compatible strings and descriptions. 75 "compat_desc_base.yaml": "test-bindings-init/compat_desc_base.yaml", 76 "compat_desc.yaml": "test-bindings-init/compat_desc.yaml", 77} 78 79 80def load_binding(path: str) -> edtlib.Binding: 81 """Load YAML file as Binding instance, 82 using YAML_BASE to resolve includes. 83 84 Args: 85 path: Path relative to ZEPHYR_BASE/scripts/dts/python-devicetree/tests. 86 """ 87 with _from_here(): 88 binding = edtlib.Binding( 89 path=path, 90 fname2path=YAML_KNOWN_BASE_BINDINGS, 91 raw=None, 92 require_compatible=False, 93 require_description=False, 94 ) 95 return binding 96 97 98def child_binding_of(path: str) -> edtlib.Binding: 99 """Load YAML file as Binding instance, and returns its child-binding. 100 The child-binding must exist. 101 102 Args: 103 path: Path relative to ZEPHYR_BASE/scripts/dts/python-devicetree/tests. 104 """ 105 binding = load_binding(path) 106 assert binding.child_binding 107 return binding.child_binding 108 109 110def grandchild_binding_of(path: str) -> edtlib.Binding: 111 """Load YAML file as Binding instance, and returns its grandchild-binding. 112 The grandchild-binding must exist. 113 114 Args: 115 path: Path relative to ZEPHYR_BASE/scripts/dts/python-devicetree/tests. 116 """ 117 child_binding = child_binding_of(path) 118 assert child_binding.child_binding 119 return child_binding.child_binding 120 121 122def verify_expected_propspec( 123 propspec: edtlib.PropertySpec, 124 /, 125 *, 126 # Most properties are integers. 127 expect_type: str = "int", 128 expect_req: bool = False, 129 expect_desc: str | None = None, 130 expect_enum: list[int | str] | None = None, 131 expect_const: Any | None = None, 132 expect_default: Any | None = None, 133) -> None: 134 """Compare a property specification with the definitions 135 we (finally) expect. 136 137 All definitions are tested for equality. 138 139 Args: 140 propsec: The property specification to verify. 141 expect_type: The expected property type definition. 142 expect_req: Whether the property is expected to be required. 143 expect_desc: The expected property description. 144 expect_enum: The expected property "enum:" definition. 145 expect_const: The expected property "const:" definition. 146 expect_default: The expected property "default:" definition. 147 """ 148 assert expect_type == propspec.type 149 assert expect_req == propspec.required 150 assert expect_desc == propspec.description 151 assert expect_enum == propspec.enum 152 assert expect_const == propspec.const 153 assert expect_default == propspec.default 154 155 156def verify_binding_propspecs_consistency(binding: edtlib.Binding) -> None: 157 """Verify consistency between what's in Binding.prop2specs 158 and Binding.raw. 159 160 Asserts that: 161 Binding.raw["properties"][prop] == Binding.prop2specs[prop]._raw 162 163 If the binding has a child-binding, also recursively verify child-bindings. 164 165 NOTE: do not confuse with binding.prop2specs[prop].binding == binding, 166 which we do not assume here. 167 """ 168 if binding.prop2specs: 169 assert set(binding.raw["properties"].keys()) == set(binding.prop2specs.keys()) 170 assert all( 171 binding.raw["properties"][prop] == propspec._raw 172 for prop, propspec in binding.prop2specs.items() 173 ) 174 if binding.child_binding: 175 verify_binding_propspecs_consistency(binding.child_binding) 176 177 178def test_expect_propspecs_inherited_bindings() -> None: 179 """Test the basics of including property specifications. 180 181 Specifications are simply inherited without modifications 182 up to the grandchild-binding level. 183 184 Check that we actually inherit all expected definitions as-is. 185 """ 186 binding = load_binding("test-bindings-init/base_inherit.yaml") 187 188 # Binding level. 189 assert { 190 "prop-1", 191 "prop-2", 192 "prop-enum", 193 "prop-req", 194 "prop-const", 195 "prop-default", 196 } == set(binding.prop2specs.keys()) 197 propspec = binding.prop2specs["prop-1"] 198 verify_expected_propspec(propspec, expect_desc="Base property 1.") 199 propspec = binding.prop2specs["prop-2"] 200 verify_expected_propspec(propspec, expect_type="string") 201 propspec = binding.prop2specs["prop-enum"] 202 verify_expected_propspec(propspec, expect_type="string", expect_enum=["FOO", "BAR"]) 203 propspec = binding.prop2specs["prop-const"] 204 verify_expected_propspec(propspec, expect_const=8) 205 propspec = binding.prop2specs["prop-req"] 206 verify_expected_propspec(propspec, expect_req=True) 207 propspec = binding.prop2specs["prop-default"] 208 verify_expected_propspec(propspec, expect_default=1) 209 210 # Child-Binding level. 211 assert binding.child_binding 212 child_binding = binding.child_binding 213 assert { 214 "child-prop-1", 215 "child-prop-2", 216 "child-prop-enum", 217 "child-prop-req", 218 "child-prop-const", 219 "child-prop-default", 220 } == set(child_binding.prop2specs.keys()) 221 propspec = child_binding.prop2specs["child-prop-1"] 222 verify_expected_propspec(propspec, expect_desc="Base child-prop 1.") 223 propspec = child_binding.prop2specs["child-prop-2"] 224 verify_expected_propspec(propspec, expect_type="string") 225 propspec = child_binding.prop2specs["child-prop-enum"] 226 verify_expected_propspec(propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"]) 227 propspec = child_binding.prop2specs["child-prop-const"] 228 verify_expected_propspec(propspec, expect_const=16) 229 propspec = child_binding.prop2specs["child-prop-req"] 230 verify_expected_propspec(propspec, expect_req=True) 231 propspec = child_binding.prop2specs["child-prop-default"] 232 verify_expected_propspec(propspec, expect_default=2) 233 234 # GrandChild-Binding level. 235 assert child_binding.child_binding 236 grandchild_binding = child_binding.child_binding 237 assert { 238 "grandchild-prop-1", 239 "grandchild-prop-2", 240 "grandchild-prop-enum", 241 "grandchild-prop-req", 242 "grandchild-prop-const", 243 "grandchild-prop-default", 244 } == set(grandchild_binding.prop2specs.keys()) 245 propspec = grandchild_binding.prop2specs["grandchild-prop-1"] 246 verify_expected_propspec(propspec, expect_desc="Base grandchild-prop 1.") 247 propspec = grandchild_binding.prop2specs["grandchild-prop-2"] 248 verify_expected_propspec(propspec, expect_type="string") 249 propspec = grandchild_binding.prop2specs["grandchild-prop-enum"] 250 verify_expected_propspec( 251 propspec, 252 expect_type="string", 253 expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], 254 ) 255 propspec = grandchild_binding.prop2specs["grandchild-prop-const"] 256 verify_expected_propspec(propspec, expect_const=32) 257 propspec = grandchild_binding.prop2specs["grandchild-prop-req"] 258 verify_expected_propspec(propspec, expect_req=True) 259 propspec = grandchild_binding.prop2specs["grandchild-prop-default"] 260 verify_expected_propspec(propspec, expect_default=3) 261 262 263def test_expect_propspecs_amended_bindings() -> None: 264 """Test the basics of including and amending property specifications. 265 266 Base specifications are included once at the binding level: 267 268 include: base.yaml 269 properties: 270 # Amend base.yaml 271 child-binding: 272 properties: 273 # Amend base.yaml 274 child-binding: 275 properties: 276 # Amend base.yaml 277 278 Check that we finally get the expected property specifications 279 up to the grandchild-binding level. 280 """ 281 binding = load_binding("test-bindings-init/base_amend.yaml") 282 283 # Binding level. 284 # 285 assert { 286 "prop-1", 287 "prop-2", 288 "prop-enum", 289 "prop-req", 290 "prop-const", 291 "prop-default", 292 "prop-new", 293 } == set(binding.prop2specs.keys()) 294 propspec = binding.prop2specs["prop-1"] 295 verify_expected_propspec( 296 propspec, 297 # Amended in base_amend.yaml. 298 expect_desc="Overwritten description.", 299 expect_const=0xF0, 300 ) 301 propspec = binding.prop2specs["prop-2"] 302 verify_expected_propspec( 303 propspec, 304 expect_type="string", 305 # Amended in base_amend.yaml. 306 expect_desc="New description.", 307 expect_enum=["EXT_FOO", "EXT_BAR"], 308 expect_default="EXT_FOO", 309 ) 310 propspec = binding.prop2specs["prop-enum"] 311 verify_expected_propspec( 312 propspec, 313 expect_type="string", 314 expect_enum=["FOO", "BAR"], 315 # Amended in base_amend.yaml. 316 expect_req=True, 317 ) 318 # Inherited from base.yaml without modification. 319 propspec = binding.prop2specs["prop-const"] 320 verify_expected_propspec(propspec, expect_const=8) 321 propspec = binding.prop2specs["prop-req"] 322 verify_expected_propspec(propspec, expect_req=True) 323 propspec = binding.prop2specs["prop-default"] 324 verify_expected_propspec(propspec, expect_default=1) 325 326 # New property in base_amend.yaml. 327 propspec = binding.prop2specs["prop-new"] 328 verify_expected_propspec(propspec) 329 330 # Child-Binding level. 331 # 332 assert binding.child_binding 333 child_binding = binding.child_binding 334 assert { 335 "child-prop-1", 336 "child-prop-2", 337 "child-prop-enum", 338 "child-prop-req", 339 "child-prop-const", 340 "child-prop-default", 341 "child-prop-new", 342 } == set(child_binding.prop2specs.keys()) 343 propspec = child_binding.prop2specs["child-prop-1"] 344 verify_expected_propspec( 345 propspec, 346 # Amended in base_amend.yaml. 347 expect_desc="Overwritten description (child).", 348 expect_const=0xF1, 349 ) 350 propspec = child_binding.prop2specs["child-prop-2"] 351 verify_expected_propspec( 352 propspec, 353 expect_type="string", 354 # Amended in base_amend.yaml. 355 expect_desc="New description (child).", 356 expect_enum=["CHILD_EXT_FOO", "CHILD_EXT_BAR"], 357 expect_default="CHILD_EXT_FOO", 358 ) 359 propspec = child_binding.prop2specs["child-prop-enum"] 360 verify_expected_propspec( 361 propspec, 362 expect_type="string", 363 expect_enum=["CHILD_FOO", "CHILD_BAR"], 364 # Amended in base_amend.yaml. 365 expect_req=True, 366 ) 367 # Inherited from base.yaml without modification. 368 propspec = child_binding.prop2specs["child-prop-const"] 369 verify_expected_propspec(propspec, expect_const=16) 370 propspec = child_binding.prop2specs["child-prop-req"] 371 verify_expected_propspec(propspec, expect_req=True) 372 propspec = child_binding.prop2specs["child-prop-default"] 373 verify_expected_propspec(propspec, expect_default=2) 374 375 # New property in base_amend.yaml. 376 propspec = child_binding.prop2specs["child-prop-new"] 377 verify_expected_propspec(propspec) 378 379 # GrandChild-Binding level. 380 # 381 assert child_binding.child_binding 382 grandchild_binding = child_binding.child_binding 383 assert { 384 "grandchild-prop-1", 385 "grandchild-prop-2", 386 "grandchild-prop-enum", 387 "grandchild-prop-req", 388 "grandchild-prop-const", 389 "grandchild-prop-default", 390 "grandchild-prop-new", 391 } == set(grandchild_binding.prop2specs.keys()) 392 propspec = grandchild_binding.prop2specs["grandchild-prop-1"] 393 verify_expected_propspec( 394 propspec, 395 # Amended in base_amend.yaml. 396 expect_desc="Overwritten description (grandchild).", 397 expect_const=0xF2, 398 ) 399 propspec = grandchild_binding.prop2specs["grandchild-prop-2"] 400 verify_expected_propspec( 401 propspec, 402 expect_type="string", 403 # Amended in base_amend.yaml. 404 expect_desc="New description (grandchild).", 405 expect_enum=["GRANDCHILD_EXT_FOO", "GRANDCHILD_EXT_BAR"], 406 expect_default="GRANDCHILD_EXT_FOO", 407 ) 408 propspec = grandchild_binding.prop2specs["grandchild-prop-enum"] 409 verify_expected_propspec( 410 propspec, 411 expect_type="string", 412 expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], 413 # Amended in base_amend.yaml. 414 expect_req=True, 415 ) 416 # Inherited from base.yaml without modification. 417 propspec = grandchild_binding.prop2specs["grandchild-prop-const"] 418 verify_expected_propspec(propspec, expect_const=32) 419 propspec = grandchild_binding.prop2specs["grandchild-prop-req"] 420 verify_expected_propspec(propspec, expect_req=True) 421 propspec = grandchild_binding.prop2specs["grandchild-prop-default"] 422 verify_expected_propspec(propspec, expect_default=3) 423 424 # New property in base_amend.yaml. 425 propspec = grandchild_binding.prop2specs["grandchild-prop-new"] 426 verify_expected_propspec(propspec) 427 428 429def test_expect_propspecs_multi_child_binding() -> None: 430 """Test including base bindings at multiple levels. 431 432 Base specifications are included at the binding, child-binding 433 and child-binding levels: 434 435 include: base.yaml 436 child-binding: 437 include: base.yaml 438 child-binding: 439 include: base.yaml 440 441 This test checks that we finally get the expected property specifications 442 at the child-binding level. 443 """ 444 binding = child_binding_of("test-bindings-init/base_multi.yaml") 445 446 assert { 447 # From top-level "include:" element. 448 "child-prop-1", 449 "child-prop-2", 450 "child-prop-enum", 451 # From "child-binding: include:" element. 452 "prop-1", 453 "prop-2", 454 "prop-enum", 455 } == set(binding.prop2specs.keys()) 456 457 # Inherited from base.yaml without modification. 458 propspec = binding.prop2specs["child-prop-2"] 459 verify_expected_propspec(propspec, expect_type="string") 460 propspec = binding.prop2specs["child-prop-enum"] 461 verify_expected_propspec(propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"]) 462 463 propspec = binding.prop2specs["child-prop-1"] 464 verify_expected_propspec( 465 propspec, 466 expect_desc="Base child-prop 1.", 467 # Amended in base_multi.yaml. 468 expect_const=0xF1, 469 ) 470 propspec = binding.prop2specs["prop-1"] 471 verify_expected_propspec( 472 propspec, 473 expect_desc="Base property 1.", 474 # Amended in base_multi.yaml. 475 expect_const=0xF1, 476 ) 477 propspec = binding.prop2specs["prop-2"] 478 verify_expected_propspec( 479 propspec, 480 expect_type="string", 481 # Amended in base_multi.yaml. 482 expect_desc="New description (child).", 483 ) 484 propspec = binding.prop2specs["prop-enum"] 485 verify_expected_propspec( 486 propspec, 487 expect_type="string", 488 expect_enum=["FOO", "BAR"], 489 # Amended in base_multi.yaml. 490 expect_default="FOO", 491 expect_req=True, 492 ) 493 494 495def test_expect_propspecs_multi_grandchild_binding() -> None: 496 """Test including base bindings at multiple levels. 497 498 This test checks that we finally get the expected property specifications 499 at the grandchild-binding level. 500 501 See also: test_expect_propspecs_multi_child_binding() 502 """ 503 binding = grandchild_binding_of("test-bindings-init/base_multi.yaml") 504 505 assert { 506 # From top-level "include:" element. 507 "grandchild-prop-1", 508 "grandchild-prop-2", 509 "grandchild-prop-enum", 510 # From "child-binding: include:" element. 511 "child-prop-1", 512 "child-prop-2", 513 "child-prop-enum", 514 # From "child-binding: child-binding: include:" element. 515 "prop-1", 516 "prop-2", 517 "prop-enum", 518 } == set(binding.prop2specs.keys()) 519 520 # Inherited from base.yaml without modification. 521 propspec = binding.prop2specs["grandchild-prop-2"] 522 verify_expected_propspec(propspec, expect_type="string") 523 propspec = binding.prop2specs["grandchild-prop-enum"] 524 verify_expected_propspec( 525 propspec, 526 expect_type="string", 527 expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], 528 ) 529 propspec = binding.prop2specs["child-prop-2"] 530 verify_expected_propspec(propspec, expect_type="string") 531 propspec = binding.prop2specs["child-prop-enum"] 532 verify_expected_propspec(propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"]) 533 534 propspec = binding.prop2specs["grandchild-prop-1"] 535 verify_expected_propspec( 536 propspec, 537 expect_desc="Base grandchild-prop 1.", 538 # Amended in base_multi.yaml. 539 expect_const=0xF2, 540 ) 541 propspec = binding.prop2specs["child-prop-1"] 542 verify_expected_propspec( 543 propspec, 544 expect_desc="Base child-prop 1.", 545 # Amended in base_multi.yaml. 546 expect_const=0xF2, 547 ) 548 propspec = binding.prop2specs["prop-1"] 549 verify_expected_propspec( 550 propspec, 551 expect_desc="Base property 1.", 552 # Amended in base_amend.yaml. 553 expect_const=0xF2, 554 ) 555 propspec = binding.prop2specs["prop-2"] 556 verify_expected_propspec( 557 propspec, 558 expect_type="string", 559 # Amended in base_amend.yaml. 560 expect_desc="New description (grandchild).", 561 ) 562 propspec = binding.prop2specs["prop-enum"] 563 verify_expected_propspec( 564 propspec, 565 expect_type="string", 566 expect_enum=["FOO", "BAR"], 567 # Amended in base_amend.yaml. 568 expect_req=True, 569 expect_default="FOO", 570 ) 571 572 573def test_expect_propspecs_multi_grand_grandchild_binding() -> None: 574 """Test including base bindings at multiple levels. 575 576 This test checks that we finally get the expected property specifications 577 at the grand-grandchild-binding level. 578 579 See also: test_expect_propspecs_multi_child_binding() 580 """ 581 binding = grandchild_binding_of("test-bindings-init/base_multi.yaml").child_binding 582 assert binding 583 584 assert { 585 # From "child-binding: include:" element. 586 "child-prop-1", 587 "child-prop-2", 588 "child-prop-enum", 589 # From "child-binding: child-binding: include:" element. 590 "grandchild-prop-1", 591 "grandchild-prop-2", 592 "grandchild-prop-enum", 593 } == set(binding.prop2specs.keys()) 594 595 # Inherited from base.yaml without modification. 596 propspec = binding.prop2specs["child-prop-1"] 597 verify_expected_propspec(propspec, expect_desc="Base child-prop 1.") 598 propspec = binding.prop2specs["child-prop-2"] 599 verify_expected_propspec(propspec, expect_type="string") 600 propspec = binding.prop2specs["child-prop-enum"] 601 verify_expected_propspec(propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"]) 602 propspec = binding.prop2specs["grandchild-prop-1"] 603 verify_expected_propspec(propspec, expect_desc="Base grandchild-prop 1.") 604 propspec = binding.prop2specs["grandchild-prop-2"] 605 verify_expected_propspec(propspec, expect_type="string") 606 propspec = binding.prop2specs["grandchild-prop-enum"] 607 verify_expected_propspec( 608 propspec, 609 expect_type="string", 610 expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], 611 ) 612 613 614def test_expect_propspecs_diamond_binding() -> None: 615 """Test property specifications produced by diamond inheritance. 616 617 This test checks that we finally get the expected property specifications 618 at the binding level. 619 """ 620 binding = load_binding("test-bindings-init/diamond.yaml") 621 622 assert { 623 # From base.yaml, amended in base_amend.yaml (left), 624 # last modified in thing.yaml (right). 625 "prop-1", 626 # From base.yaml, amended in base_amend.yaml (left), 627 # and thing.yaml (right), last modified in diamond.yaml(top). 628 "prop-enum", 629 # From base.yaml, inherited in base_amend.yaml (left). 630 "prop-default", 631 # From thing.yaml (right). 632 "prop-thing", 633 # From diamond.yaml (top). 634 "prop-diamond", 635 } == set(binding.prop2specs.keys()) 636 637 # Inherited from base.yaml without modification. 638 propspec = binding.prop2specs["prop-default"] 639 verify_expected_propspec(propspec, expect_default=1) 640 # Inherited from thing.yaml without modification. 641 propspec = binding.prop2specs["prop-thing"] 642 verify_expected_propspec(propspec, expect_desc="Thing property.") 643 644 # New property in diamond.yaml. 645 propspec = binding.prop2specs["prop-diamond"] 646 verify_expected_propspec(propspec) 647 648 propspec = binding.prop2specs["prop-1"] 649 verify_expected_propspec( 650 propspec, 651 # From base_amend.yaml. 652 expect_const=0xF0, 653 # Included first wins. 654 expect_desc="Overwritten description.", 655 # From thing.yaml. 656 expect_default=1, 657 ) 658 propspec = binding.prop2specs["prop-enum"] 659 verify_expected_propspec( 660 propspec, 661 expect_type="string", 662 expect_enum=["FOO", "BAR"], 663 # From base_amend.yaml. 664 expect_req=True, 665 # From diamond.yaml. 666 expect_desc="Overwritten in diamond.yaml.", 667 expect_default="FOO", 668 ) 669 670 671def test_expect_propspecs_diamond_child_binding() -> None: 672 """Test property specifications produced by diamond inheritance. 673 674 This test checks that we finally get the expected property specifications 675 at the child-binding level. 676 """ 677 binding = child_binding_of("test-bindings-init/diamond.yaml") 678 679 assert { 680 # From base.yaml, amended in base_amend.yaml (left), 681 # last modified in thing.yaml (right). 682 "child-prop-1", 683 # From base.yaml, amended in base_amend.yaml (left), 684 # and thing.yaml (right), last modified in diamond.yaml(top). 685 "child-prop-enum", 686 # From base.yaml, inherited in base_amend.yaml (left). 687 "child-prop-default", 688 # From thing.yaml (right). 689 "child-prop-thing", 690 # From diamond.yaml (top). 691 "child-prop-diamond", 692 } == set(binding.prop2specs.keys()) 693 694 propspec = binding.prop2specs["child-prop-1"] 695 verify_expected_propspec( 696 propspec, 697 # From base_amend.yaml. 698 expect_const=0xF1, 699 # Included first wins. 700 expect_desc="Overwritten description (child).", 701 # From thing.yaml. 702 expect_default=2, 703 ) 704 705 propspec = binding.prop2specs["child-prop-enum"] 706 verify_expected_propspec( 707 propspec, 708 expect_type="string", 709 expect_enum=["CHILD_FOO", "CHILD_BAR"], 710 # From base_amend.yaml. 711 # ORed with thing.yaml. 712 expect_req=True, 713 # From diamond.yaml. 714 expect_default="CHILD_FOO", 715 expect_desc="Overwritten in diamond.yaml (child).", 716 ) 717 718 # Inherited from base.yaml without modification. 719 propspec = binding.prop2specs["child-prop-default"] 720 verify_expected_propspec(propspec, expect_default=2) 721 # Inherited from thing.yaml without modification. 722 propspec = binding.prop2specs["child-prop-thing"] 723 verify_expected_propspec(propspec, expect_desc="Thing child-binding property.") 724 725 # New property in diamond.yaml. 726 propspec = binding.prop2specs["child-prop-diamond"] 727 verify_expected_propspec(propspec) 728 729 730def test_expect_propspecs_diamond_grandchild_binding() -> None: 731 """Test property specifications produced by diamond inheritance. 732 733 This test checks that we finally get the expected property specifications 734 at the grandchild-binding level. 735 """ 736 binding = grandchild_binding_of("test-bindings-init/diamond.yaml") 737 738 assert { 739 # From base.yaml, amended in base_amend.yaml (left), 740 # last modified in thing.yaml (right). 741 "grandchild-prop-1", 742 # From base.yaml, amended in base_amend.yaml (left), 743 # last modified in diamond.yaml (top). 744 "grandchild-prop-enum", 745 # From base.yaml, inherited in base_amend.yaml (left). 746 "grandchild-prop-default", 747 # From thing.yaml (right). 748 "grandchild-prop-thing", 749 # From diamond.yaml (top). 750 "grandchild-prop-diamond", 751 } == set(binding.prop2specs.keys()) 752 753 propspec = binding.prop2specs["grandchild-prop-1"] 754 verify_expected_propspec( 755 propspec, 756 # From base_amend.yaml. 757 expect_const=0xF2, 758 # Included first wins. 759 expect_desc="Overwritten description (grandchild).", 760 # From thing.yaml. 761 expect_default=3, 762 ) 763 764 propspec = binding.prop2specs["grandchild-prop-enum"] 765 verify_expected_propspec( 766 propspec, 767 expect_type="string", 768 expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], 769 # From base_amend.yaml. 770 # ORed with thing.yaml. 771 expect_req=True, 772 # From diamond.yaml. 773 expect_default="GRANDCHILD_FOO", 774 expect_desc="Overwritten in diamond.yaml (grandchild).", 775 ) 776 777 # Inherited from base.yaml without modification. 778 propspec = binding.prop2specs["grandchild-prop-default"] 779 verify_expected_propspec(propspec, expect_default=3) 780 # Inherited from thing.yaml without modification. 781 propspec = binding.prop2specs["grandchild-prop-thing"] 782 verify_expected_propspec(propspec, expect_desc="Thing grandchild-binding property.") 783 784 # New property in diamond.yaml. 785 propspec = binding.prop2specs["grandchild-prop-diamond"] 786 verify_expected_propspec(propspec) 787 788 789def test_binding_description_overwrite() -> None: 790 """Test whether we can overwrite a binding's description. 791 792 Included files can't overwrite the description set by the including binding 793 (despite that "description:" appears before "include:"). 794 795 This seems consistent: the top-level binding file "wins". 796 """ 797 binding = load_binding("test-bindings-init/compat_desc.yaml") 798 assert binding.description == "Binding description." 799 800 binding = load_binding("test-bindings-init/compat_desc_multi.yaml") 801 assert binding.description == "Binding description (multi)." 802 803 804def test_binding_compat_overwrite() -> None: 805 """Test whether we can overwrite a binding's compatible string. 806 807 Included files can't overwrite the compatible string set by the 808 including binding (despite that "compatible:" appears before "include:"). 809 810 This seems consistent: the top-level binding file "wins". 811 """ 812 binding = load_binding("test-bindings-init/compat_desc.yaml") 813 assert binding.compatible == "vnd,compat-desc" 814 815 binding = load_binding("test-bindings-init/compat_desc_multi.yaml") 816 assert binding.compatible == "vnd,compat-desc-multi" 817 818 819def test_child_binding_description_overwrite() -> None: 820 """Test whether we can overwrite a child-binding's description. 821 822 An including binding can overwrite an inherited child-binding's description. 823 824 When we include multiple files overwriting the description 825 at the child-binding level, the first "wins". 826 """ 827 child_binding = child_binding_of("test-bindings-init/compat_desc.yaml") 828 # Overwrite inherited description. 829 assert child_binding.description == "Child-binding description." 830 831 child_binding = child_binding_of("test-bindings-init/compat_desc_multi.yaml") 832 # When inherited multiple times, the first "description:" wins. 833 assert child_binding.description == "Child-binding description (base)." 834 835 836def test_child_binding_compat_overwrite() -> None: 837 """Test whether we can overwrite a child-binding's compatible string. 838 839 An including binding can overwrite an inherited child-binding's 840 compatible string. 841 842 When we include multiple files overwriting the compatible string 843 at the child-binding level, the first "wins". 844 """ 845 child_binding = child_binding_of("test-bindings-init/compat_desc.yaml") 846 # Overwrite inherited description. 847 assert child_binding.compatible == "vnd,child-compat-desc" 848 849 child_binding = child_binding_of("test-bindings-init/compat_desc_multi.yaml") 850 # When inherited multiple times, the first "compatible:" wins. 851 assert child_binding.compatible == "vnd,child-compat-desc-base" 852 853 854def test_grandchild_binding_description_overwrite() -> None: 855 """Test whether we can overwrite a grandchild-binding's description. 856 857 See also: test_child_binding_description_overwrite() 858 """ 859 grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc.yaml") 860 assert grandchild_binding.description == "Grandchild-binding description." 861 862 grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc_multi.yaml") 863 assert grandchild_binding.description == "Grandchild-binding description (base)." 864 865 866def test_grandchild_binding_compat_overwrite() -> None: 867 """Test whether we can overwrite a grandchild-binding's compatible string. 868 869 See also: test_child_binding_compat_overwrite() 870 """ 871 grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc.yaml") 872 # Overwrite inherited description. 873 assert grandchild_binding.compatible == "vnd,grandchild-compat-desc" 874 875 grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc_multi.yaml") 876 # When inherited multiple times, the first "compatible:" wins. 877 assert grandchild_binding.compatible == "vnd,grandchild-compat-desc-base" 878 879 880def test_filter_inherited_propspecs_basics() -> None: 881 """Test the basics of filtering properties inherited via an intermediary 882 binding file. 883 884 Use-case "B filters I includes X": 885 - X is a base binding file, specifying common properties 886 - I is an intermediary binding file, which includes X without modification 887 nor filter 888 - B includes I, filtering the properties it chooses to inherit 889 with an allowlist or a blocklist 890 891 Checks, up to the grandchild-binding level, that the properties inherited 892 from X via I are actually filtered as B intends to. 893 """ 894 # Binding level. 895 binding = load_binding("test-bindings-init/simple_allowlist.yaml") 896 # Allowed properties. 897 assert {"prop-1", "prop-2"} == set(binding.prop2specs.keys()) 898 binding = load_binding("test-bindings-init/simple_blocklist.yaml") 899 # Non blocked properties. 900 assert {"prop-2", "prop-3"} == set(binding.prop2specs.keys()) 901 902 # Child-binding level. 903 child_binding = child_binding_of("test-bindings-init/simple_allowlist.yaml") 904 # Allowed properties. 905 assert {"child-prop-1", "child-prop-2"} == set(child_binding.prop2specs.keys()) 906 child_binding = child_binding_of("test-bindings-init/simple_blocklist.yaml") 907 # Non blocked properties. 908 assert {"child-prop-2", "child-prop-3"} == set(child_binding.prop2specs.keys()) 909 910 # GrandChild-binding level. 911 grandchild_binding = grandchild_binding_of("test-bindings-init/simple_allowlist.yaml") 912 # Allowed properties. 913 assert {"grandchild-prop-1", "grandchild-prop-2"} == set(grandchild_binding.prop2specs.keys()) 914 grandchild_binding = grandchild_binding_of("test-bindings-init/simple_blocklist.yaml") 915 # Non blocked properties. 916 assert {"grandchild-prop-2", "grandchild-prop-3"} == set(grandchild_binding.prop2specs.keys()) 917 918 919def test_filter_inherited_propspecs_among_allowed() -> None: 920 """Test filtering properties which have been allowed by an intermediary 921 binding file. 922 923 Complementary to test_filter_inherited_propspecs_basics(). 924 925 Use-case "B filters I filters X": 926 - X is a base binding file, specifying common properties 927 - I is an intermediary binding file, filtering the properties specified 928 in X with an allowlist 929 - B includes I, filtering the properties it chooses to inherit 930 also with an allowlist 931 932 Checks, up to the grandchild-binding level, that B inherits the properties 933 specified in X which are first allowed in I, then also allowed in B. 934 935 For that, we check that if B allows only properties that are not allowed in I, 936 we then end up with no property at all. 937 """ 938 binding = load_binding("test-bindings-init/filter_among_allowed.yaml") 939 assert not set(binding.prop2specs.keys()) 940 assert binding.child_binding 941 child_binding = binding.child_binding 942 assert not set(child_binding.prop2specs.keys()) 943 assert child_binding.child_binding 944 grandchild_binding = child_binding.child_binding 945 assert not set(grandchild_binding.prop2specs.keys()) 946 947 948def test_filter_inherited_propspecs_among_notblocked() -> None: 949 """Test filtering properties which have not been blocked by an intermediary 950 binding file. 951 952 Complementary to test_filter_inherited_propspecs_basics(). 953 954 Use-case "B filters I filters X": 955 - X is a base binding file, specifying common properties 956 - I is an intermediary binding file, filtering the properties specified 957 in X with a blocklist 958 - B includes I, filtering the properties it chooses to inherit 959 also with a blocklist 960 961 Checks, up to the grandchild-binding level, that B inherits the properties 962 specified in X which are not blocked in I, then neither blocked in B. 963 964 For that, we check that if B blocks all properties that are not blocked in I, 965 we then end up with no property at all. 966 """ 967 binding = load_binding("test-bindings-init/filter_among_notblocked.yaml") 968 assert not set(binding.prop2specs.keys()) 969 assert binding.child_binding 970 child_binding = binding.child_binding 971 assert not set(child_binding.prop2specs.keys()) 972 assert child_binding.child_binding 973 grandchild_binding = child_binding.child_binding 974 assert not set(grandchild_binding.prop2specs.keys()) 975 976 977def test_filter_inherited_propspecs_allows_notblocked() -> None: 978 """Test allowing properties which have not been blocked by an intermediary 979 binding file. 980 981 Complementary to test_filter_inherited_propspecs_basics(). 982 983 Use-case "B filters I filters X": 984 - X is a base binding file, specifying common properties 985 - I is an intermediary binding file, filtering the properties specified 986 in X with a blocklist 987 - B includes I, filtering the properties it chooses to inherit 988 also with an allowlist 989 990 Checks, up to the grandchild-binding level, that B inherits the properties 991 specified in X which are not blocked in I, then allowed in B. 992 993 Permits to verify we can apply both "property-allowlist" 994 and "property-blocklist" filters to the same file, 995 as long as they're not applied simultaneously 996 (i.e. within the same YAML "include:" map). 997 """ 998 binding = load_binding("test-bindings-init/filter_allows_notblocked.yaml") 999 assert {"prop-2"} == set(binding.prop2specs.keys()) 1000 assert binding.child_binding 1001 child_binding = binding.child_binding 1002 assert {"child-prop-2"} == set(child_binding.prop2specs.keys()) 1003 assert child_binding.child_binding 1004 grandchild_binding = child_binding.child_binding 1005 assert {"grandchild-prop-2"} == set(grandchild_binding.prop2specs.keys()) 1006 1007 1008def test_invalid_binding_type_override() -> None: 1009 """An including binding should not try to override the "type:" 1010 of an inherited property. 1011 1012 Tested up to the grandchild-binding level. 1013 """ 1014 with pytest.raises(edtlib.EDTError) as e: 1015 load_binding("test-bindings-init/invalid_proptype.yaml") 1016 assert "prop-1" in str(e) 1017 assert "'int' replaced with 'string'" in str(e) 1018 1019 with pytest.raises(edtlib.EDTError) as e: 1020 load_binding("test-bindings-init/invalid_child_proptype.yaml") 1021 assert "child-prop-1" in str(e) 1022 assert "'int' replaced with 'string'" in str(e) 1023 1024 with pytest.raises(edtlib.EDTError) as e: 1025 load_binding("test-bindings-init/invalid_grandchild_proptype.yaml") 1026 assert "grandchild-prop-1" in str(e) 1027 assert "'int' replaced with 'string'" in str(e) 1028 1029 1030def test_invalid_binding_const_override() -> None: 1031 """An including binding should not try to override the "const:" value 1032 in a property specification inherited from an included file. 1033 1034 Tested up to the grandchild-binding level. 1035 """ 1036 with pytest.raises(edtlib.EDTError) as e: 1037 load_binding("test-bindings-init/invalid_propconst.yaml") 1038 assert "prop-const" in str(e) 1039 assert "'8' replaced with '999'" in str(e) 1040 1041 with pytest.raises(edtlib.EDTError) as e: 1042 load_binding("test-bindings-init/invalid_child_propconst.yaml") 1043 assert "child-prop-const" in str(e) 1044 assert "'16' replaced with '999'" in str(e) 1045 1046 with pytest.raises(edtlib.EDTError) as e: 1047 load_binding("test-bindings-init/invalid_grandchild_propconst.yaml") 1048 assert "grandchild-prop-const" in str(e) 1049 assert "'32' replaced with '999'" in str(e) 1050 1051 1052def test_invalid_binding_required_override() -> None: 1053 """An including binding should not try to override "required: true" 1054 in a property specification inherited from an included file. 1055 1056 Tested up to the grandchild-binding level. 1057 """ 1058 with pytest.raises(edtlib.EDTError) as e: 1059 load_binding("test-bindings-init/invalid_propreq.yaml") 1060 assert "prop-req" in str(e) 1061 assert "'True' replaced with 'False'" in str(e) 1062 1063 with pytest.raises(edtlib.EDTError) as e: 1064 load_binding("test-bindings-init/invalid_child_propreq.yaml") 1065 assert "child-prop-req" in str(e) 1066 assert "'True' replaced with 'False'" in str(e) 1067 1068 with pytest.raises(edtlib.EDTError) as e: 1069 load_binding("test-bindings-init/invalid_grandchild_propreq.yaml") 1070 assert "grandchild-prop-req" in str(e) 1071 assert "'True' replaced with 'False'" in str(e) 1072 1073 1074def test_invalid_binding_default_override() -> None: 1075 """An including binding should not try to override the "default:" value 1076 in a property specification inherited from an included file. 1077 1078 Tested up to the grandchild-binding level. 1079 """ 1080 with pytest.raises(edtlib.EDTError) as e: 1081 load_binding("test-bindings-init/invalid_propdefault.yaml") 1082 assert "prop-default" in str(e) 1083 assert "'1' replaced with '999'" in str(e) 1084 1085 with pytest.raises(edtlib.EDTError) as e: 1086 load_binding("test-bindings-init/invalid_child_propdefault.yaml") 1087 assert "child-prop-default" in str(e) 1088 assert "'2' replaced with '999'" in str(e) 1089 1090 with pytest.raises(edtlib.EDTError) as e: 1091 load_binding("test-bindings-init/invalid_grandchild_propdefault.yaml") 1092 assert "grandchild-prop-default" in str(e) 1093 assert "'3' replaced with '999'" in str(e) 1094 1095 1096def test_invalid_binding_enum_override() -> None: 1097 """An including binding should not try to override the "enum:" values 1098 in a property specification inherited from an included file. 1099 1100 Tested up to the grandchild-binding level. 1101 """ 1102 with pytest.raises(edtlib.EDTError) as e: 1103 load_binding("test-bindings-init/invalid_propenum.yaml") 1104 assert "prop-enum" in str(e) 1105 assert "'['FOO', 'BAR']' replaced with '['OTHER_FOO', 'OTHER_BAR']'" in str(e) 1106 1107 with pytest.raises(edtlib.EDTError) as e: 1108 load_binding("test-bindings-init/invalid_child_propenum.yaml") 1109 assert "child-prop-enum" in str(e) 1110 assert "'['CHILD_FOO', 'CHILD_BAR']' replaced with '['OTHER_FOO', 'OTHER_BAR']'" in str(e) 1111 1112 with pytest.raises(edtlib.EDTError) as e: 1113 load_binding("test-bindings-init/invalid_grandchild_propenum.yaml") 1114 assert "grandchild-prop-enum" in str(e) 1115 assert ( 1116 "'['GRANDCHILD_FOO', 'GRANDCHILD_BAR']' replaced with '['OTHER_FOO', 'OTHER_BAR']'" 1117 in str(e) 1118 ) 1119 1120 1121def test_bindings_propspecs_consistency() -> None: 1122 """Verify property specifications consistency. 1123 1124 Consistency is recursively checked for all defined properties, 1125 from top-level binding files down to their child bindings. 1126 1127 Consistency is checked with: 1128 Binding.raw["properties"][prop] == Binding.prop2specs[prop]._raw 1129 1130 See verify_binding_propspecs_consistency(). 1131 """ 1132 binding = load_binding("test-bindings-init/base_inherit.yaml") 1133 verify_binding_propspecs_consistency(binding) 1134 1135 binding = load_binding("test-bindings-init/base_amend.yaml") 1136 verify_binding_propspecs_consistency(binding) 1137 1138 binding = load_binding("test-bindings-init/base_multi.yaml") 1139 verify_binding_propspecs_consistency(binding) 1140 1141 binding = load_binding("test-bindings-init/thing.yaml") 1142 verify_binding_propspecs_consistency(binding) 1143 1144 binding = load_binding("test-bindings-init/diamond.yaml") 1145 verify_binding_propspecs_consistency(binding) 1146 1147 1148# Borrowed from test_edtlib.py. 1149@contextlib.contextmanager 1150def _from_here() -> Generator[None, None, None]: 1151 cwd = os.getcwd() 1152 try: 1153 os.chdir(os.path.dirname(__file__)) 1154 yield 1155 finally: 1156 os.chdir(cwd) 1157 1158 1159def _basename(path: str | None) -> str: 1160 return os.path.basename(path or "?") 1161