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