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 sub1 { 916 x = < 1 >; 917 sub2 { 918 x = < &sub >, ⊂ 919 }; 920 /delete-node/ sub2; 921 }; 922 923 sub3: sub3 { 924 x = < &sub >, ⊂ 925 }; 926 927 sub4 { 928 x = < &sub >, ⊂ 929 }; 930}; 931 932/delete-node/ &sub3; 933/delete-node/ &{/sub4}; 934""", 935""" 936/dts-v1/; 937 938/ { 939 sub1 { 940 x = < 0x1 >; 941 }; 942}; 943""") 944 945 verify_error_endswith(""" 946/dts-v1/; 947 948/ { 949}; 950 951/delete-node/ &missing; 952""", 953":6 (column 15): parse error: undefined node label 'missing'") 954 955 verify_error_endswith(""" 956/dts-v1/; 957 958/delete-node/ { 959""", 960":3 (column 15): parse error: expected label (&foo) or path (&{/foo/bar}) reference") 961 962def test_include_curdir(tmp_path): 963 '''Verify that /include/ (which is handled in the lexer) searches the 964 current directory''' 965 966 with temporary_chdir(tmp_path): 967 with open("same-dir-1", "w") as f: 968 f.write(""" 969 x = [ 00 ]; 970 /include/ "same-dir-2" 971""") 972 with open("same-dir-2", "w") as f: 973 f.write(""" 974 y = [ 01 ]; 975 /include/ "same-dir-3" 976""") 977 with open("same-dir-3", "w") as f: 978 f.write(""" 979 z = [ 02 ]; 980""") 981 with open("test.dts", "w") as f: 982 f.write(""" 983/dts-v1/; 984 985/ { 986 /include/ "same-dir-1" 987}; 988""") 989 dt = dtlib.DT("test.dts") 990 assert str(dt) == """ 991/dts-v1/; 992 993/ { 994 x = [ 00 ]; 995 y = [ 01 ]; 996 z = [ 02 ]; 997}; 998"""[1:-1] 999 1000def test_include_is_lexical(tmp_path): 1001 '''/include/ is done in the lexer, which means that property 1002 definitions can span multiple included files in different 1003 directories.''' 1004 1005 with open(tmp_path / "tmp2.dts", "w") as f: 1006 f.write(""" 1007/dts-v1/; 1008/ { 1009""") 1010 with open(tmp_path / "tmp3.dts", "w") as f: 1011 f.write(""" 1012 x = <1>; 1013""") 1014 1015 subdir_1 = tmp_path / "subdir-1" 1016 subdir_1.mkdir() 1017 with open(subdir_1 / "via-include-path-1", "w") as f: 1018 f.write(""" 1019 = /include/ "via-include-path-2" 1020""") 1021 1022 subdir_2 = tmp_path / "subdir-2" 1023 subdir_2.mkdir() 1024 with open(subdir_2 / "via-include-path-2", "w") as f: 1025 f.write(""" 1026 <2>; 1027}; 1028""") 1029 1030 with open(tmp_path / "test.dts", "w") as test_dts: 1031 test_dts.write(""" 1032/include/ "tmp2.dts" 1033/include/ "tmp3.dts" 1034y /include/ "via-include-path-1" 1035""") 1036 1037 with temporary_chdir(tmp_path): 1038 dt = dtlib.DT("test.dts", include_path=(subdir_1, subdir_2)) 1039 expected_dt = """ 1040/dts-v1/; 1041 1042/ { 1043 x = < 0x1 >; 1044 y = < 0x2 >; 1045}; 1046"""[1:-1] 1047 assert str(dt) == expected_dt 1048 1049def test_include_misc(tmp_path): 1050 '''Miscellaneous /include/ tests.''' 1051 1052 # Missing includes should error out. 1053 1054 verify_error_endswith(""" 1055/include/ "missing" 1056""", 1057":1 (column 1): parse error: 'missing' could not be found") 1058 1059 # Verify that an error in an included file points to the right location 1060 1061 with temporary_chdir(tmp_path): 1062 with open("tmp2.dts", "w") as f: 1063 f.write("""\ 1064 1065 1066 x 1067""") 1068 with open("tmp.dts", "w") as f: 1069 f.write(""" 1070 1071 1072/include/ "tmp2.dts" 1073""") 1074 with dtlib_raises("tmp2.dts:3 (column 3): parse error: " 1075 "expected '/dts-v1/;' at start of file"): 1076 dtlib.DT("tmp.dts") 1077 1078def test_include_recursion(tmp_path): 1079 '''Test recursive /include/ detection''' 1080 1081 with temporary_chdir(tmp_path): 1082 with open("tmp2.dts", "w") as f: 1083 f.write('/include/ "tmp3.dts"\n') 1084 with open("tmp3.dts", "w") as f: 1085 f.write('/include/ "tmp.dts"\n') 1086 1087 with open("tmp.dts", "w") as f: 1088 f.write('/include/ "tmp2.dts"\n') 1089 expected_err = """\ 1090tmp3.dts:1 (column 1): parse error: recursive /include/: 1091tmp.dts:1 -> 1092tmp2.dts:1 -> 1093tmp3.dts:1 -> 1094tmp.dts""" 1095 with dtlib_raises(expected_err): 1096 dtlib.DT("tmp.dts") 1097 1098 with open("tmp.dts", "w") as f: 1099 f.write('/include/ "tmp.dts"\n') 1100 expected_err = """\ 1101tmp.dts:1 (column 1): parse error: recursive /include/: 1102tmp.dts:1 -> 1103tmp.dts""" 1104 with dtlib_raises(expected_err): 1105 dtlib.DT("tmp.dts") 1106 1107def test_omit_if_no_ref(): 1108 '''The /omit-if-no-ref/ marker is a bit of undocumented 1109 dtc magic that removes a node from the tree if it isn't 1110 referred to elsewhere. 1111 1112 https://elinux.org/Device_Tree_Source_Undocumented 1113 ''' 1114 1115 verify_parse(""" 1116/dts-v1/; 1117 1118/ { 1119 x = < &{/referenced} >, &referenced2; 1120 1121 /omit-if-no-ref/ referenced { 1122 }; 1123 1124 referenced2: referenced2 { 1125 }; 1126 1127 /omit-if-no-ref/ unreferenced { 1128 }; 1129 1130 l1: /omit-if-no-ref/ unreferenced2 { 1131 }; 1132 1133 /omit-if-no-ref/ l2: unreferenced3 { 1134 }; 1135 1136 unreferenced4: unreferenced4 { 1137 }; 1138 1139 unreferenced5 { 1140 }; 1141}; 1142 1143/omit-if-no-ref/ &referenced2; 1144/omit-if-no-ref/ &unreferenced4; 1145/omit-if-no-ref/ &{/unreferenced5}; 1146""", 1147""" 1148/dts-v1/; 1149 1150/ { 1151 x = < &{/referenced} >, &referenced2; 1152 referenced { 1153 phandle = < 0x1 >; 1154 }; 1155 referenced2: referenced2 { 1156 }; 1157}; 1158""") 1159 1160 verify_error_endswith(""" 1161/dts-v1/; 1162 1163/ { 1164 /omit-if-no-ref/ x = ""; 1165}; 1166""", 1167":4 (column 21): parse error: /omit-if-no-ref/ can only be used on nodes") 1168 1169 verify_error_endswith(""" 1170/dts-v1/; 1171 1172/ { 1173 /omit-if-no-ref/ x; 1174}; 1175""", 1176":4 (column 20): parse error: /omit-if-no-ref/ can only be used on nodes") 1177 1178 verify_error_endswith(""" 1179/dts-v1/; 1180 1181/ { 1182 /omit-if-no-ref/ { 1183 }; 1184}; 1185""", 1186":4 (column 19): parse error: expected node or property name") 1187 1188 verify_error_endswith(""" 1189/dts-v1/; 1190 1191/ { 1192 /omit-if-no-ref/ = < 0 >; 1193}; 1194""", 1195":4 (column 19): parse error: expected node or property name") 1196 1197 verify_error_endswith(""" 1198/dts-v1/; 1199 1200/ { 1201}; 1202 1203/omit-if-no-ref/ &missing; 1204""", 1205":6 (column 18): parse error: undefined node label 'missing'") 1206 1207 verify_error_endswith(""" 1208/dts-v1/; 1209 1210/omit-if-no-ref/ { 1211""", 1212":3 (column 18): parse error: expected label (&foo) or path (&{/foo/bar}) reference") 1213 1214def test_expr(): 1215 '''Property values may contain expressions.''' 1216 1217 verify_parse(""" 1218/dts-v1/; 1219 1220/ { 1221 ter1 = < (0 ? 1 : 0 ? 2 : 3) >; 1222 ter2 = < (0 ? 1 : 1 ? 2 : 3) >; 1223 ter3 = < (1 ? 1 : 0 ? 2 : 3) >; 1224 ter4 = < (1 ? 1 : 1 ? 2 : 3) >; 1225 or1 = < (0 || 0) >; 1226 or2 = < (0 || 1) >; 1227 or3 = < (1 || 0) >; 1228 or4 = < (1 || 1) >; 1229 and1 = < (0 && 0) >; 1230 and2 = < (0 && 1) >; 1231 and3 = < (1 && 0) >; 1232 and4 = < (1 && 1) >; 1233 bitor = < (1 | 2) >; 1234 bitxor = < (7 ^ 2) >; 1235 bitand = < (3 & 6) >; 1236 eq1 = < (1 == 0) >; 1237 eq2 = < (1 == 1) >; 1238 neq1 = < (1 != 0) >; 1239 neq2 = < (1 != 1) >; 1240 lt1 = < (1 < 2) >; 1241 lt2 = < (2 < 2) >; 1242 lt3 = < (3 < 2) >; 1243 lteq1 = < (1 <= 2) >; 1244 lteq2 = < (2 <= 2) >; 1245 lteq3 = < (3 <= 2) >; 1246 gt1 = < (1 > 2) >; 1247 gt2 = < (2 > 2) >; 1248 gt3 = < (3 > 2) >; 1249 gteq1 = < (1 >= 2) >; 1250 gteq2 = < (2 >= 2) >; 1251 gteq3 = < (3 >= 2) >; 1252 lshift = < (2 << 3) >; 1253 rshift = < (16 >> 3) >; 1254 add = < (3 + 4) >; 1255 sub = < (7 - 4) >; 1256 mul = < (3 * 4) >; 1257 div = < (11 / 3) >; 1258 mod = < (11 % 3) >; 1259 unary_minus = < (-3) >; 1260 bitnot = < (~1) >; 1261 not0 = < (!-1) >; 1262 not1 = < (!0) >; 1263 not2 = < (!1) >; 1264 not3 = < (!2) >; 1265 nest = < (((--3) + (-2)) * (--(-2))) >; 1266 char_lits = < ('a' + 'b') >; 1267}; 1268""", 1269""" 1270/dts-v1/; 1271 1272/ { 1273 ter1 = < 0x3 >; 1274 ter2 = < 0x2 >; 1275 ter3 = < 0x1 >; 1276 ter4 = < 0x1 >; 1277 or1 = < 0x0 >; 1278 or2 = < 0x1 >; 1279 or3 = < 0x1 >; 1280 or4 = < 0x1 >; 1281 and1 = < 0x0 >; 1282 and2 = < 0x0 >; 1283 and3 = < 0x0 >; 1284 and4 = < 0x1 >; 1285 bitor = < 0x3 >; 1286 bitxor = < 0x5 >; 1287 bitand = < 0x2 >; 1288 eq1 = < 0x0 >; 1289 eq2 = < 0x1 >; 1290 neq1 = < 0x1 >; 1291 neq2 = < 0x0 >; 1292 lt1 = < 0x1 >; 1293 lt2 = < 0x0 >; 1294 lt3 = < 0x0 >; 1295 lteq1 = < 0x1 >; 1296 lteq2 = < 0x1 >; 1297 lteq3 = < 0x0 >; 1298 gt1 = < 0x0 >; 1299 gt2 = < 0x0 >; 1300 gt3 = < 0x1 >; 1301 gteq1 = < 0x0 >; 1302 gteq2 = < 0x1 >; 1303 gteq3 = < 0x1 >; 1304 lshift = < 0x10 >; 1305 rshift = < 0x2 >; 1306 add = < 0x7 >; 1307 sub = < 0x3 >; 1308 mul = < 0xc >; 1309 div = < 0x3 >; 1310 mod = < 0x2 >; 1311 unary_minus = < 0xfffffffd >; 1312 bitnot = < 0xfffffffe >; 1313 not0 = < 0x0 >; 1314 not1 = < 0x1 >; 1315 not2 = < 0x0 >; 1316 not3 = < 0x0 >; 1317 nest = < 0xfffffffe >; 1318 char_lits = < 0xc3 >; 1319}; 1320""") 1321 1322 verify_error_endswith(""" 1323/dts-v1/; 1324 1325/ { 1326 a = < (1/(-1 + 1)) >; 1327}; 1328""", 1329":4 (column 18): parse error: division by zero") 1330 1331 verify_error_endswith(""" 1332/dts-v1/; 1333 1334/ { 1335 a = < (1%0) >; 1336}; 1337""", 1338":4 (column 11): parse error: division by zero") 1339 1340def test_comment_removal(): 1341 '''Comments should be removed when round-tripped to a str.''' 1342 1343 verify_parse(""" 1344/**//dts-v1//**/;// 1345// 1346// foo 1347/ /**/{// foo 1348x/**/=/* 1349foo 1350*/</**/1/***/>/****/;/**/}/*/**/; 1351""", 1352""" 1353/dts-v1/; 1354 1355/ { 1356 x = < 0x1 >; 1357}; 1358""") 1359 1360def verify_path_is(path, node_name, dt): 1361 '''Verify 'node.name' matches 'node_name' in 'dt'.''' 1362 1363 try: 1364 node = dt.get_node(path) 1365 assert node.name == node_name, f'unexpected path {path}' 1366 except dtlib.DTError: 1367 assert False, f'no node found for path {path}' 1368 1369def verify_path_error(path, msg, dt): 1370 '''Verify that an attempt to get node 'path' from 'dt' raises 1371 a DTError whose str is 'msg'.''' 1372 1373 with dtlib_raises(msg): 1374 dt.get_node(path) 1375 1376def test_get_node(): 1377 '''Test DT.get_node().''' 1378 1379 dt = parse(""" 1380/dts-v1/; 1381 1382/ { 1383 foo { 1384 bar { 1385 }; 1386 }; 1387 1388 baz { 1389 }; 1390}; 1391""") 1392 1393 verify_path_is("/", "/", dt) 1394 verify_path_is("//", "/", dt) 1395 verify_path_is("///", "/", dt) 1396 verify_path_is("/foo", "foo", dt) 1397 verify_path_is("//foo", "foo", dt) 1398 verify_path_is("///foo", "foo", dt) 1399 verify_path_is("/foo/bar", "bar", dt) 1400 verify_path_is("//foo//bar", "bar", dt) 1401 verify_path_is("///foo///bar", "bar", dt) 1402 verify_path_is("/baz", "baz", dt) 1403 1404 verify_path_error( 1405 "", 1406 "no alias '' found -- did you forget the leading '/' in the node path?", 1407 dt) 1408 verify_path_error( 1409 "missing", 1410 "no alias 'missing' found -- did you forget the leading '/' in the node path?", 1411 dt) 1412 verify_path_error( 1413 "/missing", 1414 "component 'missing' in path '/missing' does not exist", 1415 dt) 1416 verify_path_error( 1417 "/foo/missing", 1418 "component 'missing' in path '/foo/missing' does not exist", 1419 dt) 1420 1421 def verify_path_exists(path): 1422 assert dt.has_node(path), f"path '{path}' does not exist" 1423 1424 def verify_path_missing(path): 1425 assert not dt.has_node(path), f"path '{path}' exists" 1426 1427 verify_path_exists("/") 1428 verify_path_exists("/foo") 1429 verify_path_exists("/foo/bar") 1430 1431 verify_path_missing("/missing") 1432 verify_path_missing("/foo/missing") 1433 1434def test_aliases(): 1435 '''Test /aliases''' 1436 1437 dt = parse(""" 1438/dts-v1/; 1439 1440/ { 1441 aliases { 1442 alias1 = &l1; 1443 alias2 = &l2; 1444 alias3 = &{/sub/node3}; 1445 alias4 = &{/node4}; 1446 }; 1447 1448 l1: node1 { 1449 }; 1450 1451 l2: node2 { 1452 }; 1453 1454 sub { 1455 node3 { 1456 }; 1457 }; 1458 1459 node4 { 1460 node5 { 1461 }; 1462 }; 1463}; 1464""") 1465 1466 def verify_alias_target(alias, node_name): 1467 verify_path_is(alias, node_name, dt) 1468 assert alias in dt.alias2node 1469 assert dt.alias2node[alias].name == node_name, f"bad result for {alias}" 1470 1471 verify_alias_target("alias1", "node1") 1472 verify_alias_target("alias2", "node2") 1473 verify_alias_target("alias3", "node3") 1474 verify_path_is("alias4/node5", "node5", dt) 1475 1476 verify_path_error( 1477 "alias4/node5/node6", 1478 "component 'node6' in path 'alias4/node5/node6' does not exist", 1479 dt) 1480 1481 verify_error_matches(""" 1482/dts-v1/; 1483 1484/ { 1485 aliases { 1486 a = [ 00 ]; 1487 }; 1488}; 1489""", 1490"expected property 'a' on /aliases in .*" + 1491re.escape("to be assigned with either 'a = &foo' or 'a = \"/path/to/node\"', not 'a = [ 00 ];'")) 1492 1493 verify_error_matches(r""" 1494/dts-v1/; 1495 1496/ { 1497 aliases { 1498 a = "\xFF"; 1499 }; 1500}; 1501""", 1502re.escape(r"value of property 'a' (b'\xff\x00') on /aliases in ") + 1503".* is not valid UTF-8") 1504 1505 verify_error(""" 1506/dts-v1/; 1507 1508/ { 1509 aliases { 1510 A = "/aliases"; 1511 }; 1512}; 1513""", 1514"/aliases: alias property name 'A' should include only characters from [0-9a-z-]") 1515 1516 verify_error_matches(r""" 1517/dts-v1/; 1518 1519/ { 1520 aliases { 1521 a = "/missing"; 1522 }; 1523}; 1524""", 1525"property 'a' on /aliases in .* points to the non-existent node \"/missing\"") 1526 1527def test_prop_type(): 1528 '''Test Property.type''' 1529 1530 def verify_type(prop, expected): 1531 actual = dt.root.props[prop].type 1532 assert actual == expected, f'{prop} has wrong type' 1533 1534 dt = parse(""" 1535/dts-v1/; 1536 1537/ { 1538 empty; 1539 bytes1 = [ ]; 1540 bytes2 = [ 01 ]; 1541 bytes3 = [ 01 02 ]; 1542 bytes4 = foo: [ 01 bar: 02 ]; 1543 bytes5 = /bits/ 8 < 1 2 3 >; 1544 num = < 1 >; 1545 nums1 = < >; 1546 nums2 = < >, < >; 1547 nums3 = < 1 2 >; 1548 nums4 = < 1 2 >, < 3 >, < 4 >; 1549 string = "foo"; 1550 strings = "foo", "bar"; 1551 path1 = &node; 1552 path2 = &{/node}; 1553 phandle1 = < &node >; 1554 phandle2 = < &{/node} >; 1555 phandles1 = < &node &node >; 1556 phandles2 = < &node >, < &node >; 1557 phandle-and-nums-1 = < &node 1 >; 1558 phandle-and-nums-2 = < &node 1 2 &node 3 4 >; 1559 phandle-and-nums-3 = < &node 1 2 >, < &node 3 4 >; 1560 compound1 = < 1 >, [ 02 ]; 1561 compound2 = "foo", < >; 1562 1563 node: node { 1564 }; 1565}; 1566""") 1567 1568 verify_type("empty", dtlib.Type.EMPTY) 1569 verify_type("bytes1", dtlib.Type.BYTES) 1570 verify_type("bytes2", dtlib.Type.BYTES) 1571 verify_type("bytes3", dtlib.Type.BYTES) 1572 verify_type("bytes4", dtlib.Type.BYTES) 1573 verify_type("bytes5", dtlib.Type.BYTES) 1574 verify_type("num", dtlib.Type.NUM) 1575 verify_type("nums1", dtlib.Type.NUMS) 1576 verify_type("nums2", dtlib.Type.NUMS) 1577 verify_type("nums3", dtlib.Type.NUMS) 1578 verify_type("nums4", dtlib.Type.NUMS) 1579 verify_type("string", dtlib.Type.STRING) 1580 verify_type("strings", dtlib.Type.STRINGS) 1581 verify_type("phandle1", dtlib.Type.PHANDLE) 1582 verify_type("phandle2", dtlib.Type.PHANDLE) 1583 verify_type("phandles1", dtlib.Type.PHANDLES) 1584 verify_type("phandles2", dtlib.Type.PHANDLES) 1585 verify_type("phandle-and-nums-1", dtlib.Type.PHANDLES_AND_NUMS) 1586 verify_type("phandle-and-nums-2", dtlib.Type.PHANDLES_AND_NUMS) 1587 verify_type("phandle-and-nums-3", dtlib.Type.PHANDLES_AND_NUMS) 1588 verify_type("path1", dtlib.Type.PATH) 1589 verify_type("path2", dtlib.Type.PATH) 1590 verify_type("compound1", dtlib.Type.COMPOUND) 1591 verify_type("compound2", dtlib.Type.COMPOUND) 1592 1593def test_prop_type_casting(): 1594 '''Test Property.to_{num,nums,string,strings,node}()''' 1595 1596 dt = parse(r""" 1597/dts-v1/; 1598 1599/ { 1600 u = < 1 >; 1601 s = < 0xFFFFFFFF >; 1602 u8 = /bits/ 8 < 1 >; 1603 u16 = /bits/ 16 < 1 2 >; 1604 u64 = /bits/ 64 < 1 >; 1605 bytes = [ 01 02 03 ]; 1606 empty; 1607 zero = < >; 1608 two_u = < 1 2 >; 1609 two_s = < 0xFFFFFFFF 0xFFFFFFFE >; 1610 three_u = < 1 2 3 >; 1611 three_u_split = < 1 >, < 2 >, < 3 >; 1612 empty_string = ""; 1613 string = "foo\tbar baz"; 1614 invalid_string = "\xff"; 1615 strings = "foo", "bar", "baz"; 1616 invalid_strings = "foo", "\xff", "bar"; 1617 ref = <&{/target}>; 1618 refs = <&{/target} &{/target2}>; 1619 refs2 = <&{/target}>, <&{/target2}>; 1620 path = &{/target}; 1621 manualpath = "/target"; 1622 missingpath = "/missing"; 1623 1624 target { 1625 phandle = < 100 >; 1626 }; 1627 1628 target2 { 1629 }; 1630}; 1631""") 1632 1633 # Test Property.to_num() 1634 1635 def verify_to_num(prop, signed, expected): 1636 signed_str = "a signed" if signed else "an unsigned" 1637 actual = dt.root.props[prop].to_num(signed) 1638 assert actual == expected, \ 1639 f"{prop} has bad {signed_str} numeric value" 1640 1641 def verify_to_num_error_matches(prop, expected_re): 1642 with dtlib_raises(err_matches=expected_re): 1643 dt.root.props[prop].to_num() 1644 1645 verify_to_num("u", False, 1) 1646 verify_to_num("u", True, 1) 1647 verify_to_num("s", False, 0xFFFFFFFF) 1648 verify_to_num("s", True, -1) 1649 1650 verify_to_num_error_matches( 1651 "two_u", 1652 "expected property 'two_u' on / in .* to be assigned with " + 1653 re.escape("'two_u = < (number) >;', not 'two_u = < 0x1 0x2 >;'")) 1654 verify_to_num_error_matches( 1655 "u8", 1656 "expected property 'u8' on / in .* to be assigned with " + 1657 re.escape("'u8 = < (number) >;', not 'u8 = [ 01 ];'")) 1658 verify_to_num_error_matches( 1659 "u16", 1660 "expected property 'u16' on / in .* to be assigned with " + 1661 re.escape("'u16 = < (number) >;', not 'u16 = /bits/ 16 < 0x1 0x2 >;'")) 1662 verify_to_num_error_matches( 1663 "u64", 1664 "expected property 'u64' on / in .* to be assigned with " + 1665 re.escape("'u64 = < (number) >;', not 'u64 = /bits/ 64 < 0x1 >;'")) 1666 verify_to_num_error_matches( 1667 "string", 1668 "expected property 'string' on / in .* to be assigned with " + 1669 re.escape("'string = < (number) >;', not 'string = \"foo\\tbar baz\";'")) 1670 1671 # Test Property.to_nums() 1672 1673 def verify_to_nums(prop, signed, expected): 1674 signed_str = "signed" if signed else "unsigned" 1675 actual = dt.root.props[prop].to_nums(signed) 1676 assert actual == expected, \ 1677 f"'{prop}' gives the wrong {signed_str} numbers" 1678 1679 def verify_to_nums_error_matches(prop, expected_re): 1680 with dtlib_raises(err_matches=expected_re): 1681 dt.root.props[prop].to_nums() 1682 1683 verify_to_nums("zero", False, []) 1684 verify_to_nums("u", False, [1]) 1685 verify_to_nums("two_u", False, [1, 2]) 1686 verify_to_nums("two_u", True, [1, 2]) 1687 verify_to_nums("two_s", False, [0xFFFFFFFF, 0xFFFFFFFE]) 1688 verify_to_nums("two_s", True, [-1, -2]) 1689 verify_to_nums("three_u", False, [1, 2, 3]) 1690 verify_to_nums("three_u_split", False, [1, 2, 3]) 1691 1692 verify_to_nums_error_matches( 1693 "empty", 1694 "expected property 'empty' on / in .* to be assigned with " + 1695 re.escape("'empty = < (number) (number) ... >;', not 'empty;'")) 1696 verify_to_nums_error_matches( 1697 "string", 1698 "expected property 'string' on / in .* to be assigned with " + 1699 re.escape("'string = < (number) (number) ... >;', ") + 1700 re.escape("not 'string = \"foo\\tbar baz\";'")) 1701 1702 # Test Property.to_bytes() 1703 1704 def verify_to_bytes(prop, expected): 1705 actual = dt.root.props[prop].to_bytes() 1706 assert actual == expected, f"'{prop}' gives the wrong bytes" 1707 1708 def verify_to_bytes_error_matches(prop, expected_re): 1709 with dtlib_raises(err_matches=expected_re): 1710 dt.root.props[prop].to_bytes() 1711 1712 verify_to_bytes("u8", b"\x01") 1713 verify_to_bytes("bytes", b"\x01\x02\x03") 1714 1715 verify_to_bytes_error_matches( 1716 "u16", 1717 "expected property 'u16' on / in .* to be assigned with " + 1718 re.escape("'u16 = [ (byte) (byte) ... ];', ") + 1719 re.escape("not 'u16 = /bits/ 16 < 0x1 0x2 >;'")) 1720 verify_to_bytes_error_matches( 1721 "empty", 1722 "expected property 'empty' on / in .* to be assigned with " + 1723 re.escape("'empty = [ (byte) (byte) ... ];', not 'empty;'")) 1724 1725 # Test Property.to_string() 1726 1727 def verify_to_string(prop, expected): 1728 actual = dt.root.props[prop].to_string() 1729 assert actual == expected, f"'{prop}' to_string gives the wrong string" 1730 1731 def verify_to_string_error_matches(prop, expected_re): 1732 with dtlib_raises(err_matches=expected_re): 1733 dt.root.props[prop].to_string() 1734 1735 verify_to_string("empty_string", "") 1736 verify_to_string("string", "foo\tbar baz") 1737 1738 verify_to_string_error_matches( 1739 "u", 1740 "expected property 'u' on / in .* to be assigned with " + 1741 re.escape("'u = \"string\";', not 'u = < 0x1 >;'")) 1742 verify_to_string_error_matches( 1743 "strings", 1744 "expected property 'strings' on / in .* to be assigned with " + 1745 re.escape("'strings = \"string\";', ")+ 1746 re.escape("not 'strings = \"foo\", \"bar\", \"baz\";'")) 1747 verify_to_string_error_matches( 1748 "invalid_string", 1749 re.escape(r"value of property 'invalid_string' (b'\xff\x00') on / ") + 1750 "in .* is not valid UTF-8") 1751 1752 # Test Property.to_strings() 1753 1754 def verify_to_strings(prop, expected): 1755 actual = dt.root.props[prop].to_strings() 1756 assert actual == expected, f"'{prop}' to_strings gives the wrong value" 1757 1758 def verify_to_strings_error_matches(prop, expected_re): 1759 with dtlib_raises(err_matches=expected_re): 1760 dt.root.props[prop].to_strings() 1761 1762 verify_to_strings("empty_string", [""]) 1763 verify_to_strings("string", ["foo\tbar baz"]) 1764 verify_to_strings("strings", ["foo", "bar", "baz"]) 1765 1766 verify_to_strings_error_matches( 1767 "u", 1768 "expected property 'u' on / in .* to be assigned with " + 1769 re.escape("'u = \"string\", \"string\", ... ;', not 'u = < 0x1 >;'")) 1770 verify_to_strings_error_matches( 1771 "invalid_strings", 1772 "value of property 'invalid_strings' " + 1773 re.escape(r"(b'foo\x00\xff\x00bar\x00') on / in ") + 1774 ".* is not valid UTF-8") 1775 1776 # Test Property.to_node() 1777 1778 def verify_to_node(prop, path): 1779 actual = dt.root.props[prop].to_node().path 1780 assert actual == path, f"'{prop}' points at wrong path" 1781 1782 def verify_to_node_error_matches(prop, expected_re): 1783 with dtlib_raises(err_matches=expected_re): 1784 dt.root.props[prop].to_node() 1785 1786 verify_to_node("ref", "/target") 1787 1788 verify_to_node_error_matches( 1789 "u", 1790 "expected property 'u' on / in .* to be assigned with " + 1791 re.escape("'u = < &foo >;', not 'u = < 0x1 >;'")) 1792 verify_to_node_error_matches( 1793 "string", 1794 "expected property 'string' on / in .* to be assigned with " + 1795 re.escape("'string = < &foo >;', not 'string = \"foo\\tbar baz\";'")) 1796 1797 # Test Property.to_nodes() 1798 1799 def verify_to_nodes(prop, paths): 1800 actual = [node.path for node in dt.root.props[prop].to_nodes()] 1801 assert actual == paths, f"'{prop} gives wrong node paths" 1802 1803 def verify_to_nodes_error_matches(prop, expected_re): 1804 with dtlib_raises(err_matches=expected_re): 1805 dt.root.props[prop].to_nodes() 1806 1807 verify_to_nodes("zero", []) 1808 verify_to_nodes("ref", ["/target"]) 1809 verify_to_nodes("refs", ["/target", "/target2"]) 1810 verify_to_nodes("refs2", ["/target", "/target2"]) 1811 1812 verify_to_nodes_error_matches( 1813 "u", 1814 "expected property 'u' on / in .* to be assigned with " + 1815 re.escape("'u = < &foo &bar ... >;', not 'u = < 0x1 >;'")) 1816 verify_to_nodes_error_matches( 1817 "string", 1818 "expected property 'string' on / in .* to be assigned with " + 1819 re.escape("'string = < &foo &bar ... >;', ") + 1820 re.escape("not 'string = \"foo\\tbar baz\";'")) 1821 1822 # Test Property.to_path() 1823 1824 def verify_to_path(prop, path): 1825 actual = dt.root.props[prop].to_path().path 1826 assert actual == path, f"'{prop} gives the wrong path" 1827 1828 def verify_to_path_error_matches(prop, expected_re): 1829 with dtlib_raises(err_matches=expected_re): 1830 dt.root.props[prop].to_path() 1831 1832 verify_to_path("path", "/target") 1833 verify_to_path("manualpath", "/target") 1834 1835 verify_to_path_error_matches( 1836 "u", 1837 "expected property 'u' on / in .* to be assigned with either " + 1838 re.escape("'u = &foo' or 'u = \"/path/to/node\"', not 'u = < 0x1 >;'")) 1839 verify_to_path_error_matches( 1840 "missingpath", 1841 "property 'missingpath' on / in .* points to the non-existent node " 1842 '"/missing"') 1843 1844 # Test top-level to_num() and to_nums() 1845 1846 def verify_raw_to_num(fn, prop, length, signed, expected): 1847 actual = fn(dt.root.props[prop].value, length, signed) 1848 assert actual == expected, \ 1849 f"{fn.__name__}(<{prop}>, {length}, {signed}) gives wrong value" 1850 1851 def verify_raw_to_num_error(fn, data, length, msg): 1852 # We're using this instead of dtlib_raises() for the extra 1853 # context we get from the assertion below. 1854 with pytest.raises(dtlib.DTError) as e: 1855 fn(data, length) 1856 assert str(e.value) == msg, \ 1857 (f"{fn.__name__}() called with data='{data}', length='{length}' " 1858 "gives the wrong error") 1859 1860 verify_raw_to_num(dtlib.to_num, "u", None, False, 1) 1861 verify_raw_to_num(dtlib.to_num, "u", 4, False, 1) 1862 verify_raw_to_num(dtlib.to_num, "s", None, False, 0xFFFFFFFF) 1863 verify_raw_to_num(dtlib.to_num, "s", None, True, -1) 1864 verify_raw_to_num(dtlib.to_nums, "empty", 4, False, []) 1865 verify_raw_to_num(dtlib.to_nums, "u16", 2, False, [1, 2]) 1866 verify_raw_to_num(dtlib.to_nums, "two_s", 4, False, [0xFFFFFFFF, 0xFFFFFFFE]) 1867 verify_raw_to_num(dtlib.to_nums, "two_s", 4, True, [-1, -2]) 1868 1869 verify_raw_to_num_error(dtlib.to_num, 0, 0, "'0' has type 'int', expected 'bytes'") 1870 verify_raw_to_num_error(dtlib.to_num, b"", 0, "'length' must be greater than zero, was 0") 1871 verify_raw_to_num_error(dtlib.to_num, b"foo", 2, "b'foo' is 3 bytes long, expected 2") 1872 verify_raw_to_num_error(dtlib.to_nums, 0, 0, "'0' has type 'int', expected 'bytes'") 1873 verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'length' must be greater than zero, was 0") 1874 verify_raw_to_num_error(dtlib.to_nums, b"foooo", 2, "b'foooo' is 5 bytes long, expected a length that's a a multiple of 2") 1875 1876def test_duplicate_labels(): 1877 ''' 1878 It is an error to duplicate labels in most conditions, but there 1879 are some exceptions where it's OK. 1880 ''' 1881 1882 verify_error(""" 1883/dts-v1/; 1884 1885/ { 1886 sub1 { 1887 label: foo { 1888 }; 1889 }; 1890 1891 sub2 { 1892 label: bar { 1893 }; 1894 }; 1895}; 1896""", 1897"Label 'label' appears on /sub1/foo and on /sub2/bar") 1898 1899 verify_error(""" 1900/dts-v1/; 1901 1902/ { 1903 sub { 1904 label: foo { 1905 }; 1906 }; 1907}; 1908/ { 1909 sub { 1910 label: bar { 1911 }; 1912 }; 1913}; 1914""", 1915"Label 'label' appears on /sub/bar and on /sub/foo") 1916 1917 verify_error(""" 1918/dts-v1/; 1919 1920/ { 1921 foo: a = < 0 >; 1922 foo: node { 1923 }; 1924}; 1925""", 1926"Label 'foo' appears on /node and on property 'a' of node /") 1927 1928 verify_error(""" 1929/dts-v1/; 1930 1931/ { 1932 foo: a = < 0 >; 1933 node { 1934 foo: b = < 0 >; 1935 }; 1936}; 1937""", 1938"Label 'foo' appears on property 'a' of node / and on property 'b' of node /node") 1939 1940 verify_error(""" 1941/dts-v1/; 1942 1943/ { 1944 foo: a = foo: < 0 >; 1945}; 1946""", 1947"Label 'foo' appears in the value of property 'a' of node / and on property 'a' of node /") 1948 1949 # Giving the same label twice for the same node is fine 1950 verify_parse(""" 1951/dts-v1/; 1952 1953/ { 1954 sub { 1955 label: foo { 1956 }; 1957 }; 1958}; 1959/ { 1960 1961 sub { 1962 label: foo { 1963 }; 1964 }; 1965}; 1966""", 1967""" 1968/dts-v1/; 1969 1970/ { 1971 sub { 1972 label: foo { 1973 }; 1974 }; 1975}; 1976""") 1977 1978 # Duplicate labels are fine if one of the nodes is deleted 1979 verify_parse(""" 1980/dts-v1/; 1981 1982/ { 1983 label: foo { 1984 }; 1985 label: bar { 1986 }; 1987}; 1988 1989/delete-node/ &{/bar}; 1990""", 1991""" 1992/dts-v1/; 1993 1994/ { 1995 label: foo { 1996 }; 1997}; 1998""") 1999 2000 # 2001 # Test overriding/deleting a property with references 2002 # 2003 2004 verify_parse(""" 2005/dts-v1/; 2006 2007/ { 2008 x = &foo, < &foo >; 2009 y = &foo, < &foo >; 2010 foo: foo { 2011 }; 2012}; 2013 2014/ { 2015 x = < 1 >; 2016 /delete-property/ y; 2017}; 2018""", 2019""" 2020/dts-v1/; 2021 2022/ { 2023 x = < 0x1 >; 2024 foo: foo { 2025 }; 2026}; 2027""") 2028 2029 # 2030 # Test self-referential node 2031 # 2032 2033 verify_parse(""" 2034/dts-v1/; 2035 2036/ { 2037 label: foo { 2038 x = &{/foo}, &label, < &label >; 2039 }; 2040}; 2041""", 2042""" 2043/dts-v1/; 2044 2045/ { 2046 label: foo { 2047 x = &{/foo}, &label, < &label >; 2048 phandle = < 0x1 >; 2049 }; 2050}; 2051""") 2052 2053 # 2054 # Test /memreserve/ 2055 # 2056 2057 dt = verify_parse(""" 2058/dts-v1/; 2059 2060l1: l2: /memreserve/ (1 + 1) (2 * 2); 2061/memreserve/ 0x100 0x200; 2062 2063/ { 2064}; 2065""", 2066""" 2067/dts-v1/; 2068 2069l1: l2: /memreserve/ 0x0000000000000002 0x0000000000000004; 2070/memreserve/ 0x0000000000000100 0x0000000000000200; 2071 2072/ { 2073}; 2074""") 2075 2076 expected = [(["l1", "l2"], 2, 4), ([], 0x100, 0x200)] 2077 assert dt.memreserves == expected 2078 2079 verify_error_endswith(""" 2080/dts-v1/; 2081 2082foo: / { 2083}; 2084""", 2085":3 (column 6): parse error: expected /memreserve/ after labels at beginning of file") 2086 2087def test_reprs(): 2088 '''Test the __repr__() functions.''' 2089 2090 dts = """ 2091/dts-v1/; 2092 2093/ { 2094 x = < 0 >; 2095 sub { 2096 y = < 1 >; 2097 }; 2098}; 2099""" 2100 2101 dt = parse(dts, include_path=("foo", "bar")) 2102 2103 assert re.fullmatch(r"DT\(filename='.*', include_path=.'foo', 'bar'.\)", 2104 repr(dt)) 2105 assert re.fullmatch("<Property 'x' at '/' in '.*'>", 2106 repr(dt.root.props["x"])) 2107 assert re.fullmatch("<Node /sub in '.*'>", 2108 repr(dt.root.nodes["sub"])) 2109 2110 dt = parse(dts, include_path=iter(("foo", "bar"))) 2111 2112 assert re.fullmatch(r"DT\(filename='.*', include_path=.'foo', 'bar'.\)", 2113 repr(dt)) 2114 2115def test_names(): 2116 '''Tests for node/property names.''' 2117 2118 verify_parse(r""" 2119/dts-v1/; 2120 2121/ { 2122 // A leading \ is accepted but ignored in node/propert names 2123 \aA0,._+*#?- = &_, &{/aA0,._+@-}; 2124 2125 // Names that overlap with operators and integer literals 2126 2127 + = [ 00 ]; 2128 * = [ 02 ]; 2129 - = [ 01 ]; 2130 ? = [ 03 ]; 2131 0 = [ 04 ]; 2132 0x123 = [ 05 ]; 2133 2134 // Node names are more restrictive than property names. 2135 _: \aA0,._+@- { 2136 }; 2137 2138 0 { 2139 }; 2140}; 2141""", 2142""" 2143/dts-v1/; 2144 2145/ { 2146 aA0,._+*#?- = &_, &{/aA0,._+@-}; 2147 + = [ 00 ]; 2148 * = [ 02 ]; 2149 - = [ 01 ]; 2150 ? = [ 03 ]; 2151 0 = [ 04 ]; 2152 0x123 = [ 05 ]; 2153 _: aA0,._+@- { 2154 }; 2155 0 { 2156 }; 2157}; 2158""") 2159 2160 verify_error_endswith(r""" 2161/dts-v1/; 2162 2163/ { 2164 foo@3; 2165}; 2166""", 2167":4 (column 7): parse error: '@' is only allowed in node names") 2168 2169 verify_error_endswith(r""" 2170/dts-v1/; 2171 2172/ { 2173 foo@3 = < 0 >; 2174}; 2175""", 2176":4 (column 8): parse error: '@' is only allowed in node names") 2177 2178 verify_error_endswith(r""" 2179/dts-v1/; 2180 2181/ { 2182 foo@2@3 { 2183 }; 2184}; 2185""", 2186":4 (column 10): parse error: multiple '@' in node name") 2187 2188def test_dense_input(): 2189 ''' 2190 Test that a densely written DTS input round-trips to something 2191 readable. 2192 ''' 2193 2194 verify_parse(""" 2195/dts-v1/;/{l1:l2:foo{l3:l4:bar{l5:x=l6:/bits/8<l7:1 l8:2>l9:,[03],"a";};};}; 2196""", 2197""" 2198/dts-v1/; 2199 2200/ { 2201 l1: l2: foo { 2202 l3: l4: bar { 2203 l5: x = l6: [ l7: 01 l8: 02 l9: ], [ 03 ], "a"; 2204 }; 2205 }; 2206}; 2207""") 2208 2209def test_misc(): 2210 '''Test miscellaneous errors and non-errors.''' 2211 2212 verify_error_endswith("", ":1 (column 1): parse error: expected '/dts-v1/;' at start of file") 2213 2214 verify_error_endswith(""" 2215/dts-v1/; 2216""", 2217":2 (column 1): parse error: no root node defined") 2218 2219 verify_error_endswith(""" 2220/dts-v1/; /plugin/; 2221""", 2222":1 (column 11): parse error: /plugin/ is not supported") 2223 2224 verify_error_endswith(""" 2225/dts-v1/; 2226 2227/ { 2228 foo: foo { 2229 }; 2230}; 2231 2232// Only one label supported before label references at the top level 2233l1: l2: &foo { 2234}; 2235""", 2236":9 (column 5): parse error: expected label reference (&foo)") 2237 2238 verify_error_endswith(""" 2239/dts-v1/; 2240 2241/ { 2242 foo: {}; 2243}; 2244""", 2245":4 (column 14): parse error: expected node or property name") 2246 2247 # Multiple /dts-v1/ at the start of a file is fine 2248 verify_parse(""" 2249/dts-v1/; 2250/dts-v1/; 2251 2252/ { 2253}; 2254""", 2255""" 2256/dts-v1/; 2257 2258/ { 2259}; 2260""") 2261 2262def test_dangling_alias(): 2263 dt = parse(''' 2264/dts-v1/; 2265 2266/ { 2267 aliases { foo = "/missing"; }; 2268}; 2269''', force=True) 2270 assert dt.get_node('/aliases').props['foo'].to_string() == '/missing' 2271 2272def test_duplicate_nodes(): 2273 # Duplicate node names in the same {} block are an error in dtc, 2274 # so we want to reproduce the same behavior. But we also need to 2275 # make sure that doesn't break overlays modifying the same node. 2276 2277 verify_error_endswith(""" 2278/dts-v1/; 2279 2280/ { 2281 foo {}; 2282 foo {}; 2283}; 2284""", "/foo: duplicate node name") 2285 2286 verify_parse(""" 2287/dts-v1/; 2288 2289/ { 2290 foo { prop = <3>; }; 2291}; 2292/ { 2293 foo { prop = <4>; }; 2294}; 2295""", 2296""" 2297/dts-v1/; 2298 2299/ { 2300 foo { 2301 prop = < 0x4 >; 2302 }; 2303}; 2304""") 2305 2306def test_deepcopy(): 2307 dt = parse(''' 2308/dts-v1/; 2309 2310memreservelabel: /memreserve/ 0xdeadbeef 0x4000; 2311 2312/ { 2313 aliases { 2314 foo = &nodelabel; 2315 }; 2316 rootprop_label: rootprop = prop_offset0: <0x12345678 prop_offset4: 0x0>; 2317 nodelabel: node@1234 { 2318 nodeprop = <3>; 2319 subnode { 2320 ref-to-node = <&nodelabel>; 2321 }; 2322 }; 2323}; 2324''') 2325 dt_copy = deepcopy(dt) 2326 assert dt_copy.filename == dt.filename 2327 2328 # dt_copy.root checks: 2329 root_copy = dt_copy.root 2330 assert root_copy is not dt.root 2331 assert root_copy.parent is None 2332 assert root_copy.dt is dt_copy 2333 assert root_copy.labels == [] 2334 assert root_copy.labels is not dt.root.labels 2335 2336 # dt_copy.memreserves checks: 2337 assert dt_copy.memreserves == [ 2338 (set(['memreservelabel']), 0xdeadbeef, 0x4000) 2339 ] 2340 assert dt_copy.memreserves is not dt.memreserves 2341 2342 # Miscellaneous dt_copy node and property checks: 2343 assert 'rootprop' in root_copy.props 2344 rootprop_copy = root_copy.props['rootprop'] 2345 assert rootprop_copy is not dt.root.props['rootprop'] 2346 assert rootprop_copy.name == 'rootprop' 2347 assert rootprop_copy.value == b'\x12\x34\x56\x78\0\0\0\0' 2348 assert rootprop_copy.type == dtlib.Type.NUMS 2349 assert rootprop_copy.labels == ['rootprop_label'] 2350 assert rootprop_copy.labels is not dt.root.props['rootprop'].labels 2351 assert rootprop_copy.offset_labels == { 2352 'prop_offset0': 0, 2353 'prop_offset4': 4, 2354 } 2355 assert rootprop_copy.offset_labels is not \ 2356 dt.root.props['rootprop'].offset_labels 2357 assert rootprop_copy.node is root_copy 2358 2359 assert dt_copy.has_node('/node@1234') 2360 node_copy = dt_copy.get_node('/node@1234') 2361 assert node_copy is not dt.get_node('/node@1234') 2362 assert node_copy.labels == ['nodelabel'] 2363 assert node_copy.labels is not dt.get_node('/node@1234').labels 2364 assert node_copy.name == 'node@1234' 2365 assert node_copy.unit_addr == '1234' 2366 assert node_copy.path == '/node@1234' 2367 assert set(node_copy.props.keys()) == set(['nodeprop', 'phandle']) 2368 assert node_copy.props is not dt.get_node('/node@1234').props 2369 assert node_copy.props['nodeprop'].name == 'nodeprop' 2370 assert node_copy.props['nodeprop'].labels == [] 2371 assert node_copy.props['nodeprop'].offset_labels == {} 2372 assert node_copy.props['nodeprop'].node is node_copy 2373 assert node_copy.dt is dt_copy 2374 2375 assert 'subnode' in node_copy.nodes 2376 subnode_copy = node_copy.nodes['subnode'] 2377 assert subnode_copy is not dt.get_node('/node@1234/subnode') 2378 assert subnode_copy.parent is node_copy 2379 2380 # dt_copy.label2prop and .label2prop_offset checks: 2381 assert 'rootprop_label' in dt_copy.label2prop 2382 assert dt_copy.label2prop['rootprop_label'] is rootprop_copy 2383 assert list(dt_copy.label2prop_offset.keys()) == ['prop_offset0', 2384 'prop_offset4'] 2385 assert dt_copy.label2prop_offset['prop_offset4'][0] is rootprop_copy 2386 assert dt_copy.label2prop_offset['prop_offset4'][1] == 4 2387 2388 # dt_copy.foo2node checks: 2389 def check_node_lookup_table(attr_name): 2390 original = getattr(dt, attr_name) 2391 copy = getattr(dt_copy, attr_name) 2392 assert original is not copy 2393 assert list(original.keys()) == list(copy.keys()) 2394 assert all([original_node.path == copy_node.path and 2395 original_node is not copy_node 2396 for original_node, copy_node in 2397 zip(original.values(), copy.values())]) 2398 2399 check_node_lookup_table('alias2node') 2400 check_node_lookup_table('label2node') 2401 check_node_lookup_table('phandle2node') 2402 2403 assert list(dt_copy.alias2node.keys()) == ['foo'] 2404 assert dt_copy.alias2node['foo'] is node_copy 2405 2406 assert list(dt_copy.label2node.keys()) == ['nodelabel'] 2407 assert dt_copy.label2node['nodelabel'] is node_copy 2408 2409 assert dt_copy.phandle2node 2410 # This is a little awkward because of the way dtlib allocates 2411 # phandles. 2412 phandle2node_copy_values = set(dt_copy.phandle2node.values()) 2413 assert node_copy in phandle2node_copy_values 2414 for node in dt.node_iter(): 2415 assert node not in phandle2node_copy_values 2416 2417def test_move_node(): 2418 # Test cases for DT.move_node(). 2419 2420 dt = parse(''' 2421/dts-v1/; 2422 2423/ { 2424 aliases { 2425 parent-alias = &parent_label; 2426 }; 2427 parent_label: parent { 2428 child {}; 2429 }; 2430 bar { 2431 shouldbechosen { 2432 foo = "bar"; 2433 }; 2434 }; 2435}; 2436''') 2437 parent = dt.get_node('/parent') 2438 child = dt.get_node('/parent/child') 2439 2440 dt.move_node(parent, '/newpath') 2441 2442 assert parent.path == '/newpath' 2443 assert child.path == '/newpath/child' 2444 assert child.parent is parent 2445 assert child.parent is dt.get_node('/newpath') 2446 assert dt.get_node('parent-alias') is parent 2447 assert dt.label2node['parent_label'] is parent 2448 2449 assert not dt.has_node('/chosen') 2450 dt.move_node(dt.get_node('/bar/shouldbechosen'), '/chosen') 2451 assert dt.has_node('/chosen') 2452 assert 'foo' in dt.get_node('/chosen').props 2453 2454 with dtlib_raises("the root node can't be moved"): 2455 dt.move_node(dt.root, '/somewhere/else') 2456 2457 with dtlib_raises("can't move '/newpath' to '/aliases': " 2458 "destination node exists"): 2459 dt.move_node(parent, '/aliases') 2460 2461 with dtlib_raises("path 'xyz' doesn't start with '/'"): 2462 dt.move_node(parent, 'xyz') 2463 2464 with dtlib_raises("new path '/ invalid': bad character ' '"): 2465 dt.move_node(parent, '/ invalid') 2466 2467 with dtlib_raises("can't move '/newpath' to '/foo/bar': " 2468 "parent node '/foo' doesn't exist"): 2469 dt.move_node(parent, '/foo/bar') 2470