1# Copyright (c) 2019, Nordic Semiconductor 2# SPDX-License-Identifier: BSD-3-Clause 3 4import contextlib 5import os 6import re 7import tempfile 8from copy import deepcopy 9from typing import Optional 10 11import pytest 12 13from devicetree import dtlib 14 15# Test suite for dtlib.py. 16# 17# Run it using pytest (https://docs.pytest.org/en/stable/usage.html): 18# 19# $ pytest tests/test_dtlib.py 20# 21# Extra options you can pass to pytest for debugging: 22# 23# - to stop on the first failure with shorter traceback output, 24# use '-x --tb=native' 25# - to drop into a debugger on failure, use '--pdb' 26# - to run a particular test function or functions, use 27# '-k test_function_pattern_goes_here' 28 29def parse(dts, include_path=(), **kwargs): 30 '''Parse a DTS string 'dts', using the given include path. 31 32 Any kwargs are passed on to DT().''' 33 34 fd, path = tempfile.mkstemp(prefix='pytest-', suffix='.dts') 35 try: 36 os.write(fd, dts.encode('utf-8')) 37 return dtlib.DT(path, include_path, **kwargs) 38 finally: 39 os.close(fd) 40 os.unlink(path) 41 42def verify_parse(dts, expected, include_path=()): 43 '''Like parse(), but also verifies that the parsed DT object's string 44 representation is expected[1:-1]. 45 46 The [1:] is so that the first line can be put on a separate line 47 after triple quotes, as is done below.''' 48 49 dt = parse(dts[1:], include_path) 50 51 actual = str(dt) 52 expected = expected[1:-1] 53 assert actual == expected, f'unexpected round-trip on {dts}' 54 55 return dt 56 57def verify_error(dts, expected_msg): 58 '''Verify that parsing 'dts' results in a DTError with the 59 given error message 'msg'. The message must match exactly.''' 60 61 with dtlib_raises(expected_msg): 62 parse(dts[1:]) 63 64def verify_error_endswith(dts, expected_msg): 65 ''' 66 Like verify_error(), but checks the message ends with 67 'expected_msg' instead of checking for strict equality. 68 ''' 69 70 with dtlib_raises(err_endswith=expected_msg): 71 parse(dts[1:]) 72 73def verify_error_matches(dts, expected_re): 74 ''' 75 Like verify_error(), but checks the message fully matches regular 76 expression 'expected_re' instead of checking for strict equality. 77 ''' 78 79 with dtlib_raises(err_matches=expected_re): 80 parse(dts[1:]) 81 82@contextlib.contextmanager 83def temporary_chdir(dirname): 84 '''A context manager that changes directory to 'dirname'. 85 86 The current working directory is unconditionally returned to its 87 present location after the context manager exits. 88 ''' 89 here = os.getcwd() 90 try: 91 os.chdir(dirname) 92 yield 93 finally: 94 os.chdir(here) 95 96@contextlib.contextmanager 97def dtlib_raises(err: Optional[str] = None, 98 err_endswith: Optional[str] = None, 99 err_matches: Optional[str] = None): 100 '''A context manager for running a block of code that should raise 101 DTError. Exactly one of the arguments 'err', 'err_endswith', 102 and 'err_matches' must be given. The semantics are: 103 104 - err: error message must be exactly this 105 - err_endswith: error message must end with this 106 - err_matches: error message must match this regular expression 107 ''' 108 109 assert sum([bool(err), bool(err_endswith), bool(err_matches)]) == 1 110 111 with pytest.raises(dtlib.DTError) as e: 112 yield 113 114 actual_err = str(e.value) 115 if err: 116 assert actual_err == err 117 elif err_endswith: 118 assert actual_err.endswith(err_endswith) 119 else: 120 assert re.fullmatch(err_matches, actual_err), \ 121 f'actual message:\n{actual_err!r}\n' \ 122 f'does not match:\n{err_matches!r}' 123 124def test_invalid_nodenames(): 125 # Regression test that verifies node names are not matched against 126 # the more permissive set of rules used for property names. 127 128 verify_error_endswith(""" 129/dts-v1/; 130/ { node? {}; }; 131""", 132 "/node?: bad character '?' in node name") 133 134def test_cell_parsing(): 135 '''Miscellaneous properties containing zero or more cells''' 136 137 verify_parse(""" 138/dts-v1/; 139 140/ { 141 a; 142 b = < >; 143 c = [ ]; 144 d = < 10 20 >; 145 e = < 0U 1L 2UL 3LL 4ULL >; 146 f = < 0x10 0x20 >; 147 g = < 010 020 >; 148 h = /bits/ 8 < 0x10 0x20 (-1) >; 149 i = /bits/ 16 < 0x10 0x20 (-1) >; 150 j = /bits/ 32 < 0x10 0x20 (-1) >; 151 k = /bits/ 64 < 0x10 0x20 (-1) >; 152 l = < 'a' 'b' 'c' >; 153}; 154""", 155""" 156/dts-v1/; 157 158/ { 159 a; 160 b; 161 c; 162 d = < 0xa 0x14 >; 163 e = < 0x0 0x1 0x2 0x3 0x4 >; 164 f = < 0x10 0x20 >; 165 g = < 0x8 0x10 >; 166 h = [ 10 20 FF ]; 167 i = /bits/ 16 < 0x10 0x20 0xffff >; 168 j = < 0x10 0x20 0xffffffff >; 169 k = /bits/ 64 < 0x10 0x20 0xffffffffffffffff >; 170 l = < 0x61 0x62 0x63 >; 171}; 172""") 173 174 verify_error_endswith(""" 175/dts-v1/; 176 177/ { 178 a = /bits/ 16 < 0x10000 >; 179}; 180""", 181":4 (column 18): parse error: 65536 does not fit in 16 bits") 182 183 verify_error_endswith(""" 184/dts-v1/; 185 186/ { 187 a = < 0x100000000 >; 188}; 189""", 190":4 (column 8): parse error: 4294967296 does not fit in 32 bits") 191 192 verify_error_endswith(""" 193/dts-v1/; 194 195/ { 196 a = /bits/ 128 < 0 >; 197}; 198""", 199":4 (column 13): parse error: expected 8, 16, 32, or 64") 200 201def test_bytes_parsing(): 202 '''Properties with byte array values''' 203 204 verify_parse(""" 205/dts-v1/; 206 207/ { 208 a = [ ]; 209 b = [ 12 34 ]; 210 c = [ 1234 ]; 211}; 212""", 213""" 214/dts-v1/; 215 216/ { 217 a; 218 b = [ 12 34 ]; 219 c = [ 12 34 ]; 220}; 221""") 222 223 verify_error_endswith(""" 224/dts-v1/; 225 226/ { 227 a = [ 123 ]; 228}; 229""", 230":4 (column 10): parse error: expected two-digit byte or ']'") 231 232def test_string_parsing(): 233 '''Properties with string values''' 234 235 verify_parse(r""" 236/dts-v1/; 237 238/ { 239 a = ""; 240 b = "ABC"; 241 c = "\\\"\xab\377\a\b\t\n\v\f\r"; 242}; 243""", 244r""" 245/dts-v1/; 246 247/ { 248 a = ""; 249 b = "ABC"; 250 c = "\\\"\xab\xff\a\b\t\n\v\f\r"; 251}; 252""") 253 254 verify_error_endswith(r""" 255/dts-v1/; 256 257/ { 258 a = "\400"; 259}; 260""", 261":4 (column 6): parse error: octal escape out of range (> 255)") 262 263def test_char_literal_parsing(): 264 '''Properties with character literal values''' 265 266 verify_parse(r""" 267/dts-v1/; 268 269/ { 270 a = < '\'' >; 271 b = < '\x12' >; 272}; 273""", 274""" 275/dts-v1/; 276 277/ { 278 a = < 0x27 >; 279 b = < 0x12 >; 280}; 281""") 282 283 verify_error_endswith(""" 284/dts-v1/; 285 286/ { 287 // Character literals are not allowed at the top level 288 a = 'x'; 289}; 290""", 291":5 (column 6): parse error: malformed value") 292 293 verify_error_endswith(""" 294/dts-v1/; 295 296/ { 297 a = < '' >; 298}; 299""", 300":4 (column 7): parse error: character literals must be length 1") 301 302 verify_error_endswith(""" 303/dts-v1/; 304 305/ { 306 a = < '12' >; 307}; 308""", 309":4 (column 7): parse error: character literals must be length 1") 310 311def test_incbin(tmp_path): 312 '''Test /incbin/, an undocumented feature that allows for 313 binary file inclusion. 314 315 https://github.com/dgibson/dtc/commit/e37ec7d5889fa04047daaa7a4ff55150ed7954d4''' 316 317 open(tmp_path / "tmp_bin", "wb").write(b"\00\01\02\03") 318 319 verify_parse(f""" 320/dts-v1/; 321 322/ {{ 323 a = /incbin/ ("{tmp_path}/tmp_bin"); 324 b = /incbin/ ("{tmp_path}/tmp_bin", 1, 1); 325 c = /incbin/ ("{tmp_path}/tmp_bin", 1, 2); 326}}; 327""", 328""" 329/dts-v1/; 330 331/ { 332 a = [ 00 01 02 03 ]; 333 b = [ 01 ]; 334 c = [ 01 02 ]; 335}; 336""") 337 338 verify_parse(""" 339/dts-v1/; 340 341/ { 342 a = /incbin/ ("tmp_bin"); 343}; 344""", 345""" 346/dts-v1/; 347 348/ { 349 a = [ 00 01 02 03 ]; 350}; 351""", 352 include_path=(tmp_path,)) 353 354 verify_error_endswith(r""" 355/dts-v1/; 356 357/ { 358 a = /incbin/ ("missing"); 359}; 360""", 361":4 (column 25): parse error: 'missing' could not be found") 362 363def test_node_merging(): 364 ''' 365 Labels and properties specified for the same node in different 366 statements should be merged. 367 ''' 368 369 verify_parse(""" 370/dts-v1/; 371 372/ { 373 l1: l2: l1: foo { 374 foo1 = [ 01 ]; 375 l4: l5: bar { 376 bar1 = [ 01 ]; 377 }; 378 }; 379}; 380 381l3: &l1 { 382 foo2 = [ 02 ]; 383 l6: l7: bar { 384 bar2 = [ 02 ]; 385 }; 386}; 387 388&l3 { 389 foo3 = [ 03 ]; 390}; 391 392&{/foo} { 393 foo4 = [ 04 ]; 394}; 395 396&{/foo/bar} { 397 bar3 = [ 03 ]; 398 l8: baz {}; 399}; 400 401/ { 402}; 403 404/ { 405 top = [ 01 ]; 406}; 407""", 408""" 409/dts-v1/; 410 411/ { 412 top = [ 01 ]; 413 l1: l2: l3: foo { 414 foo1 = [ 01 ]; 415 foo2 = [ 02 ]; 416 foo3 = [ 03 ]; 417 foo4 = [ 04 ]; 418 l4: l5: l6: l7: bar { 419 bar1 = [ 01 ]; 420 bar2 = [ 02 ]; 421 bar3 = [ 03 ]; 422 l8: baz { 423 }; 424 }; 425 }; 426}; 427""") 428 429 verify_error_endswith(""" 430/dts-v1/; 431 432/ { 433}; 434 435&missing { 436}; 437""", 438":6 (column 1): parse error: undefined node label 'missing'") 439 440 verify_error_endswith(""" 441/dts-v1/; 442 443/ { 444}; 445 446&{foo} { 447}; 448""", 449":6 (column 1): parse error: node path 'foo' does not start with '/'") 450 451 verify_error_endswith(""" 452/dts-v1/; 453 454/ { 455}; 456 457&{/foo} { 458}; 459""", 460":6 (column 1): parse error: component 'foo' in path '/foo' does not exist") 461 462def test_property_labels(): 463 '''Like nodes, properties can have labels too.''' 464 465 def verify_label2prop(label, expected): 466 actual = dt.label2prop[label].name 467 assert actual == expected, f"label '{label}' mapped to wrong property" 468 469 dt = verify_parse(""" 470/dts-v1/; 471 472/ { 473 a; 474 b; 475 l2: c; 476 l4: l5: l5: l4: d = < 0 >; 477}; 478 479/ { 480 l1: b; 481 l3: c; 482 l6: d; 483}; 484""", 485""" 486/dts-v1/; 487 488/ { 489 a; 490 l1: b; 491 l2: l3: c; 492 l4: l5: l6: d = < 0x0 >; 493}; 494""") 495 496 verify_label2prop("l1", "b") 497 verify_label2prop("l2", "c") 498 verify_label2prop("l3", "c") 499 verify_label2prop("l4", "d") 500 verify_label2prop("l5", "d") 501 verify_label2prop("l6", "d") 502 503def test_property_offset_labels(): 504 ''' 505 It's possible to give labels to data at nonnegative byte offsets 506 within a property value. 507 ''' 508 509 def verify_label2offset(label, expected_prop, expected_offset): 510 actual_prop, actual_offset = dt.label2prop_offset[label] 511 actual_prop = actual_prop.name 512 assert (actual_prop, actual_offset) == \ 513 (expected_prop, expected_offset), \ 514 f"label '{label}' maps to wrong offset or property" 515 516 dt = verify_parse(""" 517/dts-v1/; 518 519/ { 520 a = l01: l02: < l03: &node l04: l05: 2 l06: >, 521 l07: l08: [ l09: 03 l10: l11: 04 l12: l13: ] l14:, "A"; 522 523 b = < 0 > l23: l24:; 524 525 node: node { 526 }; 527}; 528""", 529""" 530/dts-v1/; 531 532/ { 533 a = l01: l02: < l03: &node l04: l05: 0x2 l06: l07: l08: >, [ l09: 03 l10: l11: 04 l12: l13: l14: ], "A"; 534 b = < 0x0 l23: l24: >; 535 node: node { 536 phandle = < 0x1 >; 537 }; 538}; 539""") 540 541 verify_label2offset("l01", "a", 0) 542 verify_label2offset("l02", "a", 0) 543 verify_label2offset("l04", "a", 4) 544 verify_label2offset("l05", "a", 4) 545 verify_label2offset("l06", "a", 8) 546 verify_label2offset("l09", "a", 8) 547 verify_label2offset("l10", "a", 9) 548 549 verify_label2offset("l23", "b", 4) 550 verify_label2offset("l24", "b", 4) 551 552def test_unit_addr(): 553 '''Node unit addresses must be correctly extracted from their names.''' 554 555 def verify_unit_addr(path, expected): 556 node = dt.get_node(path) 557 assert node.unit_addr == expected, \ 558 f"{node!r} has unexpected unit address" 559 560 dt = verify_parse(""" 561/dts-v1/; 562 563/ { 564 no-unit-addr { 565 }; 566 567 unit-addr@ABC { 568 }; 569 570 unit-addr-non-numeric@foo-bar { 571 }; 572}; 573""", 574""" 575/dts-v1/; 576 577/ { 578 no-unit-addr { 579 }; 580 unit-addr@ABC { 581 }; 582 unit-addr-non-numeric@foo-bar { 583 }; 584}; 585""") 586 587 verify_unit_addr("/no-unit-addr", "") 588 verify_unit_addr("/unit-addr@ABC", "ABC") 589 verify_unit_addr("/unit-addr-non-numeric@foo-bar", "foo-bar") 590 591def test_node_path_references(): 592 '''Node phandles may be specified using a reference to the node's path.''' 593 594 verify_parse(""" 595/dts-v1/; 596 597/ { 598 a = &label; 599 b = [ 01 ], &label; 600 c = [ 01 ], &label, <2>; 601 d = &{/abc}; 602 label: abc { 603 e = &label; 604 f = &{/abc}; 605 }; 606}; 607""", 608""" 609/dts-v1/; 610 611/ { 612 a = &label; 613 b = [ 01 ], &label; 614 c = [ 01 ], &label, < 0x2 >; 615 d = &{/abc}; 616 label: abc { 617 e = &label; 618 f = &{/abc}; 619 }; 620}; 621""") 622 623 verify_error(""" 624/dts-v1/; 625 626/ { 627 sub { 628 x = &missing; 629 }; 630}; 631""", 632"/sub: undefined node label 'missing'") 633 634 verify_error(""" 635/dts-v1/; 636 637/ { 638 sub { 639 x = &{/sub/missing}; 640 }; 641}; 642""", 643"/sub: component 'missing' in path '/sub/missing' does not exist") 644 645def test_phandles(): 646 '''Various tests related to phandles.''' 647 648 verify_parse(""" 649/dts-v1/; 650 651/ { 652 x = < &a &{/b} &c >; 653 654 dummy1 { 655 phandle = < 1 >; 656 }; 657 658 dummy2 { 659 phandle = < 3 >; 660 }; 661 662 a: a { 663 }; 664 665 b { 666 }; 667 668 c: c { 669 phandle = < 0xFF >; 670 }; 671}; 672""", 673""" 674/dts-v1/; 675 676/ { 677 x = < &a &{/b} &c >; 678 dummy1 { 679 phandle = < 0x1 >; 680 }; 681 dummy2 { 682 phandle = < 0x3 >; 683 }; 684 a: a { 685 phandle = < 0x2 >; 686 }; 687 b { 688 phandle = < 0x4 >; 689 }; 690 c: c { 691 phandle = < 0xff >; 692 }; 693}; 694""") 695 696 # Check that a node can be assigned a phandle to itself. This just forces a 697 # phandle to be allocated on it. The C tools support this too. 698 verify_parse(""" 699/dts-v1/; 700 701/ { 702 dummy { 703 phandle = < 1 >; 704 }; 705 706 a { 707 foo: phandle = < &{/a} >; 708 }; 709 710 label: b { 711 bar: phandle = < &label >; 712 }; 713}; 714""", 715""" 716/dts-v1/; 717 718/ { 719 dummy { 720 phandle = < 0x1 >; 721 }; 722 a { 723 foo: phandle = < &{/a} >; 724 }; 725 label: b { 726 bar: phandle = < &label >; 727 }; 728}; 729""") 730 731 verify_error(""" 732/dts-v1/; 733 734/ { 735 sub { 736 x = < &missing >; 737 }; 738}; 739""", 740"/sub: undefined node label 'missing'") 741 742 verify_error_endswith(""" 743/dts-v1/; 744 745/ { 746 a: sub { 747 x = /bits/ 16 < &a >; 748 }; 749}; 750""", 751":5 (column 19): parse error: phandle references are only allowed in arrays with 32-bit elements") 752 753 verify_error(""" 754/dts-v1/; 755 756/ { 757 foo { 758 phandle = [ 00 ]; 759 }; 760}; 761""", 762"/foo: bad phandle length (1), expected 4 bytes") 763 764 verify_error(""" 765/dts-v1/; 766 767/ { 768 foo { 769 phandle = < 0 >; 770 }; 771}; 772""", 773"/foo: bad value 0x00000000 for phandle") 774 775 verify_error(""" 776/dts-v1/; 777 778/ { 779 foo { 780 phandle = < (-1) >; 781 }; 782}; 783""", 784"/foo: bad value 0xffffffff for phandle") 785 786 verify_error(""" 787/dts-v1/; 788 789/ { 790 foo { 791 phandle = < 17 >; 792 }; 793 794 bar { 795 phandle = < 17 >; 796 }; 797}; 798""", 799"/bar: duplicated phandle 0x11 (seen before at /foo)") 800 801 verify_error(""" 802/dts-v1/; 803 804/ { 805 foo { 806 phandle = < &{/bar} >; 807 }; 808 809 bar { 810 }; 811}; 812""", 813"/foo: phandle refers to another node") 814 815def test_phandle2node(): 816 '''Test the phandle2node dict in a dt instance.''' 817 818 def verify_phandle2node(prop, offset, expected_name): 819 phandle = dtlib.to_num(dt.root.props[prop].value[offset:offset + 4]) 820 actual_name = dt.phandle2node[phandle].name 821 822 assert actual_name == expected_name, \ 823 f"'{prop}' is a phandle for the wrong thing" 824 825 dt = parse(""" 826/dts-v1/; 827 828/ { 829 phandle_ = < &{/node1} 0 1 >; 830 phandles = < 0 &{/node2} 1 &{/node3} >; 831 832 node1 { 833 phandle = < 123 >; 834 }; 835 836 node2 { 837 }; 838 839 node3 { 840 }; 841}; 842""") 843 844 verify_phandle2node("phandle_", 0, "node1") 845 verify_phandle2node("phandles", 4, "node2") 846 verify_phandle2node("phandles", 12, "node3") 847 848def test_mixed_assign(): 849 '''Test mixed value type assignments''' 850 851 verify_parse(""" 852/dts-v1/; 853 854/ { 855 x = /bits/ 8 < 0xFF 0xFF >, 856 &abc, 857 < 0xFF &abc 0xFF &abc >, 858 &abc, 859 [ FF FF ], 860 "abc"; 861 862 abc: abc { 863 }; 864}; 865""", 866""" 867/dts-v1/; 868 869/ { 870 x = [ FF FF ], &abc, < 0xff &abc 0xff &abc >, &abc, [ FF FF ], "abc"; 871 abc: abc { 872 phandle = < 0x1 >; 873 }; 874}; 875""") 876 877def test_deletion(): 878 '''Properties and nodes may be deleted from the tree.''' 879 880 # Test property deletion 881 882 verify_parse(""" 883/dts-v1/; 884 885/ { 886 keep = < 1 >; 887 delete = < &sub >, ⊂ 888 /delete-property/ missing; 889 /delete-property/ delete; 890 sub: sub { 891 y = < &sub >, ⊂ 892 }; 893}; 894 895&sub { 896 /delete-property/ y; 897}; 898""", 899""" 900/dts-v1/; 901 902/ { 903 keep = < 0x1 >; 904 sub: sub { 905 }; 906}; 907""") 908 909 # Test node deletion 910 911 verify_parse(""" 912/dts-v1/; 913 914/ { 915 x = "foo"; 916 sub0 { 917 x = "bar"; 918 }; 919}; 920 921/delete-node/ &{/}; 922 923/ { 924 sub1 { 925 x = < 1 >; 926 sub2 { 927 x = < &sub >, ⊂ 928 }; 929 /delete-node/ sub2; 930 }; 931 932 sub3: sub3 { 933 x = < &sub >, ⊂ 934 }; 935 936 sub4 { 937 x = < &sub >, ⊂ 938 }; 939}; 940 941/delete-node/ &sub3; 942/delete-node/ &{/sub4}; 943""", 944""" 945/dts-v1/; 946 947/ { 948 sub1 { 949 x = < 0x1 >; 950 }; 951}; 952""") 953 954 verify_parse(""" 955/dts-v1/; 956 957/ { 958 x: x = < &sub >, ⊂ 959 960 sub1 { 961 x = < &sub >, ⊂ 962 }; 963 sub2: sub2 { 964 x = < &sub >, ⊂ 965 }; 966}; 967 968/delete-node/ &{/}; 969""", 970""" 971/dts-v1/; 972 973/ { 974}; 975""") 976 977 verify_error_endswith(""" 978/dts-v1/; 979 980/ { 981}; 982 983/delete-node/ &missing; 984""", 985":6 (column 15): parse error: undefined node label 'missing'") 986 987 verify_error_endswith(""" 988/dts-v1/; 989 990/delete-node/ { 991""", 992":3 (column 15): parse error: expected label (&foo) or path (&{/foo/bar}) reference") 993 994def test_include_curdir(tmp_path): 995 '''Verify that /include/ (which is handled in the lexer) searches the 996 current directory''' 997 998 with temporary_chdir(tmp_path): 999 with open("same-dir-1", "w") as f: 1000 f.write(""" 1001 x = [ 00 ]; 1002 /include/ "same-dir-2" 1003""") 1004 with open("same-dir-2", "w") as f: 1005 f.write(""" 1006 y = [ 01 ]; 1007 /include/ "same-dir-3" 1008""") 1009 with open("same-dir-3", "w") as f: 1010 f.write(""" 1011 z = [ 02 ]; 1012""") 1013 with open("test.dts", "w") as f: 1014 f.write(""" 1015/dts-v1/; 1016 1017/ { 1018 /include/ "same-dir-1" 1019}; 1020""") 1021 dt = dtlib.DT("test.dts") 1022 assert str(dt) == """ 1023/dts-v1/; 1024 1025/ { 1026 x = [ 00 ]; 1027 y = [ 01 ]; 1028 z = [ 02 ]; 1029}; 1030"""[1:-1] 1031 1032def test_include_is_lexical(tmp_path): 1033 '''/include/ is done in the lexer, which means that property 1034 definitions can span multiple included files in different 1035 directories.''' 1036 1037 with open(tmp_path / "tmp2.dts", "w") as f: 1038 f.write(""" 1039/dts-v1/; 1040/ { 1041""") 1042 with open(tmp_path / "tmp3.dts", "w") as f: 1043 f.write(""" 1044 x = <1>; 1045""") 1046 1047 subdir_1 = tmp_path / "subdir-1" 1048 subdir_1.mkdir() 1049 with open(subdir_1 / "via-include-path-1", "w") as f: 1050 f.write(""" 1051 = /include/ "via-include-path-2" 1052""") 1053 1054 subdir_2 = tmp_path / "subdir-2" 1055 subdir_2.mkdir() 1056 with open(subdir_2 / "via-include-path-2", "w") as f: 1057 f.write(""" 1058 <2>; 1059}; 1060""") 1061 1062 with open(tmp_path / "test.dts", "w") as test_dts: 1063 test_dts.write(""" 1064/include/ "tmp2.dts" 1065/include/ "tmp3.dts" 1066y /include/ "via-include-path-1" 1067""") 1068 1069 with temporary_chdir(tmp_path): 1070 dt = dtlib.DT("test.dts", include_path=(subdir_1, subdir_2)) 1071 expected_dt = """ 1072/dts-v1/; 1073 1074/ { 1075 x = < 0x1 >; 1076 y = < 0x2 >; 1077}; 1078"""[1:-1] 1079 assert str(dt) == expected_dt 1080 1081def test_include_misc(tmp_path): 1082 '''Miscellaneous /include/ tests.''' 1083 1084 # Missing includes should error out. 1085 1086 verify_error_endswith(""" 1087/include/ "missing" 1088""", 1089":1 (column 1): parse error: 'missing' could not be found") 1090 1091 # Verify that an error in an included file points to the right location 1092 1093 with temporary_chdir(tmp_path): 1094 with open("tmp2.dts", "w") as f: 1095 f.write("""\ 1096 1097 1098 x 1099""") 1100 with open("tmp.dts", "w") as f: 1101 f.write(""" 1102 1103 1104/include/ "tmp2.dts" 1105""") 1106 with dtlib_raises("tmp2.dts:3 (column 3): parse error: " 1107 "expected '/dts-v1/;' at start of file"): 1108 dtlib.DT("tmp.dts") 1109 1110def test_include_recursion(tmp_path): 1111 '''Test recursive /include/ detection''' 1112 1113 with temporary_chdir(tmp_path): 1114 with open("tmp2.dts", "w") as f: 1115 f.write('/include/ "tmp3.dts"\n') 1116 with open("tmp3.dts", "w") as f: 1117 f.write('/include/ "tmp.dts"\n') 1118 1119 with open("tmp.dts", "w") as f: 1120 f.write('/include/ "tmp2.dts"\n') 1121 expected_err = """\ 1122tmp3.dts:1 (column 1): parse error: recursive /include/: 1123tmp.dts:1 -> 1124tmp2.dts:1 -> 1125tmp3.dts:1 -> 1126tmp.dts""" 1127 with dtlib_raises(expected_err): 1128 dtlib.DT("tmp.dts") 1129 1130 with open("tmp.dts", "w") as f: 1131 f.write('/include/ "tmp.dts"\n') 1132 expected_err = """\ 1133tmp.dts:1 (column 1): parse error: recursive /include/: 1134tmp.dts:1 -> 1135tmp.dts""" 1136 with dtlib_raises(expected_err): 1137 dtlib.DT("tmp.dts") 1138 1139def test_omit_if_no_ref(): 1140 '''The /omit-if-no-ref/ marker is a bit of undocumented 1141 dtc magic that removes a node from the tree if it isn't 1142 referred to elsewhere. 1143 1144 https://elinux.org/Device_Tree_Source_Undocumented 1145 ''' 1146 1147 verify_parse(""" 1148/dts-v1/; 1149 1150/ { 1151 x = < &{/referenced} >, &referenced2; 1152 1153 /omit-if-no-ref/ referenced { 1154 }; 1155 1156 referenced2: referenced2 { 1157 }; 1158 1159 /omit-if-no-ref/ unreferenced { 1160 }; 1161 1162 l1: /omit-if-no-ref/ unreferenced2 { 1163 }; 1164 1165 /omit-if-no-ref/ l2: unreferenced3 { 1166 }; 1167 1168 unreferenced4: unreferenced4 { 1169 }; 1170 1171 unreferenced5 { 1172 }; 1173}; 1174 1175/omit-if-no-ref/ &referenced2; 1176/omit-if-no-ref/ &unreferenced4; 1177/omit-if-no-ref/ &{/unreferenced5}; 1178""", 1179""" 1180/dts-v1/; 1181 1182/ { 1183 x = < &{/referenced} >, &referenced2; 1184 referenced { 1185 phandle = < 0x1 >; 1186 }; 1187 referenced2: referenced2 { 1188 }; 1189}; 1190""") 1191 1192 verify_error_endswith(""" 1193/dts-v1/; 1194 1195/ { 1196 /omit-if-no-ref/ x = ""; 1197}; 1198""", 1199":4 (column 21): parse error: /omit-if-no-ref/ can only be used on nodes") 1200 1201 verify_error_endswith(""" 1202/dts-v1/; 1203 1204/ { 1205 /omit-if-no-ref/ x; 1206}; 1207""", 1208":4 (column 20): parse error: /omit-if-no-ref/ can only be used on nodes") 1209 1210 verify_error_endswith(""" 1211/dts-v1/; 1212 1213/ { 1214 /omit-if-no-ref/ { 1215 }; 1216}; 1217""", 1218":4 (column 19): parse error: expected node or property name") 1219 1220 verify_error_endswith(""" 1221/dts-v1/; 1222 1223/ { 1224 /omit-if-no-ref/ = < 0 >; 1225}; 1226""", 1227":4 (column 19): parse error: expected node or property name") 1228 1229 verify_error_endswith(""" 1230/dts-v1/; 1231 1232/ { 1233}; 1234 1235/omit-if-no-ref/ &missing; 1236""", 1237":6 (column 18): parse error: undefined node label 'missing'") 1238 1239 verify_error_endswith(""" 1240/dts-v1/; 1241 1242/omit-if-no-ref/ { 1243""", 1244":3 (column 18): parse error: expected label (&foo) or path (&{/foo/bar}) reference") 1245 1246def test_expr(): 1247 '''Property values may contain expressions.''' 1248 1249 verify_parse(""" 1250/dts-v1/; 1251 1252/ { 1253 ter1 = < (0 ? 1 : 0 ? 2 : 3) >; 1254 ter2 = < (0 ? 1 : 1 ? 2 : 3) >; 1255 ter3 = < (1 ? 1 : 0 ? 2 : 3) >; 1256 ter4 = < (1 ? 1 : 1 ? 2 : 3) >; 1257 or1 = < (0 || 0) >; 1258 or2 = < (0 || 1) >; 1259 or3 = < (1 || 0) >; 1260 or4 = < (1 || 1) >; 1261 and1 = < (0 && 0) >; 1262 and2 = < (0 && 1) >; 1263 and3 = < (1 && 0) >; 1264 and4 = < (1 && 1) >; 1265 bitor = < (1 | 2) >; 1266 bitxor = < (7 ^ 2) >; 1267 bitand = < (3 & 6) >; 1268 eq1 = < (1 == 0) >; 1269 eq2 = < (1 == 1) >; 1270 neq1 = < (1 != 0) >; 1271 neq2 = < (1 != 1) >; 1272 lt1 = < (1 < 2) >; 1273 lt2 = < (2 < 2) >; 1274 lt3 = < (3 < 2) >; 1275 lteq1 = < (1 <= 2) >; 1276 lteq2 = < (2 <= 2) >; 1277 lteq3 = < (3 <= 2) >; 1278 gt1 = < (1 > 2) >; 1279 gt2 = < (2 > 2) >; 1280 gt3 = < (3 > 2) >; 1281 gteq1 = < (1 >= 2) >; 1282 gteq2 = < (2 >= 2) >; 1283 gteq3 = < (3 >= 2) >; 1284 lshift = < (2 << 3) >; 1285 rshift = < (16 >> 3) >; 1286 add = < (3 + 4) >; 1287 sub = < (7 - 4) >; 1288 mul = < (3 * 4) >; 1289 div = < (11 / 3) >; 1290 mod = < (11 % 3) >; 1291 unary_minus = < (-3) >; 1292 bitnot = < (~1) >; 1293 not0 = < (!-1) >; 1294 not1 = < (!0) >; 1295 not2 = < (!1) >; 1296 not3 = < (!2) >; 1297 nest = < (((--3) + (-2)) * (--(-2))) >; 1298 char_lits = < ('a' + 'b') >; 1299}; 1300""", 1301""" 1302/dts-v1/; 1303 1304/ { 1305 ter1 = < 0x3 >; 1306 ter2 = < 0x2 >; 1307 ter3 = < 0x1 >; 1308 ter4 = < 0x1 >; 1309 or1 = < 0x0 >; 1310 or2 = < 0x1 >; 1311 or3 = < 0x1 >; 1312 or4 = < 0x1 >; 1313 and1 = < 0x0 >; 1314 and2 = < 0x0 >; 1315 and3 = < 0x0 >; 1316 and4 = < 0x1 >; 1317 bitor = < 0x3 >; 1318 bitxor = < 0x5 >; 1319 bitand = < 0x2 >; 1320 eq1 = < 0x0 >; 1321 eq2 = < 0x1 >; 1322 neq1 = < 0x1 >; 1323 neq2 = < 0x0 >; 1324 lt1 = < 0x1 >; 1325 lt2 = < 0x0 >; 1326 lt3 = < 0x0 >; 1327 lteq1 = < 0x1 >; 1328 lteq2 = < 0x1 >; 1329 lteq3 = < 0x0 >; 1330 gt1 = < 0x0 >; 1331 gt2 = < 0x0 >; 1332 gt3 = < 0x1 >; 1333 gteq1 = < 0x0 >; 1334 gteq2 = < 0x1 >; 1335 gteq3 = < 0x1 >; 1336 lshift = < 0x10 >; 1337 rshift = < 0x2 >; 1338 add = < 0x7 >; 1339 sub = < 0x3 >; 1340 mul = < 0xc >; 1341 div = < 0x3 >; 1342 mod = < 0x2 >; 1343 unary_minus = < 0xfffffffd >; 1344 bitnot = < 0xfffffffe >; 1345 not0 = < 0x0 >; 1346 not1 = < 0x1 >; 1347 not2 = < 0x0 >; 1348 not3 = < 0x0 >; 1349 nest = < 0xfffffffe >; 1350 char_lits = < 0xc3 >; 1351}; 1352""") 1353 1354 verify_error_endswith(""" 1355/dts-v1/; 1356 1357/ { 1358 a = < (1/(-1 + 1)) >; 1359}; 1360""", 1361":4 (column 18): parse error: division by zero") 1362 1363 verify_error_endswith(""" 1364/dts-v1/; 1365 1366/ { 1367 a = < (1%0) >; 1368}; 1369""", 1370":4 (column 11): parse error: division by zero") 1371 1372def test_comment_removal(): 1373 '''Comments should be removed when round-tripped to a str.''' 1374 1375 verify_parse(""" 1376/**//dts-v1//**/;// 1377// 1378// foo 1379/ /**/{// foo 1380x/**/=/* 1381foo 1382*/</**/1/***/>/****/;/**/}/*/**/; 1383""", 1384""" 1385/dts-v1/; 1386 1387/ { 1388 x = < 0x1 >; 1389}; 1390""") 1391 1392def verify_path_is(path, node_name, dt): 1393 '''Verify 'node.name' matches 'node_name' in 'dt'.''' 1394 1395 try: 1396 node = dt.get_node(path) 1397 assert node.name == node_name, f'unexpected path {path}' 1398 except dtlib.DTError: 1399 assert False, f'no node found for path {path}' 1400 1401def verify_path_error(path, msg, dt): 1402 '''Verify that an attempt to get node 'path' from 'dt' raises 1403 a DTError whose str is 'msg'.''' 1404 1405 with dtlib_raises(msg): 1406 dt.get_node(path) 1407 1408def test_get_node(): 1409 '''Test DT.get_node().''' 1410 1411 dt = parse(""" 1412/dts-v1/; 1413 1414/ { 1415 foo { 1416 bar { 1417 }; 1418 }; 1419 1420 baz { 1421 }; 1422}; 1423""") 1424 1425 verify_path_is("/", "/", dt) 1426 verify_path_is("//", "/", dt) 1427 verify_path_is("///", "/", dt) 1428 verify_path_is("/foo", "foo", dt) 1429 verify_path_is("//foo", "foo", dt) 1430 verify_path_is("///foo", "foo", dt) 1431 verify_path_is("/foo/bar", "bar", dt) 1432 verify_path_is("//foo//bar", "bar", dt) 1433 verify_path_is("///foo///bar", "bar", dt) 1434 verify_path_is("/baz", "baz", dt) 1435 1436 verify_path_error( 1437 "", 1438 "no alias '' found -- did you forget the leading '/' in the node path?", 1439 dt) 1440 verify_path_error( 1441 "missing", 1442 "no alias 'missing' found -- did you forget the leading '/' in the node path?", 1443 dt) 1444 verify_path_error( 1445 "/missing", 1446 "component 'missing' in path '/missing' does not exist", 1447 dt) 1448 verify_path_error( 1449 "/foo/missing", 1450 "component 'missing' in path '/foo/missing' does not exist", 1451 dt) 1452 1453 def verify_path_exists(path): 1454 assert dt.has_node(path), f"path '{path}' does not exist" 1455 1456 def verify_path_missing(path): 1457 assert not dt.has_node(path), f"path '{path}' exists" 1458 1459 verify_path_exists("/") 1460 verify_path_exists("/foo") 1461 verify_path_exists("/foo/bar") 1462 1463 verify_path_missing("/missing") 1464 verify_path_missing("/foo/missing") 1465 1466def test_aliases(): 1467 '''Test /aliases''' 1468 1469 dt = parse(""" 1470/dts-v1/; 1471 1472/ { 1473 aliases { 1474 alias1 = &l1; 1475 alias2 = &l2; 1476 alias3 = &{/sub/node3}; 1477 alias4 = &{/node4}; 1478 }; 1479 1480 l1: node1 { 1481 }; 1482 1483 l2: node2 { 1484 }; 1485 1486 sub { 1487 node3 { 1488 }; 1489 }; 1490 1491 node4 { 1492 node5 { 1493 }; 1494 }; 1495}; 1496""") 1497 1498 def verify_alias_target(alias, node_name): 1499 verify_path_is(alias, node_name, dt) 1500 assert alias in dt.alias2node 1501 assert dt.alias2node[alias].name == node_name, f"bad result for {alias}" 1502 1503 verify_alias_target("alias1", "node1") 1504 verify_alias_target("alias2", "node2") 1505 verify_alias_target("alias3", "node3") 1506 verify_path_is("alias4/node5", "node5", dt) 1507 1508 verify_path_error( 1509 "alias4/node5/node6", 1510 "component 'node6' in path 'alias4/node5/node6' does not exist", 1511 dt) 1512 1513 verify_error_matches(""" 1514/dts-v1/; 1515 1516/ { 1517 aliases { 1518 a = [ 00 ]; 1519 }; 1520}; 1521""", 1522"expected property 'a' on /aliases in .*" + 1523re.escape("to be assigned with either 'a = &foo' or 'a = \"/path/to/node\"', not 'a = [ 00 ];'")) 1524 1525 verify_error_matches(r""" 1526/dts-v1/; 1527 1528/ { 1529 aliases { 1530 a = "\xFF"; 1531 }; 1532}; 1533""", 1534re.escape(r"value of property 'a' (b'\xff\x00') on /aliases in ") + 1535".* is not valid UTF-8") 1536 1537 verify_error(""" 1538/dts-v1/; 1539 1540/ { 1541 aliases { 1542 A = "/aliases"; 1543 }; 1544}; 1545""", 1546"/aliases: alias property name 'A' should include only characters from [0-9a-z-]") 1547 1548 verify_error_matches(r""" 1549/dts-v1/; 1550 1551/ { 1552 aliases { 1553 a = "/missing"; 1554 }; 1555}; 1556""", 1557"property 'a' on /aliases in .* points to the non-existent node \"/missing\"") 1558 1559def test_prop_type(): 1560 '''Test Property.type''' 1561 1562 def verify_type(prop, expected): 1563 actual = dt.root.props[prop].type 1564 assert actual == expected, f'{prop} has wrong type' 1565 1566 dt = parse(""" 1567/dts-v1/; 1568 1569/ { 1570 empty; 1571 bytes1 = [ ]; 1572 bytes2 = [ 01 ]; 1573 bytes3 = [ 01 02 ]; 1574 bytes4 = foo: [ 01 bar: 02 ]; 1575 bytes5 = /bits/ 8 < 1 2 3 >; 1576 num = < 1 >; 1577 nums1 = < >; 1578 nums2 = < >, < >; 1579 nums3 = < 1 2 >; 1580 nums4 = < 1 2 >, < 3 >, < 4 >; 1581 string = "foo"; 1582 strings = "foo", "bar"; 1583 path1 = &node; 1584 path2 = &{/node}; 1585 phandle1 = < &node >; 1586 phandle2 = < &{/node} >; 1587 phandles1 = < &node &node >; 1588 phandles2 = < &node >, < &node >; 1589 phandle-and-nums-1 = < &node 1 >; 1590 phandle-and-nums-2 = < &node 1 2 &node 3 4 >; 1591 phandle-and-nums-3 = < &node 1 2 >, < &node 3 4 >; 1592 compound1 = < 1 >, [ 02 ]; 1593 compound2 = "foo", < >; 1594 1595 node: node { 1596 }; 1597}; 1598""") 1599 1600 verify_type("empty", dtlib.Type.EMPTY) 1601 verify_type("bytes1", dtlib.Type.BYTES) 1602 verify_type("bytes2", dtlib.Type.BYTES) 1603 verify_type("bytes3", dtlib.Type.BYTES) 1604 verify_type("bytes4", dtlib.Type.BYTES) 1605 verify_type("bytes5", dtlib.Type.BYTES) 1606 verify_type("num", dtlib.Type.NUM) 1607 verify_type("nums1", dtlib.Type.NUMS) 1608 verify_type("nums2", dtlib.Type.NUMS) 1609 verify_type("nums3", dtlib.Type.NUMS) 1610 verify_type("nums4", dtlib.Type.NUMS) 1611 verify_type("string", dtlib.Type.STRING) 1612 verify_type("strings", dtlib.Type.STRINGS) 1613 verify_type("phandle1", dtlib.Type.PHANDLE) 1614 verify_type("phandle2", dtlib.Type.PHANDLE) 1615 verify_type("phandles1", dtlib.Type.PHANDLES) 1616 verify_type("phandles2", dtlib.Type.PHANDLES) 1617 verify_type("phandle-and-nums-1", dtlib.Type.PHANDLES_AND_NUMS) 1618 verify_type("phandle-and-nums-2", dtlib.Type.PHANDLES_AND_NUMS) 1619 verify_type("phandle-and-nums-3", dtlib.Type.PHANDLES_AND_NUMS) 1620 verify_type("path1", dtlib.Type.PATH) 1621 verify_type("path2", dtlib.Type.PATH) 1622 verify_type("compound1", dtlib.Type.COMPOUND) 1623 verify_type("compound2", dtlib.Type.COMPOUND) 1624 1625def test_prop_type_casting(): 1626 '''Test Property.to_{num,nums,string,strings,node}()''' 1627 1628 dt = parse(r""" 1629/dts-v1/; 1630 1631/ { 1632 u = < 1 >; 1633 s = < 0xFFFFFFFF >; 1634 u8 = /bits/ 8 < 1 >; 1635 u16 = /bits/ 16 < 1 2 >; 1636 u64 = /bits/ 64 < 1 >; 1637 bytes = [ 01 02 03 ]; 1638 empty; 1639 zero = < >; 1640 two_u = < 1 2 >; 1641 two_s = < 0xFFFFFFFF 0xFFFFFFFE >; 1642 three_u = < 1 2 3 >; 1643 three_u_split = < 1 >, < 2 >, < 3 >; 1644 empty_string = ""; 1645 string = "foo\tbar baz"; 1646 invalid_string = "\xff"; 1647 strings = "foo", "bar", "baz"; 1648 invalid_strings = "foo", "\xff", "bar"; 1649 ref = <&{/target}>; 1650 refs = <&{/target} &{/target2}>; 1651 refs2 = <&{/target}>, <&{/target2}>; 1652 path = &{/target}; 1653 manualpath = "/target"; 1654 missingpath = "/missing"; 1655 1656 target { 1657 phandle = < 100 >; 1658 }; 1659 1660 target2 { 1661 }; 1662}; 1663""") 1664 1665 # Test Property.to_num() 1666 1667 def verify_to_num(prop, signed, expected): 1668 signed_str = "a signed" if signed else "an unsigned" 1669 actual = dt.root.props[prop].to_num(signed) 1670 assert actual == expected, \ 1671 f"{prop} has bad {signed_str} numeric value" 1672 1673 def verify_to_num_error_matches(prop, expected_re): 1674 with dtlib_raises(err_matches=expected_re): 1675 dt.root.props[prop].to_num() 1676 1677 verify_to_num("u", False, 1) 1678 verify_to_num("u", True, 1) 1679 verify_to_num("s", False, 0xFFFFFFFF) 1680 verify_to_num("s", True, -1) 1681 1682 verify_to_num_error_matches( 1683 "two_u", 1684 "expected property 'two_u' on / in .* to be assigned with " + 1685 re.escape("'two_u = < (number) >;', not 'two_u = < 0x1 0x2 >;'")) 1686 verify_to_num_error_matches( 1687 "u8", 1688 "expected property 'u8' on / in .* to be assigned with " + 1689 re.escape("'u8 = < (number) >;', not 'u8 = [ 01 ];'")) 1690 verify_to_num_error_matches( 1691 "u16", 1692 "expected property 'u16' on / in .* to be assigned with " + 1693 re.escape("'u16 = < (number) >;', not 'u16 = /bits/ 16 < 0x1 0x2 >;'")) 1694 verify_to_num_error_matches( 1695 "u64", 1696 "expected property 'u64' on / in .* to be assigned with " + 1697 re.escape("'u64 = < (number) >;', not 'u64 = /bits/ 64 < 0x1 >;'")) 1698 verify_to_num_error_matches( 1699 "string", 1700 "expected property 'string' on / in .* to be assigned with " + 1701 re.escape("'string = < (number) >;', not 'string = \"foo\\tbar baz\";'")) 1702 1703 # Test Property.to_nums() 1704 1705 def verify_to_nums(prop, signed, expected): 1706 signed_str = "signed" if signed else "unsigned" 1707 actual = dt.root.props[prop].to_nums(signed) 1708 assert actual == expected, \ 1709 f"'{prop}' gives the wrong {signed_str} numbers" 1710 1711 def verify_to_nums_error_matches(prop, expected_re): 1712 with dtlib_raises(err_matches=expected_re): 1713 dt.root.props[prop].to_nums() 1714 1715 verify_to_nums("zero", False, []) 1716 verify_to_nums("u", False, [1]) 1717 verify_to_nums("two_u", False, [1, 2]) 1718 verify_to_nums("two_u", True, [1, 2]) 1719 verify_to_nums("two_s", False, [0xFFFFFFFF, 0xFFFFFFFE]) 1720 verify_to_nums("two_s", True, [-1, -2]) 1721 verify_to_nums("three_u", False, [1, 2, 3]) 1722 verify_to_nums("three_u_split", False, [1, 2, 3]) 1723 1724 verify_to_nums_error_matches( 1725 "empty", 1726 "expected property 'empty' on / in .* to be assigned with " + 1727 re.escape("'empty = < (number) (number) ... >;', not 'empty;'")) 1728 verify_to_nums_error_matches( 1729 "string", 1730 "expected property 'string' on / in .* to be assigned with " + 1731 re.escape("'string = < (number) (number) ... >;', ") + 1732 re.escape("not 'string = \"foo\\tbar baz\";'")) 1733 1734 # Test Property.to_bytes() 1735 1736 def verify_to_bytes(prop, expected): 1737 actual = dt.root.props[prop].to_bytes() 1738 assert actual == expected, f"'{prop}' gives the wrong bytes" 1739 1740 def verify_to_bytes_error_matches(prop, expected_re): 1741 with dtlib_raises(err_matches=expected_re): 1742 dt.root.props[prop].to_bytes() 1743 1744 verify_to_bytes("u8", b"\x01") 1745 verify_to_bytes("bytes", b"\x01\x02\x03") 1746 1747 verify_to_bytes_error_matches( 1748 "u16", 1749 "expected property 'u16' on / in .* to be assigned with " + 1750 re.escape("'u16 = [ (byte) (byte) ... ];', ") + 1751 re.escape("not 'u16 = /bits/ 16 < 0x1 0x2 >;'")) 1752 verify_to_bytes_error_matches( 1753 "empty", 1754 "expected property 'empty' on / in .* to be assigned with " + 1755 re.escape("'empty = [ (byte) (byte) ... ];', not 'empty;'")) 1756 1757 # Test Property.to_string() 1758 1759 def verify_to_string(prop, expected): 1760 actual = dt.root.props[prop].to_string() 1761 assert actual == expected, f"'{prop}' to_string gives the wrong string" 1762 1763 def verify_to_string_error_matches(prop, expected_re): 1764 with dtlib_raises(err_matches=expected_re): 1765 dt.root.props[prop].to_string() 1766 1767 verify_to_string("empty_string", "") 1768 verify_to_string("string", "foo\tbar baz") 1769 1770 verify_to_string_error_matches( 1771 "u", 1772 "expected property 'u' on / in .* to be assigned with " + 1773 re.escape("'u = \"string\";', not 'u = < 0x1 >;'")) 1774 verify_to_string_error_matches( 1775 "strings", 1776 "expected property 'strings' on / in .* to be assigned with " + 1777 re.escape("'strings = \"string\";', ")+ 1778 re.escape("not 'strings = \"foo\", \"bar\", \"baz\";'")) 1779 verify_to_string_error_matches( 1780 "invalid_string", 1781 re.escape(r"value of property 'invalid_string' (b'\xff\x00') on / ") + 1782 "in .* is not valid UTF-8") 1783 1784 # Test Property.to_strings() 1785 1786 def verify_to_strings(prop, expected): 1787 actual = dt.root.props[prop].to_strings() 1788 assert actual == expected, f"'{prop}' to_strings gives the wrong value" 1789 1790 def verify_to_strings_error_matches(prop, expected_re): 1791 with dtlib_raises(err_matches=expected_re): 1792 dt.root.props[prop].to_strings() 1793 1794 verify_to_strings("empty_string", [""]) 1795 verify_to_strings("string", ["foo\tbar baz"]) 1796 verify_to_strings("strings", ["foo", "bar", "baz"]) 1797 1798 verify_to_strings_error_matches( 1799 "u", 1800 "expected property 'u' on / in .* to be assigned with " + 1801 re.escape("'u = \"string\", \"string\", ... ;', not 'u = < 0x1 >;'")) 1802 verify_to_strings_error_matches( 1803 "invalid_strings", 1804 "value of property 'invalid_strings' " + 1805 re.escape(r"(b'foo\x00\xff\x00bar\x00') on / in ") + 1806 ".* is not valid UTF-8") 1807 1808 # Test Property.to_node() 1809 1810 def verify_to_node(prop, path): 1811 actual = dt.root.props[prop].to_node().path 1812 assert actual == path, f"'{prop}' points at wrong path" 1813 1814 def verify_to_node_error_matches(prop, expected_re): 1815 with dtlib_raises(err_matches=expected_re): 1816 dt.root.props[prop].to_node() 1817 1818 verify_to_node("ref", "/target") 1819 1820 verify_to_node_error_matches( 1821 "u", 1822 "expected property 'u' on / in .* to be assigned with " + 1823 re.escape("'u = < &foo >;', not 'u = < 0x1 >;'")) 1824 verify_to_node_error_matches( 1825 "string", 1826 "expected property 'string' on / in .* to be assigned with " + 1827 re.escape("'string = < &foo >;', not 'string = \"foo\\tbar baz\";'")) 1828 1829 # Test Property.to_nodes() 1830 1831 def verify_to_nodes(prop, paths): 1832 actual = [node.path for node in dt.root.props[prop].to_nodes()] 1833 assert actual == paths, f"'{prop} gives wrong node paths" 1834 1835 def verify_to_nodes_error_matches(prop, expected_re): 1836 with dtlib_raises(err_matches=expected_re): 1837 dt.root.props[prop].to_nodes() 1838 1839 verify_to_nodes("zero", []) 1840 verify_to_nodes("ref", ["/target"]) 1841 verify_to_nodes("refs", ["/target", "/target2"]) 1842 verify_to_nodes("refs2", ["/target", "/target2"]) 1843 1844 verify_to_nodes_error_matches( 1845 "u", 1846 "expected property 'u' on / in .* to be assigned with " + 1847 re.escape("'u = < &foo &bar ... >;', not 'u = < 0x1 >;'")) 1848 verify_to_nodes_error_matches( 1849 "string", 1850 "expected property 'string' on / in .* to be assigned with " + 1851 re.escape("'string = < &foo &bar ... >;', ") + 1852 re.escape("not 'string = \"foo\\tbar baz\";'")) 1853 1854 # Test Property.to_path() 1855 1856 def verify_to_path(prop, path): 1857 actual = dt.root.props[prop].to_path().path 1858 assert actual == path, f"'{prop} gives the wrong path" 1859 1860 def verify_to_path_error_matches(prop, expected_re): 1861 with dtlib_raises(err_matches=expected_re): 1862 dt.root.props[prop].to_path() 1863 1864 verify_to_path("path", "/target") 1865 verify_to_path("manualpath", "/target") 1866 1867 verify_to_path_error_matches( 1868 "u", 1869 "expected property 'u' on / in .* to be assigned with either " + 1870 re.escape("'u = &foo' or 'u = \"/path/to/node\"', not 'u = < 0x1 >;'")) 1871 verify_to_path_error_matches( 1872 "missingpath", 1873 "property 'missingpath' on / in .* points to the non-existent node " 1874 '"/missing"') 1875 1876 # Test top-level to_num() and to_nums() 1877 1878 def verify_raw_to_num(fn, prop, length, signed, expected): 1879 actual = fn(dt.root.props[prop].value, length, signed) 1880 assert actual == expected, \ 1881 f"{fn.__name__}(<{prop}>, {length}, {signed}) gives wrong value" 1882 1883 def verify_raw_to_num_error(fn, data, length, msg): 1884 # We're using this instead of dtlib_raises() for the extra 1885 # context we get from the assertion below. 1886 with pytest.raises(dtlib.DTError) as e: 1887 fn(data, length) 1888 assert str(e.value) == msg, \ 1889 (f"{fn.__name__}() called with data='{data}', length='{length}' " 1890 "gives the wrong error") 1891 1892 verify_raw_to_num(dtlib.to_num, "u", None, False, 1) 1893 verify_raw_to_num(dtlib.to_num, "u", 4, False, 1) 1894 verify_raw_to_num(dtlib.to_num, "s", None, False, 0xFFFFFFFF) 1895 verify_raw_to_num(dtlib.to_num, "s", None, True, -1) 1896 verify_raw_to_num(dtlib.to_nums, "empty", 4, False, []) 1897 verify_raw_to_num(dtlib.to_nums, "u16", 2, False, [1, 2]) 1898 verify_raw_to_num(dtlib.to_nums, "two_s", 4, False, [0xFFFFFFFF, 0xFFFFFFFE]) 1899 verify_raw_to_num(dtlib.to_nums, "two_s", 4, True, [-1, -2]) 1900 1901 verify_raw_to_num_error(dtlib.to_num, 0, 0, "'0' has type 'int', expected 'bytes'") 1902 verify_raw_to_num_error(dtlib.to_num, b"", 0, "'length' must be greater than zero, was 0") 1903 verify_raw_to_num_error(dtlib.to_num, b"foo", 2, "b'foo' is 3 bytes long, expected 2") 1904 verify_raw_to_num_error(dtlib.to_nums, 0, 0, "'0' has type 'int', expected 'bytes'") 1905 verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'length' must be greater than zero, was 0") 1906 verify_raw_to_num_error(dtlib.to_nums, b"foooo", 2, "b'foooo' is 5 bytes long, expected a length that's a multiple of 2") 1907 1908def test_duplicate_labels(): 1909 ''' 1910 It is an error to duplicate labels in most conditions, but there 1911 are some exceptions where it's OK. 1912 ''' 1913 1914 verify_error(""" 1915/dts-v1/; 1916 1917/ { 1918 sub1 { 1919 label: foo { 1920 }; 1921 }; 1922 1923 sub2 { 1924 label: bar { 1925 }; 1926 }; 1927}; 1928""", 1929"Label 'label' appears on /sub1/foo and on /sub2/bar") 1930 1931 verify_error(""" 1932/dts-v1/; 1933 1934/ { 1935 sub { 1936 label: foo { 1937 }; 1938 }; 1939}; 1940/ { 1941 sub { 1942 label: bar { 1943 }; 1944 }; 1945}; 1946""", 1947"Label 'label' appears on /sub/bar and on /sub/foo") 1948 1949 verify_error(""" 1950/dts-v1/; 1951 1952/ { 1953 foo: a = < 0 >; 1954 foo: node { 1955 }; 1956}; 1957""", 1958"Label 'foo' appears on /node and on property 'a' of node /") 1959 1960 verify_error(""" 1961/dts-v1/; 1962 1963/ { 1964 foo: a = < 0 >; 1965 node { 1966 foo: b = < 0 >; 1967 }; 1968}; 1969""", 1970"Label 'foo' appears on property 'a' of node / and on property 'b' of node /node") 1971 1972 verify_error(""" 1973/dts-v1/; 1974 1975/ { 1976 foo: a = foo: < 0 >; 1977}; 1978""", 1979"Label 'foo' appears in the value of property 'a' of node / and on property 'a' of node /") 1980 1981 # Giving the same label twice for the same node is fine 1982 verify_parse(""" 1983/dts-v1/; 1984 1985/ { 1986 sub { 1987 label: foo { 1988 }; 1989 }; 1990}; 1991/ { 1992 1993 sub { 1994 label: foo { 1995 }; 1996 }; 1997}; 1998""", 1999""" 2000/dts-v1/; 2001 2002/ { 2003 sub { 2004 label: foo { 2005 }; 2006 }; 2007}; 2008""") 2009 2010 # Duplicate labels are fine if one of the nodes is deleted 2011 verify_parse(""" 2012/dts-v1/; 2013 2014/ { 2015 label: foo { 2016 }; 2017 label: bar { 2018 }; 2019}; 2020 2021/delete-node/ &{/bar}; 2022""", 2023""" 2024/dts-v1/; 2025 2026/ { 2027 label: foo { 2028 }; 2029}; 2030""") 2031 2032 # 2033 # Test overriding/deleting a property with references 2034 # 2035 2036 verify_parse(""" 2037/dts-v1/; 2038 2039/ { 2040 x = &foo, < &foo >; 2041 y = &foo, < &foo >; 2042 foo: foo { 2043 }; 2044}; 2045 2046/ { 2047 x = < 1 >; 2048 /delete-property/ y; 2049}; 2050""", 2051""" 2052/dts-v1/; 2053 2054/ { 2055 x = < 0x1 >; 2056 foo: foo { 2057 }; 2058}; 2059""") 2060 2061 # 2062 # Test self-referential node 2063 # 2064 2065 verify_parse(""" 2066/dts-v1/; 2067 2068/ { 2069 label: foo { 2070 x = &{/foo}, &label, < &label >; 2071 }; 2072}; 2073""", 2074""" 2075/dts-v1/; 2076 2077/ { 2078 label: foo { 2079 x = &{/foo}, &label, < &label >; 2080 phandle = < 0x1 >; 2081 }; 2082}; 2083""") 2084 2085 # 2086 # Test /memreserve/ 2087 # 2088 2089 dt = verify_parse(""" 2090/dts-v1/; 2091 2092l1: l2: /memreserve/ (1 + 1) (2 * 2); 2093/memreserve/ 0x100 0x200; 2094 2095/ { 2096}; 2097""", 2098""" 2099/dts-v1/; 2100 2101l1: l2: /memreserve/ 0x0000000000000002 0x0000000000000004; 2102/memreserve/ 0x0000000000000100 0x0000000000000200; 2103 2104/ { 2105}; 2106""") 2107 2108 expected = [(["l1", "l2"], 2, 4), ([], 0x100, 0x200)] 2109 assert dt.memreserves == expected 2110 2111 verify_error_endswith(""" 2112/dts-v1/; 2113 2114foo: / { 2115}; 2116""", 2117":3 (column 6): parse error: expected /memreserve/ after labels at beginning of file") 2118 2119def test_reprs(): 2120 '''Test the __repr__() functions.''' 2121 2122 dts = """ 2123/dts-v1/; 2124 2125/ { 2126 x = < 0 >; 2127 sub { 2128 y = < 1 >; 2129 }; 2130}; 2131""" 2132 2133 dt = parse(dts, include_path=("foo", "bar")) 2134 2135 assert re.fullmatch(r"DT\(filename='.*', include_path=.'foo', 'bar'.\)", 2136 repr(dt)) 2137 assert re.fullmatch("<Property 'x' at '/' in '.*'>", 2138 repr(dt.root.props["x"])) 2139 assert re.fullmatch("<Node /sub in '.*'>", 2140 repr(dt.root.nodes["sub"])) 2141 2142 dt = parse(dts, include_path=iter(("foo", "bar"))) 2143 2144 assert re.fullmatch(r"DT\(filename='.*', include_path=.'foo', 'bar'.\)", 2145 repr(dt)) 2146 2147def test_names(): 2148 '''Tests for node/property names.''' 2149 2150 verify_parse(r""" 2151/dts-v1/; 2152 2153/ { 2154 // A leading \ is accepted but ignored in node/propert names 2155 \aA0,._+*#?- = &_, &{/aA0,._+@-}; 2156 2157 // Names that overlap with operators and integer literals 2158 2159 + = [ 00 ]; 2160 * = [ 02 ]; 2161 - = [ 01 ]; 2162 ? = [ 03 ]; 2163 0 = [ 04 ]; 2164 0x123 = [ 05 ]; 2165 2166 // Node names are more restrictive than property names. 2167 _: \aA0,._+@- { 2168 }; 2169 2170 0 { 2171 }; 2172}; 2173""", 2174""" 2175/dts-v1/; 2176 2177/ { 2178 aA0,._+*#?- = &_, &{/aA0,._+@-}; 2179 + = [ 00 ]; 2180 * = [ 02 ]; 2181 - = [ 01 ]; 2182 ? = [ 03 ]; 2183 0 = [ 04 ]; 2184 0x123 = [ 05 ]; 2185 _: aA0,._+@- { 2186 }; 2187 0 { 2188 }; 2189}; 2190""") 2191 2192 verify_error_endswith(r""" 2193/dts-v1/; 2194 2195/ { 2196 foo@3; 2197}; 2198""", 2199":4 (column 7): parse error: '@' is only allowed in node names") 2200 2201 verify_error_endswith(r""" 2202/dts-v1/; 2203 2204/ { 2205 foo@3 = < 0 >; 2206}; 2207""", 2208":4 (column 8): parse error: '@' is only allowed in node names") 2209 2210 verify_error_endswith(r""" 2211/dts-v1/; 2212 2213/ { 2214 foo@2@3 { 2215 }; 2216}; 2217""", 2218":4 (column 10): parse error: multiple '@' in node name") 2219 2220def test_dense_input(): 2221 ''' 2222 Test that a densely written DTS input round-trips to something 2223 readable. 2224 ''' 2225 2226 verify_parse(""" 2227/dts-v1/;/{l1:l2:foo{l3:l4:bar{l5:x=l6:/bits/8<l7:1 l8:2>l9:,[03],"a";};};}; 2228""", 2229""" 2230/dts-v1/; 2231 2232/ { 2233 l1: l2: foo { 2234 l3: l4: bar { 2235 l5: x = l6: [ l7: 01 l8: 02 l9: ], [ 03 ], "a"; 2236 }; 2237 }; 2238}; 2239""") 2240 2241def test_misc(): 2242 '''Test miscellaneous errors and non-errors.''' 2243 2244 verify_error_endswith("", ":1 (column 1): parse error: expected '/dts-v1/;' at start of file") 2245 2246 verify_error_endswith(""" 2247/dts-v1/; 2248""", 2249":2 (column 1): parse error: no root node defined") 2250 2251 verify_error_endswith(""" 2252/dts-v1/; /plugin/; 2253""", 2254":1 (column 11): parse error: /plugin/ is not supported") 2255 2256 verify_error_endswith(""" 2257/dts-v1/; 2258 2259/ { 2260 foo: foo { 2261 }; 2262}; 2263 2264// Only one label supported before label references at the top level 2265l1: l2: &foo { 2266}; 2267""", 2268":9 (column 5): parse error: expected label reference (&foo)") 2269 2270 verify_error_endswith(""" 2271/dts-v1/; 2272 2273/ { 2274 foo: {}; 2275}; 2276""", 2277":4 (column 14): parse error: expected node or property name") 2278 2279 # Multiple /dts-v1/ at the start of a file is fine 2280 verify_parse(""" 2281/dts-v1/; 2282/dts-v1/; 2283 2284/ { 2285}; 2286""", 2287""" 2288/dts-v1/; 2289 2290/ { 2291}; 2292""") 2293 2294def test_dangling_alias(): 2295 dt = parse(''' 2296/dts-v1/; 2297 2298/ { 2299 aliases { foo = "/missing"; }; 2300}; 2301''', force=True) 2302 assert dt.get_node('/aliases').props['foo'].to_string() == '/missing' 2303 2304def test_duplicate_nodes(): 2305 # Duplicate node names in the same {} block are an error in dtc, 2306 # so we want to reproduce the same behavior. But we also need to 2307 # make sure that doesn't break overlays modifying the same node. 2308 2309 verify_error_endswith(""" 2310/dts-v1/; 2311 2312/ { 2313 foo {}; 2314 foo {}; 2315}; 2316""", "/foo: duplicate node name") 2317 2318 verify_parse(""" 2319/dts-v1/; 2320 2321/ { 2322 foo { prop = <3>; }; 2323}; 2324/ { 2325 foo { prop = <4>; }; 2326}; 2327""", 2328""" 2329/dts-v1/; 2330 2331/ { 2332 foo { 2333 prop = < 0x4 >; 2334 }; 2335}; 2336""") 2337 2338def test_deepcopy(): 2339 dt = parse(''' 2340/dts-v1/; 2341 2342memreservelabel: /memreserve/ 0xdeadbeef 0x4000; 2343 2344/ { 2345 aliases { 2346 foo = &nodelabel; 2347 }; 2348 rootprop_label: rootprop = prop_offset0: <0x12345678 prop_offset4: 0x0>; 2349 nodelabel: node@1234 { 2350 nodeprop = <3>; 2351 subnode { 2352 ref-to-node = <&nodelabel>; 2353 }; 2354 }; 2355}; 2356''') 2357 dt_copy = deepcopy(dt) 2358 assert dt_copy.filename == dt.filename 2359 2360 # dt_copy.root checks: 2361 root_copy = dt_copy.root 2362 assert root_copy is not dt.root 2363 assert root_copy.parent is None 2364 assert root_copy.dt is dt_copy 2365 assert root_copy.labels == [] 2366 assert root_copy.labels is not dt.root.labels 2367 2368 # dt_copy.memreserves checks: 2369 assert dt_copy.memreserves == [ 2370 (set(['memreservelabel']), 0xdeadbeef, 0x4000) 2371 ] 2372 assert dt_copy.memreserves is not dt.memreserves 2373 2374 # Miscellaneous dt_copy node and property checks: 2375 assert 'rootprop' in root_copy.props 2376 rootprop_copy = root_copy.props['rootprop'] 2377 assert rootprop_copy is not dt.root.props['rootprop'] 2378 assert rootprop_copy.name == 'rootprop' 2379 assert rootprop_copy.value == b'\x12\x34\x56\x78\0\0\0\0' 2380 assert rootprop_copy.type == dtlib.Type.NUMS 2381 assert rootprop_copy.labels == ['rootprop_label'] 2382 assert rootprop_copy.labels is not dt.root.props['rootprop'].labels 2383 assert rootprop_copy.offset_labels == { 2384 'prop_offset0': 0, 2385 'prop_offset4': 4, 2386 } 2387 assert rootprop_copy.offset_labels is not \ 2388 dt.root.props['rootprop'].offset_labels 2389 assert rootprop_copy.node is root_copy 2390 2391 assert dt_copy.has_node('/node@1234') 2392 node_copy = dt_copy.get_node('/node@1234') 2393 assert node_copy is not dt.get_node('/node@1234') 2394 assert node_copy.labels == ['nodelabel'] 2395 assert node_copy.labels is not dt.get_node('/node@1234').labels 2396 assert node_copy.name == 'node@1234' 2397 assert node_copy.unit_addr == '1234' 2398 assert node_copy.path == '/node@1234' 2399 assert set(node_copy.props.keys()) == set(['nodeprop', 'phandle']) 2400 assert node_copy.props is not dt.get_node('/node@1234').props 2401 assert node_copy.props['nodeprop'].name == 'nodeprop' 2402 assert node_copy.props['nodeprop'].labels == [] 2403 assert node_copy.props['nodeprop'].offset_labels == {} 2404 assert node_copy.props['nodeprop'].node is node_copy 2405 assert node_copy.dt is dt_copy 2406 2407 assert 'subnode' in node_copy.nodes 2408 subnode_copy = node_copy.nodes['subnode'] 2409 assert subnode_copy is not dt.get_node('/node@1234/subnode') 2410 assert subnode_copy.parent is node_copy 2411 2412 # dt_copy.label2prop and .label2prop_offset checks: 2413 assert 'rootprop_label' in dt_copy.label2prop 2414 assert dt_copy.label2prop['rootprop_label'] is rootprop_copy 2415 assert list(dt_copy.label2prop_offset.keys()) == ['prop_offset0', 2416 'prop_offset4'] 2417 assert dt_copy.label2prop_offset['prop_offset4'][0] is rootprop_copy 2418 assert dt_copy.label2prop_offset['prop_offset4'][1] == 4 2419 2420 # dt_copy.foo2node checks: 2421 def check_node_lookup_table(attr_name): 2422 original = getattr(dt, attr_name) 2423 copy = getattr(dt_copy, attr_name) 2424 assert original is not copy 2425 assert list(original.keys()) == list(copy.keys()) 2426 assert all([original_node.path == copy_node.path and 2427 original_node is not copy_node 2428 for original_node, copy_node in 2429 zip(original.values(), copy.values())]) 2430 2431 check_node_lookup_table('alias2node') 2432 check_node_lookup_table('label2node') 2433 check_node_lookup_table('phandle2node') 2434 2435 assert list(dt_copy.alias2node.keys()) == ['foo'] 2436 assert dt_copy.alias2node['foo'] is node_copy 2437 2438 assert list(dt_copy.label2node.keys()) == ['nodelabel'] 2439 assert dt_copy.label2node['nodelabel'] is node_copy 2440 2441 assert dt_copy.phandle2node 2442 # This is a little awkward because of the way dtlib allocates 2443 # phandles. 2444 phandle2node_copy_values = set(dt_copy.phandle2node.values()) 2445 assert node_copy in phandle2node_copy_values 2446 for node in dt.node_iter(): 2447 assert node not in phandle2node_copy_values 2448 2449def test_move_node(): 2450 # Test cases for DT.move_node(). 2451 2452 dt = parse(''' 2453/dts-v1/; 2454 2455/ { 2456 aliases { 2457 parent-alias = &parent_label; 2458 }; 2459 parent_label: parent { 2460 child {}; 2461 }; 2462 bar { 2463 shouldbechosen { 2464 foo = "bar"; 2465 }; 2466 }; 2467}; 2468''') 2469 parent = dt.get_node('/parent') 2470 child = dt.get_node('/parent/child') 2471 2472 dt.move_node(parent, '/newpath') 2473 2474 assert parent.path == '/newpath' 2475 assert child.path == '/newpath/child' 2476 assert child.parent is parent 2477 assert child.parent is dt.get_node('/newpath') 2478 assert dt.get_node('parent-alias') is parent 2479 assert dt.label2node['parent_label'] is parent 2480 2481 assert not dt.has_node('/chosen') 2482 dt.move_node(dt.get_node('/bar/shouldbechosen'), '/chosen') 2483 assert dt.has_node('/chosen') 2484 assert 'foo' in dt.get_node('/chosen').props 2485 2486 with dtlib_raises("the root node can't be moved"): 2487 dt.move_node(dt.root, '/somewhere/else') 2488 2489 with dtlib_raises("can't move '/newpath' to '/aliases': " 2490 "destination node exists"): 2491 dt.move_node(parent, '/aliases') 2492 2493 with dtlib_raises("path 'xyz' doesn't start with '/'"): 2494 dt.move_node(parent, 'xyz') 2495 2496 with dtlib_raises("new path '/ invalid': bad character ' '"): 2497 dt.move_node(parent, '/ invalid') 2498 2499 with dtlib_raises("can't move '/newpath' to '/foo/bar': " 2500 "parent node '/foo' doesn't exist"): 2501 dt.move_node(parent, '/foo/bar') 2502