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 >, &sub;
888	/delete-property/ missing;
889	/delete-property/ delete;
890	sub: sub {
891		y = < &sub >, &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 >, &sub;
928		};
929		/delete-node/ sub2;
930	};
931
932	sub3: sub3 {
933		x = < &sub >, &sub;
934	};
935
936	sub4 {
937		x = < &sub >, &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 >, &sub;
959
960	sub1 {
961		x = < &sub >, &sub;
962	};
963	sub2: sub2 {
964		x = < &sub >, &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