# Copyright (c) 2019, Nordic Semiconductor # SPDX-License-Identifier: BSD-3-Clause import contextlib import os import re import tempfile from copy import deepcopy from typing import Optional import pytest from devicetree import dtlib # Test suite for dtlib.py. # # Run it using pytest (https://docs.pytest.org/en/stable/usage.html): # # $ pytest tests/test_dtlib.py # # Extra options you can pass to pytest for debugging: # # - to stop on the first failure with shorter traceback output, # use '-x --tb=native' # - to drop into a debugger on failure, use '--pdb' # - to run a particular test function or functions, use # '-k test_function_pattern_goes_here' def parse(dts, include_path=(), **kwargs): '''Parse a DTS string 'dts', using the given include path. Any kwargs are passed on to DT().''' fd, path = tempfile.mkstemp(prefix='pytest-', suffix='.dts') try: os.write(fd, dts.encode('utf-8')) return dtlib.DT(path, include_path, **kwargs) finally: os.close(fd) os.unlink(path) def verify_parse(dts, expected, include_path=()): '''Like parse(), but also verifies that the parsed DT object's string representation is expected[1:-1]. The [1:] is so that the first line can be put on a separate line after triple quotes, as is done below.''' dt = parse(dts[1:], include_path) actual = str(dt) expected = expected[1:-1] assert actual == expected, f'unexpected round-trip on {dts}' return dt def verify_error(dts, expected_msg): '''Verify that parsing 'dts' results in a DTError with the given error message 'msg'. The message must match exactly.''' with dtlib_raises(expected_msg): parse(dts[1:]) def verify_error_endswith(dts, expected_msg): ''' Like verify_error(), but checks the message ends with 'expected_msg' instead of checking for strict equality. ''' with dtlib_raises(err_endswith=expected_msg): parse(dts[1:]) def verify_error_matches(dts, expected_re): ''' Like verify_error(), but checks the message fully matches regular expression 'expected_re' instead of checking for strict equality. ''' with dtlib_raises(err_matches=expected_re): parse(dts[1:]) @contextlib.contextmanager def temporary_chdir(dirname): '''A context manager that changes directory to 'dirname'. The current working directory is unconditionally returned to its present location after the context manager exits. ''' here = os.getcwd() try: os.chdir(dirname) yield finally: os.chdir(here) @contextlib.contextmanager def dtlib_raises(err: Optional[str] = None, err_endswith: Optional[str] = None, err_matches: Optional[str] = None): '''A context manager for running a block of code that should raise DTError. Exactly one of the arguments 'err', 'err_endswith', and 'err_matches' must be given. The semantics are: - err: error message must be exactly this - err_endswith: error message must end with this - err_matches: error message must match this regular expression ''' assert sum([bool(err), bool(err_endswith), bool(err_matches)]) == 1 with pytest.raises(dtlib.DTError) as e: yield actual_err = str(e.value) if err: assert actual_err == err elif err_endswith: assert actual_err.endswith(err_endswith) else: assert re.fullmatch(err_matches, actual_err), \ f'actual message:\n{actual_err!r}\n' \ f'does not match:\n{err_matches!r}' def test_invalid_nodenames(): # Regression test that verifies node names are not matched against # the more permissive set of rules used for property names. verify_error_endswith(""" /dts-v1/; / { node? {}; }; """, "/node?: bad character '?' in node name") def test_cell_parsing(): '''Miscellaneous properties containing zero or more cells''' verify_parse(""" /dts-v1/; / { a; b = < >; c = [ ]; d = < 10 20 >; e = < 0U 1L 2UL 3LL 4ULL >; f = < 0x10 0x20 >; g = < 010 020 >; h = /bits/ 8 < 0x10 0x20 (-1) >; i = /bits/ 16 < 0x10 0x20 (-1) >; j = /bits/ 32 < 0x10 0x20 (-1) >; k = /bits/ 64 < 0x10 0x20 (-1) >; l = < 'a' 'b' 'c' >; }; """, """ /dts-v1/; / { a; b; c; d = < 0xa 0x14 >; e = < 0x0 0x1 0x2 0x3 0x4 >; f = < 0x10 0x20 >; g = < 0x8 0x10 >; h = [ 10 20 FF ]; i = /bits/ 16 < 0x10 0x20 0xffff >; j = < 0x10 0x20 0xffffffff >; k = /bits/ 64 < 0x10 0x20 0xffffffffffffffff >; l = < 0x61 0x62 0x63 >; }; """) verify_error_endswith(""" /dts-v1/; / { a = /bits/ 16 < 0x10000 >; }; """, ":4 (column 18): parse error: 65536 does not fit in 16 bits") verify_error_endswith(""" /dts-v1/; / { a = < 0x100000000 >; }; """, ":4 (column 8): parse error: 4294967296 does not fit in 32 bits") verify_error_endswith(""" /dts-v1/; / { a = /bits/ 128 < 0 >; }; """, ":4 (column 13): parse error: expected 8, 16, 32, or 64") def test_bytes_parsing(): '''Properties with byte array values''' verify_parse(""" /dts-v1/; / { a = [ ]; b = [ 12 34 ]; c = [ 1234 ]; }; """, """ /dts-v1/; / { a; b = [ 12 34 ]; c = [ 12 34 ]; }; """) verify_error_endswith(""" /dts-v1/; / { a = [ 123 ]; }; """, ":4 (column 10): parse error: expected two-digit byte or ']'") def test_string_parsing(): '''Properties with string values''' verify_parse(r""" /dts-v1/; / { a = ""; b = "ABC"; c = "\\\"\xab\377\a\b\t\n\v\f\r"; }; """, r""" /dts-v1/; / { a = ""; b = "ABC"; c = "\\\"\xab\xff\a\b\t\n\v\f\r"; }; """) verify_error_endswith(r""" /dts-v1/; / { a = "\400"; }; """, ":4 (column 6): parse error: octal escape out of range (> 255)") def test_char_literal_parsing(): '''Properties with character literal values''' verify_parse(r""" /dts-v1/; / { a = < '\'' >; b = < '\x12' >; }; """, """ /dts-v1/; / { a = < 0x27 >; b = < 0x12 >; }; """) verify_error_endswith(""" /dts-v1/; / { // Character literals are not allowed at the top level a = 'x'; }; """, ":5 (column 6): parse error: malformed value") verify_error_endswith(""" /dts-v1/; / { a = < '' >; }; """, ":4 (column 7): parse error: character literals must be length 1") verify_error_endswith(""" /dts-v1/; / { a = < '12' >; }; """, ":4 (column 7): parse error: character literals must be length 1") def test_incbin(tmp_path): '''Test /incbin/, an undocumented feature that allows for binary file inclusion. https://github.com/dgibson/dtc/commit/e37ec7d5889fa04047daaa7a4ff55150ed7954d4''' open(tmp_path / "tmp_bin", "wb").write(b"\00\01\02\03") verify_parse(f""" /dts-v1/; / {{ a = /incbin/ ("{tmp_path}/tmp_bin"); b = /incbin/ ("{tmp_path}/tmp_bin", 1, 1); c = /incbin/ ("{tmp_path}/tmp_bin", 1, 2); }}; """, """ /dts-v1/; / { a = [ 00 01 02 03 ]; b = [ 01 ]; c = [ 01 02 ]; }; """) verify_parse(""" /dts-v1/; / { a = /incbin/ ("tmp_bin"); }; """, """ /dts-v1/; / { a = [ 00 01 02 03 ]; }; """, include_path=(tmp_path,)) verify_error_endswith(r""" /dts-v1/; / { a = /incbin/ ("missing"); }; """, ":4 (column 25): parse error: 'missing' could not be found") def test_node_merging(): ''' Labels and properties specified for the same node in different statements should be merged. ''' verify_parse(""" /dts-v1/; / { l1: l2: l1: foo { foo1 = [ 01 ]; l4: l5: bar { bar1 = [ 01 ]; }; }; }; l3: &l1 { foo2 = [ 02 ]; l6: l7: bar { bar2 = [ 02 ]; }; }; &l3 { foo3 = [ 03 ]; }; &{/foo} { foo4 = [ 04 ]; }; &{/foo/bar} { bar3 = [ 03 ]; l8: baz {}; }; / { }; / { top = [ 01 ]; }; """, """ /dts-v1/; / { top = [ 01 ]; l1: l2: l3: foo { foo1 = [ 01 ]; foo2 = [ 02 ]; foo3 = [ 03 ]; foo4 = [ 04 ]; l4: l5: l6: l7: bar { bar1 = [ 01 ]; bar2 = [ 02 ]; bar3 = [ 03 ]; l8: baz { }; }; }; }; """) verify_error_endswith(""" /dts-v1/; / { }; &missing { }; """, ":6 (column 1): parse error: undefined node label 'missing'") verify_error_endswith(""" /dts-v1/; / { }; &{foo} { }; """, ":6 (column 1): parse error: node path 'foo' does not start with '/'") verify_error_endswith(""" /dts-v1/; / { }; &{/foo} { }; """, ":6 (column 1): parse error: component 'foo' in path '/foo' does not exist") def test_property_labels(): '''Like nodes, properties can have labels too.''' def verify_label2prop(label, expected): actual = dt.label2prop[label].name assert actual == expected, f"label '{label}' mapped to wrong property" dt = verify_parse(""" /dts-v1/; / { a; b; l2: c; l4: l5: l5: l4: d = < 0 >; }; / { l1: b; l3: c; l6: d; }; """, """ /dts-v1/; / { a; l1: b; l2: l3: c; l4: l5: l6: d = < 0x0 >; }; """) verify_label2prop("l1", "b") verify_label2prop("l2", "c") verify_label2prop("l3", "c") verify_label2prop("l4", "d") verify_label2prop("l5", "d") verify_label2prop("l6", "d") def test_property_offset_labels(): ''' It's possible to give labels to data at nonnegative byte offsets within a property value. ''' def verify_label2offset(label, expected_prop, expected_offset): actual_prop, actual_offset = dt.label2prop_offset[label] actual_prop = actual_prop.name assert (actual_prop, actual_offset) == \ (expected_prop, expected_offset), \ f"label '{label}' maps to wrong offset or property" dt = verify_parse(""" /dts-v1/; / { a = l01: l02: < l03: &node l04: l05: 2 l06: >, l07: l08: [ l09: 03 l10: l11: 04 l12: l13: ] l14:, "A"; b = < 0 > l23: l24:; node: node { }; }; """, """ /dts-v1/; / { a = l01: l02: < l03: &node l04: l05: 0x2 l06: l07: l08: >, [ l09: 03 l10: l11: 04 l12: l13: l14: ], "A"; b = < 0x0 l23: l24: >; node: node { phandle = < 0x1 >; }; }; """) verify_label2offset("l01", "a", 0) verify_label2offset("l02", "a", 0) verify_label2offset("l04", "a", 4) verify_label2offset("l05", "a", 4) verify_label2offset("l06", "a", 8) verify_label2offset("l09", "a", 8) verify_label2offset("l10", "a", 9) verify_label2offset("l23", "b", 4) verify_label2offset("l24", "b", 4) def test_unit_addr(): '''Node unit addresses must be correctly extracted from their names.''' def verify_unit_addr(path, expected): node = dt.get_node(path) assert node.unit_addr == expected, \ f"{node!r} has unexpected unit address" dt = verify_parse(""" /dts-v1/; / { no-unit-addr { }; unit-addr@ABC { }; unit-addr-non-numeric@foo-bar { }; }; """, """ /dts-v1/; / { no-unit-addr { }; unit-addr@ABC { }; unit-addr-non-numeric@foo-bar { }; }; """) verify_unit_addr("/no-unit-addr", "") verify_unit_addr("/unit-addr@ABC", "ABC") verify_unit_addr("/unit-addr-non-numeric@foo-bar", "foo-bar") def test_node_path_references(): '''Node phandles may be specified using a reference to the node's path.''' verify_parse(""" /dts-v1/; / { a = &label; b = [ 01 ], &label; c = [ 01 ], &label, <2>; d = &{/abc}; label: abc { e = &label; f = &{/abc}; }; }; """, """ /dts-v1/; / { a = &label; b = [ 01 ], &label; c = [ 01 ], &label, < 0x2 >; d = &{/abc}; label: abc { e = &label; f = &{/abc}; }; }; """) verify_error(""" /dts-v1/; / { sub { x = &missing; }; }; """, "/sub: undefined node label 'missing'") verify_error(""" /dts-v1/; / { sub { x = &{/sub/missing}; }; }; """, "/sub: component 'missing' in path '/sub/missing' does not exist") def test_phandles(): '''Various tests related to phandles.''' verify_parse(""" /dts-v1/; / { x = < &a &{/b} &c >; dummy1 { phandle = < 1 >; }; dummy2 { phandle = < 3 >; }; a: a { }; b { }; c: c { phandle = < 0xFF >; }; }; """, """ /dts-v1/; / { x = < &a &{/b} &c >; dummy1 { phandle = < 0x1 >; }; dummy2 { phandle = < 0x3 >; }; a: a { phandle = < 0x2 >; }; b { phandle = < 0x4 >; }; c: c { phandle = < 0xff >; }; }; """) # Check that a node can be assigned a phandle to itself. This just forces a # phandle to be allocated on it. The C tools support this too. verify_parse(""" /dts-v1/; / { dummy { phandle = < 1 >; }; a { foo: phandle = < &{/a} >; }; label: b { bar: phandle = < &label >; }; }; """, """ /dts-v1/; / { dummy { phandle = < 0x1 >; }; a { foo: phandle = < &{/a} >; }; label: b { bar: phandle = < &label >; }; }; """) verify_error(""" /dts-v1/; / { sub { x = < &missing >; }; }; """, "/sub: undefined node label 'missing'") verify_error_endswith(""" /dts-v1/; / { a: sub { x = /bits/ 16 < &a >; }; }; """, ":5 (column 19): parse error: phandle references are only allowed in arrays with 32-bit elements") verify_error(""" /dts-v1/; / { foo { phandle = [ 00 ]; }; }; """, "/foo: bad phandle length (1), expected 4 bytes") verify_error(""" /dts-v1/; / { foo { phandle = < 0 >; }; }; """, "/foo: bad value 0x00000000 for phandle") verify_error(""" /dts-v1/; / { foo { phandle = < (-1) >; }; }; """, "/foo: bad value 0xffffffff for phandle") verify_error(""" /dts-v1/; / { foo { phandle = < 17 >; }; bar { phandle = < 17 >; }; }; """, "/bar: duplicated phandle 0x11 (seen before at /foo)") verify_error(""" /dts-v1/; / { foo { phandle = < &{/bar} >; }; bar { }; }; """, "/foo: phandle refers to another node") def test_phandle2node(): '''Test the phandle2node dict in a dt instance.''' def verify_phandle2node(prop, offset, expected_name): phandle = dtlib.to_num(dt.root.props[prop].value[offset:offset + 4]) actual_name = dt.phandle2node[phandle].name assert actual_name == expected_name, \ f"'{prop}' is a phandle for the wrong thing" dt = parse(""" /dts-v1/; / { phandle_ = < &{/node1} 0 1 >; phandles = < 0 &{/node2} 1 &{/node3} >; node1 { phandle = < 123 >; }; node2 { }; node3 { }; }; """) verify_phandle2node("phandle_", 0, "node1") verify_phandle2node("phandles", 4, "node2") verify_phandle2node("phandles", 12, "node3") def test_mixed_assign(): '''Test mixed value type assignments''' verify_parse(""" /dts-v1/; / { x = /bits/ 8 < 0xFF 0xFF >, &abc, < 0xFF &abc 0xFF &abc >, &abc, [ FF FF ], "abc"; abc: abc { }; }; """, """ /dts-v1/; / { x = [ FF FF ], &abc, < 0xff &abc 0xff &abc >, &abc, [ FF FF ], "abc"; abc: abc { phandle = < 0x1 >; }; }; """) def test_deletion(): '''Properties and nodes may be deleted from the tree.''' # Test property deletion verify_parse(""" /dts-v1/; / { keep = < 1 >; delete = < &sub >, ⊂ /delete-property/ missing; /delete-property/ delete; sub: sub { y = < &sub >, ⊂ }; }; &sub { /delete-property/ y; }; """, """ /dts-v1/; / { keep = < 0x1 >; sub: sub { }; }; """) # Test node deletion verify_parse(""" /dts-v1/; / { x = "foo"; sub0 { x = "bar"; }; }; /delete-node/ &{/}; / { sub1 { x = < 1 >; sub2 { x = < &sub >, ⊂ }; /delete-node/ sub2; }; sub3: sub3 { x = < &sub >, ⊂ }; sub4 { x = < &sub >, ⊂ }; }; /delete-node/ &sub3; /delete-node/ &{/sub4}; """, """ /dts-v1/; / { sub1 { x = < 0x1 >; }; }; """) verify_parse(""" /dts-v1/; / { x: x = < &sub >, ⊂ sub1 { x = < &sub >, ⊂ }; sub2: sub2 { x = < &sub >, ⊂ }; }; /delete-node/ &{/}; """, """ /dts-v1/; / { }; """) verify_error_endswith(""" /dts-v1/; / { }; /delete-node/ &missing; """, ":6 (column 15): parse error: undefined node label 'missing'") verify_error_endswith(""" /dts-v1/; /delete-node/ { """, ":3 (column 15): parse error: expected label (&foo) or path (&{/foo/bar}) reference") def test_include_curdir(tmp_path): '''Verify that /include/ (which is handled in the lexer) searches the current directory''' with temporary_chdir(tmp_path): with open("same-dir-1", "w") as f: f.write(""" x = [ 00 ]; /include/ "same-dir-2" """) with open("same-dir-2", "w") as f: f.write(""" y = [ 01 ]; /include/ "same-dir-3" """) with open("same-dir-3", "w") as f: f.write(""" z = [ 02 ]; """) with open("test.dts", "w") as f: f.write(""" /dts-v1/; / { /include/ "same-dir-1" }; """) dt = dtlib.DT("test.dts") assert str(dt) == """ /dts-v1/; / { x = [ 00 ]; y = [ 01 ]; z = [ 02 ]; }; """[1:-1] def test_include_is_lexical(tmp_path): '''/include/ is done in the lexer, which means that property definitions can span multiple included files in different directories.''' with open(tmp_path / "tmp2.dts", "w") as f: f.write(""" /dts-v1/; / { """) with open(tmp_path / "tmp3.dts", "w") as f: f.write(""" x = <1>; """) subdir_1 = tmp_path / "subdir-1" subdir_1.mkdir() with open(subdir_1 / "via-include-path-1", "w") as f: f.write(""" = /include/ "via-include-path-2" """) subdir_2 = tmp_path / "subdir-2" subdir_2.mkdir() with open(subdir_2 / "via-include-path-2", "w") as f: f.write(""" <2>; }; """) with open(tmp_path / "test.dts", "w") as test_dts: test_dts.write(""" /include/ "tmp2.dts" /include/ "tmp3.dts" y /include/ "via-include-path-1" """) with temporary_chdir(tmp_path): dt = dtlib.DT("test.dts", include_path=(subdir_1, subdir_2)) expected_dt = """ /dts-v1/; / { x = < 0x1 >; y = < 0x2 >; }; """[1:-1] assert str(dt) == expected_dt def test_include_misc(tmp_path): '''Miscellaneous /include/ tests.''' # Missing includes should error out. verify_error_endswith(""" /include/ "missing" """, ":1 (column 1): parse error: 'missing' could not be found") # Verify that an error in an included file points to the right location with temporary_chdir(tmp_path): with open("tmp2.dts", "w") as f: f.write("""\ x """) with open("tmp.dts", "w") as f: f.write(""" /include/ "tmp2.dts" """) with dtlib_raises("tmp2.dts:3 (column 3): parse error: " "expected '/dts-v1/;' at start of file"): dtlib.DT("tmp.dts") def test_include_recursion(tmp_path): '''Test recursive /include/ detection''' with temporary_chdir(tmp_path): with open("tmp2.dts", "w") as f: f.write('/include/ "tmp3.dts"\n') with open("tmp3.dts", "w") as f: f.write('/include/ "tmp.dts"\n') with open("tmp.dts", "w") as f: f.write('/include/ "tmp2.dts"\n') expected_err = """\ tmp3.dts:1 (column 1): parse error: recursive /include/: tmp.dts:1 -> tmp2.dts:1 -> tmp3.dts:1 -> tmp.dts""" with dtlib_raises(expected_err): dtlib.DT("tmp.dts") with open("tmp.dts", "w") as f: f.write('/include/ "tmp.dts"\n') expected_err = """\ tmp.dts:1 (column 1): parse error: recursive /include/: tmp.dts:1 -> tmp.dts""" with dtlib_raises(expected_err): dtlib.DT("tmp.dts") def test_omit_if_no_ref(): '''The /omit-if-no-ref/ marker is a bit of undocumented dtc magic that removes a node from the tree if it isn't referred to elsewhere. https://elinux.org/Device_Tree_Source_Undocumented ''' verify_parse(""" /dts-v1/; / { x = < &{/referenced} >, &referenced2; /omit-if-no-ref/ referenced { }; referenced2: referenced2 { }; /omit-if-no-ref/ unreferenced { }; l1: /omit-if-no-ref/ unreferenced2 { }; /omit-if-no-ref/ l2: unreferenced3 { }; unreferenced4: unreferenced4 { }; unreferenced5 { }; }; /omit-if-no-ref/ &referenced2; /omit-if-no-ref/ &unreferenced4; /omit-if-no-ref/ &{/unreferenced5}; """, """ /dts-v1/; / { x = < &{/referenced} >, &referenced2; referenced { phandle = < 0x1 >; }; referenced2: referenced2 { }; }; """) verify_error_endswith(""" /dts-v1/; / { /omit-if-no-ref/ x = ""; }; """, ":4 (column 21): parse error: /omit-if-no-ref/ can only be used on nodes") verify_error_endswith(""" /dts-v1/; / { /omit-if-no-ref/ x; }; """, ":4 (column 20): parse error: /omit-if-no-ref/ can only be used on nodes") verify_error_endswith(""" /dts-v1/; / { /omit-if-no-ref/ { }; }; """, ":4 (column 19): parse error: expected node or property name") verify_error_endswith(""" /dts-v1/; / { /omit-if-no-ref/ = < 0 >; }; """, ":4 (column 19): parse error: expected node or property name") verify_error_endswith(""" /dts-v1/; / { }; /omit-if-no-ref/ &missing; """, ":6 (column 18): parse error: undefined node label 'missing'") verify_error_endswith(""" /dts-v1/; /omit-if-no-ref/ { """, ":3 (column 18): parse error: expected label (&foo) or path (&{/foo/bar}) reference") def test_expr(): '''Property values may contain expressions.''' verify_parse(""" /dts-v1/; / { ter1 = < (0 ? 1 : 0 ? 2 : 3) >; ter2 = < (0 ? 1 : 1 ? 2 : 3) >; ter3 = < (1 ? 1 : 0 ? 2 : 3) >; ter4 = < (1 ? 1 : 1 ? 2 : 3) >; or1 = < (0 || 0) >; or2 = < (0 || 1) >; or3 = < (1 || 0) >; or4 = < (1 || 1) >; and1 = < (0 && 0) >; and2 = < (0 && 1) >; and3 = < (1 && 0) >; and4 = < (1 && 1) >; bitor = < (1 | 2) >; bitxor = < (7 ^ 2) >; bitand = < (3 & 6) >; eq1 = < (1 == 0) >; eq2 = < (1 == 1) >; neq1 = < (1 != 0) >; neq2 = < (1 != 1) >; lt1 = < (1 < 2) >; lt2 = < (2 < 2) >; lt3 = < (3 < 2) >; lteq1 = < (1 <= 2) >; lteq2 = < (2 <= 2) >; lteq3 = < (3 <= 2) >; gt1 = < (1 > 2) >; gt2 = < (2 > 2) >; gt3 = < (3 > 2) >; gteq1 = < (1 >= 2) >; gteq2 = < (2 >= 2) >; gteq3 = < (3 >= 2) >; lshift = < (2 << 3) >; rshift = < (16 >> 3) >; add = < (3 + 4) >; sub = < (7 - 4) >; mul = < (3 * 4) >; div = < (11 / 3) >; mod = < (11 % 3) >; unary_minus = < (-3) >; bitnot = < (~1) >; not0 = < (!-1) >; not1 = < (!0) >; not2 = < (!1) >; not3 = < (!2) >; nest = < (((--3) + (-2)) * (--(-2))) >; char_lits = < ('a' + 'b') >; }; """, """ /dts-v1/; / { ter1 = < 0x3 >; ter2 = < 0x2 >; ter3 = < 0x1 >; ter4 = < 0x1 >; or1 = < 0x0 >; or2 = < 0x1 >; or3 = < 0x1 >; or4 = < 0x1 >; and1 = < 0x0 >; and2 = < 0x0 >; and3 = < 0x0 >; and4 = < 0x1 >; bitor = < 0x3 >; bitxor = < 0x5 >; bitand = < 0x2 >; eq1 = < 0x0 >; eq2 = < 0x1 >; neq1 = < 0x1 >; neq2 = < 0x0 >; lt1 = < 0x1 >; lt2 = < 0x0 >; lt3 = < 0x0 >; lteq1 = < 0x1 >; lteq2 = < 0x1 >; lteq3 = < 0x0 >; gt1 = < 0x0 >; gt2 = < 0x0 >; gt3 = < 0x1 >; gteq1 = < 0x0 >; gteq2 = < 0x1 >; gteq3 = < 0x1 >; lshift = < 0x10 >; rshift = < 0x2 >; add = < 0x7 >; sub = < 0x3 >; mul = < 0xc >; div = < 0x3 >; mod = < 0x2 >; unary_minus = < 0xfffffffd >; bitnot = < 0xfffffffe >; not0 = < 0x0 >; not1 = < 0x1 >; not2 = < 0x0 >; not3 = < 0x0 >; nest = < 0xfffffffe >; char_lits = < 0xc3 >; }; """) verify_error_endswith(""" /dts-v1/; / { a = < (1/(-1 + 1)) >; }; """, ":4 (column 18): parse error: division by zero") verify_error_endswith(""" /dts-v1/; / { a = < (1%0) >; }; """, ":4 (column 11): parse error: division by zero") def test_comment_removal(): '''Comments should be removed when round-tripped to a str.''' verify_parse(""" /**//dts-v1//**/;// // // foo / /**/{// foo x/**/=/* foo *//****/;/**/}/*/**/; """, """ /dts-v1/; / { x = < 0x1 >; }; """) def verify_path_is(path, node_name, dt): '''Verify 'node.name' matches 'node_name' in 'dt'.''' try: node = dt.get_node(path) assert node.name == node_name, f'unexpected path {path}' except dtlib.DTError: assert False, f'no node found for path {path}' def verify_path_error(path, msg, dt): '''Verify that an attempt to get node 'path' from 'dt' raises a DTError whose str is 'msg'.''' with dtlib_raises(msg): dt.get_node(path) def test_get_node(): '''Test DT.get_node().''' dt = parse(""" /dts-v1/; / { foo { bar { }; }; baz { }; }; """) verify_path_is("/", "/", dt) verify_path_is("//", "/", dt) verify_path_is("///", "/", dt) verify_path_is("/foo", "foo", dt) verify_path_is("//foo", "foo", dt) verify_path_is("///foo", "foo", dt) verify_path_is("/foo/bar", "bar", dt) verify_path_is("//foo//bar", "bar", dt) verify_path_is("///foo///bar", "bar", dt) verify_path_is("/baz", "baz", dt) verify_path_error( "", "no alias '' found -- did you forget the leading '/' in the node path?", dt) verify_path_error( "missing", "no alias 'missing' found -- did you forget the leading '/' in the node path?", dt) verify_path_error( "/missing", "component 'missing' in path '/missing' does not exist", dt) verify_path_error( "/foo/missing", "component 'missing' in path '/foo/missing' does not exist", dt) def verify_path_exists(path): assert dt.has_node(path), f"path '{path}' does not exist" def verify_path_missing(path): assert not dt.has_node(path), f"path '{path}' exists" verify_path_exists("/") verify_path_exists("/foo") verify_path_exists("/foo/bar") verify_path_missing("/missing") verify_path_missing("/foo/missing") def test_aliases(): '''Test /aliases''' dt = parse(""" /dts-v1/; / { aliases { alias1 = &l1; alias2 = &l2; alias3 = &{/sub/node3}; alias4 = &{/node4}; }; l1: node1 { }; l2: node2 { }; sub { node3 { }; }; node4 { node5 { }; }; }; """) def verify_alias_target(alias, node_name): verify_path_is(alias, node_name, dt) assert alias in dt.alias2node assert dt.alias2node[alias].name == node_name, f"bad result for {alias}" verify_alias_target("alias1", "node1") verify_alias_target("alias2", "node2") verify_alias_target("alias3", "node3") verify_path_is("alias4/node5", "node5", dt) verify_path_error( "alias4/node5/node6", "component 'node6' in path 'alias4/node5/node6' does not exist", dt) verify_error_matches(""" /dts-v1/; / { aliases { a = [ 00 ]; }; }; """, "expected property 'a' on /aliases in .*" + re.escape("to be assigned with either 'a = &foo' or 'a = \"/path/to/node\"', not 'a = [ 00 ];'")) verify_error_matches(r""" /dts-v1/; / { aliases { a = "\xFF"; }; }; """, re.escape(r"value of property 'a' (b'\xff\x00') on /aliases in ") + ".* is not valid UTF-8") verify_error(""" /dts-v1/; / { aliases { A = "/aliases"; }; }; """, "/aliases: alias property name 'A' should include only characters from [0-9a-z-]") verify_error_matches(r""" /dts-v1/; / { aliases { a = "/missing"; }; }; """, "property 'a' on /aliases in .* points to the non-existent node \"/missing\"") def test_prop_type(): '''Test Property.type''' def verify_type(prop, expected): actual = dt.root.props[prop].type assert actual == expected, f'{prop} has wrong type' dt = parse(""" /dts-v1/; / { empty; bytes1 = [ ]; bytes2 = [ 01 ]; bytes3 = [ 01 02 ]; bytes4 = foo: [ 01 bar: 02 ]; bytes5 = /bits/ 8 < 1 2 3 >; num = < 1 >; nums1 = < >; nums2 = < >, < >; nums3 = < 1 2 >; nums4 = < 1 2 >, < 3 >, < 4 >; string = "foo"; strings = "foo", "bar"; path1 = &node; path2 = &{/node}; phandle1 = < &node >; phandle2 = < &{/node} >; phandles1 = < &node &node >; phandles2 = < &node >, < &node >; phandle-and-nums-1 = < &node 1 >; phandle-and-nums-2 = < &node 1 2 &node 3 4 >; phandle-and-nums-3 = < &node 1 2 >, < &node 3 4 >; compound1 = < 1 >, [ 02 ]; compound2 = "foo", < >; node: node { }; }; """) verify_type("empty", dtlib.Type.EMPTY) verify_type("bytes1", dtlib.Type.BYTES) verify_type("bytes2", dtlib.Type.BYTES) verify_type("bytes3", dtlib.Type.BYTES) verify_type("bytes4", dtlib.Type.BYTES) verify_type("bytes5", dtlib.Type.BYTES) verify_type("num", dtlib.Type.NUM) verify_type("nums1", dtlib.Type.NUMS) verify_type("nums2", dtlib.Type.NUMS) verify_type("nums3", dtlib.Type.NUMS) verify_type("nums4", dtlib.Type.NUMS) verify_type("string", dtlib.Type.STRING) verify_type("strings", dtlib.Type.STRINGS) verify_type("phandle1", dtlib.Type.PHANDLE) verify_type("phandle2", dtlib.Type.PHANDLE) verify_type("phandles1", dtlib.Type.PHANDLES) verify_type("phandles2", dtlib.Type.PHANDLES) verify_type("phandle-and-nums-1", dtlib.Type.PHANDLES_AND_NUMS) verify_type("phandle-and-nums-2", dtlib.Type.PHANDLES_AND_NUMS) verify_type("phandle-and-nums-3", dtlib.Type.PHANDLES_AND_NUMS) verify_type("path1", dtlib.Type.PATH) verify_type("path2", dtlib.Type.PATH) verify_type("compound1", dtlib.Type.COMPOUND) verify_type("compound2", dtlib.Type.COMPOUND) def test_prop_type_casting(): '''Test Property.to_{num,nums,string,strings,node}()''' dt = parse(r""" /dts-v1/; / { u = < 1 >; s = < 0xFFFFFFFF >; u8 = /bits/ 8 < 1 >; u16 = /bits/ 16 < 1 2 >; u64 = /bits/ 64 < 1 >; bytes = [ 01 02 03 ]; empty; zero = < >; two_u = < 1 2 >; two_s = < 0xFFFFFFFF 0xFFFFFFFE >; three_u = < 1 2 3 >; three_u_split = < 1 >, < 2 >, < 3 >; empty_string = ""; string = "foo\tbar baz"; invalid_string = "\xff"; strings = "foo", "bar", "baz"; invalid_strings = "foo", "\xff", "bar"; ref = <&{/target}>; refs = <&{/target} &{/target2}>; refs2 = <&{/target}>, <&{/target2}>; path = &{/target}; manualpath = "/target"; missingpath = "/missing"; target { phandle = < 100 >; }; target2 { }; }; """) # Test Property.to_num() def verify_to_num(prop, signed, expected): signed_str = "a signed" if signed else "an unsigned" actual = dt.root.props[prop].to_num(signed) assert actual == expected, \ f"{prop} has bad {signed_str} numeric value" def verify_to_num_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_num() verify_to_num("u", False, 1) verify_to_num("u", True, 1) verify_to_num("s", False, 0xFFFFFFFF) verify_to_num("s", True, -1) verify_to_num_error_matches( "two_u", "expected property 'two_u' on / in .* to be assigned with " + re.escape("'two_u = < (number) >;', not 'two_u = < 0x1 0x2 >;'")) verify_to_num_error_matches( "u8", "expected property 'u8' on / in .* to be assigned with " + re.escape("'u8 = < (number) >;', not 'u8 = [ 01 ];'")) verify_to_num_error_matches( "u16", "expected property 'u16' on / in .* to be assigned with " + re.escape("'u16 = < (number) >;', not 'u16 = /bits/ 16 < 0x1 0x2 >;'")) verify_to_num_error_matches( "u64", "expected property 'u64' on / in .* to be assigned with " + re.escape("'u64 = < (number) >;', not 'u64 = /bits/ 64 < 0x1 >;'")) verify_to_num_error_matches( "string", "expected property 'string' on / in .* to be assigned with " + re.escape("'string = < (number) >;', not 'string = \"foo\\tbar baz\";'")) # Test Property.to_nums() def verify_to_nums(prop, signed, expected): signed_str = "signed" if signed else "unsigned" actual = dt.root.props[prop].to_nums(signed) assert actual == expected, \ f"'{prop}' gives the wrong {signed_str} numbers" def verify_to_nums_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_nums() verify_to_nums("zero", False, []) verify_to_nums("u", False, [1]) verify_to_nums("two_u", False, [1, 2]) verify_to_nums("two_u", True, [1, 2]) verify_to_nums("two_s", False, [0xFFFFFFFF, 0xFFFFFFFE]) verify_to_nums("two_s", True, [-1, -2]) verify_to_nums("three_u", False, [1, 2, 3]) verify_to_nums("three_u_split", False, [1, 2, 3]) verify_to_nums_error_matches( "empty", "expected property 'empty' on / in .* to be assigned with " + re.escape("'empty = < (number) (number) ... >;', not 'empty;'")) verify_to_nums_error_matches( "string", "expected property 'string' on / in .* to be assigned with " + re.escape("'string = < (number) (number) ... >;', ") + re.escape("not 'string = \"foo\\tbar baz\";'")) # Test Property.to_bytes() def verify_to_bytes(prop, expected): actual = dt.root.props[prop].to_bytes() assert actual == expected, f"'{prop}' gives the wrong bytes" def verify_to_bytes_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_bytes() verify_to_bytes("u8", b"\x01") verify_to_bytes("bytes", b"\x01\x02\x03") verify_to_bytes_error_matches( "u16", "expected property 'u16' on / in .* to be assigned with " + re.escape("'u16 = [ (byte) (byte) ... ];', ") + re.escape("not 'u16 = /bits/ 16 < 0x1 0x2 >;'")) verify_to_bytes_error_matches( "empty", "expected property 'empty' on / in .* to be assigned with " + re.escape("'empty = [ (byte) (byte) ... ];', not 'empty;'")) # Test Property.to_string() def verify_to_string(prop, expected): actual = dt.root.props[prop].to_string() assert actual == expected, f"'{prop}' to_string gives the wrong string" def verify_to_string_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_string() verify_to_string("empty_string", "") verify_to_string("string", "foo\tbar baz") verify_to_string_error_matches( "u", "expected property 'u' on / in .* to be assigned with " + re.escape("'u = \"string\";', not 'u = < 0x1 >;'")) verify_to_string_error_matches( "strings", "expected property 'strings' on / in .* to be assigned with " + re.escape("'strings = \"string\";', ")+ re.escape("not 'strings = \"foo\", \"bar\", \"baz\";'")) verify_to_string_error_matches( "invalid_string", re.escape(r"value of property 'invalid_string' (b'\xff\x00') on / ") + "in .* is not valid UTF-8") # Test Property.to_strings() def verify_to_strings(prop, expected): actual = dt.root.props[prop].to_strings() assert actual == expected, f"'{prop}' to_strings gives the wrong value" def verify_to_strings_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_strings() verify_to_strings("empty_string", [""]) verify_to_strings("string", ["foo\tbar baz"]) verify_to_strings("strings", ["foo", "bar", "baz"]) verify_to_strings_error_matches( "u", "expected property 'u' on / in .* to be assigned with " + re.escape("'u = \"string\", \"string\", ... ;', not 'u = < 0x1 >;'")) verify_to_strings_error_matches( "invalid_strings", "value of property 'invalid_strings' " + re.escape(r"(b'foo\x00\xff\x00bar\x00') on / in ") + ".* is not valid UTF-8") # Test Property.to_node() def verify_to_node(prop, path): actual = dt.root.props[prop].to_node().path assert actual == path, f"'{prop}' points at wrong path" def verify_to_node_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_node() verify_to_node("ref", "/target") verify_to_node_error_matches( "u", "expected property 'u' on / in .* to be assigned with " + re.escape("'u = < &foo >;', not 'u = < 0x1 >;'")) verify_to_node_error_matches( "string", "expected property 'string' on / in .* to be assigned with " + re.escape("'string = < &foo >;', not 'string = \"foo\\tbar baz\";'")) # Test Property.to_nodes() def verify_to_nodes(prop, paths): actual = [node.path for node in dt.root.props[prop].to_nodes()] assert actual == paths, f"'{prop} gives wrong node paths" def verify_to_nodes_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_nodes() verify_to_nodes("zero", []) verify_to_nodes("ref", ["/target"]) verify_to_nodes("refs", ["/target", "/target2"]) verify_to_nodes("refs2", ["/target", "/target2"]) verify_to_nodes_error_matches( "u", "expected property 'u' on / in .* to be assigned with " + re.escape("'u = < &foo &bar ... >;', not 'u = < 0x1 >;'")) verify_to_nodes_error_matches( "string", "expected property 'string' on / in .* to be assigned with " + re.escape("'string = < &foo &bar ... >;', ") + re.escape("not 'string = \"foo\\tbar baz\";'")) # Test Property.to_path() def verify_to_path(prop, path): actual = dt.root.props[prop].to_path().path assert actual == path, f"'{prop} gives the wrong path" def verify_to_path_error_matches(prop, expected_re): with dtlib_raises(err_matches=expected_re): dt.root.props[prop].to_path() verify_to_path("path", "/target") verify_to_path("manualpath", "/target") verify_to_path_error_matches( "u", "expected property 'u' on / in .* to be assigned with either " + re.escape("'u = &foo' or 'u = \"/path/to/node\"', not 'u = < 0x1 >;'")) verify_to_path_error_matches( "missingpath", "property 'missingpath' on / in .* points to the non-existent node " '"/missing"') # Test top-level to_num() and to_nums() def verify_raw_to_num(fn, prop, length, signed, expected): actual = fn(dt.root.props[prop].value, length, signed) assert actual == expected, \ f"{fn.__name__}(<{prop}>, {length}, {signed}) gives wrong value" def verify_raw_to_num_error(fn, data, length, msg): # We're using this instead of dtlib_raises() for the extra # context we get from the assertion below. with pytest.raises(dtlib.DTError) as e: fn(data, length) assert str(e.value) == msg, \ (f"{fn.__name__}() called with data='{data}', length='{length}' " "gives the wrong error") verify_raw_to_num(dtlib.to_num, "u", None, False, 1) verify_raw_to_num(dtlib.to_num, "u", 4, False, 1) verify_raw_to_num(dtlib.to_num, "s", None, False, 0xFFFFFFFF) verify_raw_to_num(dtlib.to_num, "s", None, True, -1) verify_raw_to_num(dtlib.to_nums, "empty", 4, False, []) verify_raw_to_num(dtlib.to_nums, "u16", 2, False, [1, 2]) verify_raw_to_num(dtlib.to_nums, "two_s", 4, False, [0xFFFFFFFF, 0xFFFFFFFE]) verify_raw_to_num(dtlib.to_nums, "two_s", 4, True, [-1, -2]) verify_raw_to_num_error(dtlib.to_num, 0, 0, "'0' has type 'int', expected 'bytes'") verify_raw_to_num_error(dtlib.to_num, b"", 0, "'length' must be greater than zero, was 0") verify_raw_to_num_error(dtlib.to_num, b"foo", 2, "b'foo' is 3 bytes long, expected 2") verify_raw_to_num_error(dtlib.to_nums, 0, 0, "'0' has type 'int', expected 'bytes'") verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'length' must be greater than zero, was 0") 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") def test_duplicate_labels(): ''' It is an error to duplicate labels in most conditions, but there are some exceptions where it's OK. ''' verify_error(""" /dts-v1/; / { sub1 { label: foo { }; }; sub2 { label: bar { }; }; }; """, "Label 'label' appears on /sub1/foo and on /sub2/bar") verify_error(""" /dts-v1/; / { sub { label: foo { }; }; }; / { sub { label: bar { }; }; }; """, "Label 'label' appears on /sub/bar and on /sub/foo") verify_error(""" /dts-v1/; / { foo: a = < 0 >; foo: node { }; }; """, "Label 'foo' appears on /node and on property 'a' of node /") verify_error(""" /dts-v1/; / { foo: a = < 0 >; node { foo: b = < 0 >; }; }; """, "Label 'foo' appears on property 'a' of node / and on property 'b' of node /node") verify_error(""" /dts-v1/; / { foo: a = foo: < 0 >; }; """, "Label 'foo' appears in the value of property 'a' of node / and on property 'a' of node /") # Giving the same label twice for the same node is fine verify_parse(""" /dts-v1/; / { sub { label: foo { }; }; }; / { sub { label: foo { }; }; }; """, """ /dts-v1/; / { sub { label: foo { }; }; }; """) # Duplicate labels are fine if one of the nodes is deleted verify_parse(""" /dts-v1/; / { label: foo { }; label: bar { }; }; /delete-node/ &{/bar}; """, """ /dts-v1/; / { label: foo { }; }; """) # # Test overriding/deleting a property with references # verify_parse(""" /dts-v1/; / { x = &foo, < &foo >; y = &foo, < &foo >; foo: foo { }; }; / { x = < 1 >; /delete-property/ y; }; """, """ /dts-v1/; / { x = < 0x1 >; foo: foo { }; }; """) # # Test self-referential node # verify_parse(""" /dts-v1/; / { label: foo { x = &{/foo}, &label, < &label >; }; }; """, """ /dts-v1/; / { label: foo { x = &{/foo}, &label, < &label >; phandle = < 0x1 >; }; }; """) # # Test /memreserve/ # dt = verify_parse(""" /dts-v1/; l1: l2: /memreserve/ (1 + 1) (2 * 2); /memreserve/ 0x100 0x200; / { }; """, """ /dts-v1/; l1: l2: /memreserve/ 0x0000000000000002 0x0000000000000004; /memreserve/ 0x0000000000000100 0x0000000000000200; / { }; """) expected = [(["l1", "l2"], 2, 4), ([], 0x100, 0x200)] assert dt.memreserves == expected verify_error_endswith(""" /dts-v1/; foo: / { }; """, ":3 (column 6): parse error: expected /memreserve/ after labels at beginning of file") def test_reprs(): '''Test the __repr__() functions.''' dts = """ /dts-v1/; / { x = < 0 >; sub { y = < 1 >; }; }; """ dt = parse(dts, include_path=("foo", "bar")) assert re.fullmatch(r"DT\(filename='.*', include_path=.'foo', 'bar'.\)", repr(dt)) assert re.fullmatch("", repr(dt.root.props["x"])) assert re.fullmatch("", repr(dt.root.nodes["sub"])) dt = parse(dts, include_path=iter(("foo", "bar"))) assert re.fullmatch(r"DT\(filename='.*', include_path=.'foo', 'bar'.\)", repr(dt)) def test_names(): '''Tests for node/property names.''' verify_parse(r""" /dts-v1/; / { // A leading \ is accepted but ignored in node/propert names \aA0,._+*#?- = &_, &{/aA0,._+@-}; // Names that overlap with operators and integer literals + = [ 00 ]; * = [ 02 ]; - = [ 01 ]; ? = [ 03 ]; 0 = [ 04 ]; 0x123 = [ 05 ]; // Node names are more restrictive than property names. _: \aA0,._+@- { }; 0 { }; }; """, """ /dts-v1/; / { aA0,._+*#?- = &_, &{/aA0,._+@-}; + = [ 00 ]; * = [ 02 ]; - = [ 01 ]; ? = [ 03 ]; 0 = [ 04 ]; 0x123 = [ 05 ]; _: aA0,._+@- { }; 0 { }; }; """) verify_error_endswith(r""" /dts-v1/; / { foo@3; }; """, ":4 (column 7): parse error: '@' is only allowed in node names") verify_error_endswith(r""" /dts-v1/; / { foo@3 = < 0 >; }; """, ":4 (column 8): parse error: '@' is only allowed in node names") verify_error_endswith(r""" /dts-v1/; / { foo@2@3 { }; }; """, ":4 (column 10): parse error: multiple '@' in node name") def test_dense_input(): ''' Test that a densely written DTS input round-trips to something readable. ''' verify_parse(""" /dts-v1/;/{l1:l2:foo{l3:l4:bar{l5:x=l6:/bits/8l9:,[03],"a";};};}; """, """ /dts-v1/; / { l1: l2: foo { l3: l4: bar { l5: x = l6: [ l7: 01 l8: 02 l9: ], [ 03 ], "a"; }; }; }; """) def test_misc(): '''Test miscellaneous errors and non-errors.''' verify_error_endswith("", ":1 (column 1): parse error: expected '/dts-v1/;' at start of file") verify_error_endswith(""" /dts-v1/; """, ":2 (column 1): parse error: no root node defined") verify_error_endswith(""" /dts-v1/; /plugin/; """, ":1 (column 11): parse error: /plugin/ is not supported") verify_error_endswith(""" /dts-v1/; / { foo: foo { }; }; // Only one label supported before label references at the top level l1: l2: &foo { }; """, ":9 (column 5): parse error: expected label reference (&foo)") verify_error_endswith(""" /dts-v1/; / { foo: {}; }; """, ":4 (column 14): parse error: expected node or property name") # Multiple /dts-v1/ at the start of a file is fine verify_parse(""" /dts-v1/; /dts-v1/; / { }; """, """ /dts-v1/; / { }; """) def test_dangling_alias(): dt = parse(''' /dts-v1/; / { aliases { foo = "/missing"; }; }; ''', force=True) assert dt.get_node('/aliases').props['foo'].to_string() == '/missing' def test_duplicate_nodes(): # Duplicate node names in the same {} block are an error in dtc, # so we want to reproduce the same behavior. But we also need to # make sure that doesn't break overlays modifying the same node. verify_error_endswith(""" /dts-v1/; / { foo {}; foo {}; }; """, "/foo: duplicate node name") verify_parse(""" /dts-v1/; / { foo { prop = <3>; }; }; / { foo { prop = <4>; }; }; """, """ /dts-v1/; / { foo { prop = < 0x4 >; }; }; """) def test_deepcopy(): dt = parse(''' /dts-v1/; memreservelabel: /memreserve/ 0xdeadbeef 0x4000; / { aliases { foo = &nodelabel; }; rootprop_label: rootprop = prop_offset0: <0x12345678 prop_offset4: 0x0>; nodelabel: node@1234 { nodeprop = <3>; subnode { ref-to-node = <&nodelabel>; }; }; }; ''') dt_copy = deepcopy(dt) assert dt_copy.filename == dt.filename # dt_copy.root checks: root_copy = dt_copy.root assert root_copy is not dt.root assert root_copy.parent is None assert root_copy.dt is dt_copy assert root_copy.labels == [] assert root_copy.labels is not dt.root.labels # dt_copy.memreserves checks: assert dt_copy.memreserves == [ (set(['memreservelabel']), 0xdeadbeef, 0x4000) ] assert dt_copy.memreserves is not dt.memreserves # Miscellaneous dt_copy node and property checks: assert 'rootprop' in root_copy.props rootprop_copy = root_copy.props['rootprop'] assert rootprop_copy is not dt.root.props['rootprop'] assert rootprop_copy.name == 'rootprop' assert rootprop_copy.value == b'\x12\x34\x56\x78\0\0\0\0' assert rootprop_copy.type == dtlib.Type.NUMS assert rootprop_copy.labels == ['rootprop_label'] assert rootprop_copy.labels is not dt.root.props['rootprop'].labels assert rootprop_copy.offset_labels == { 'prop_offset0': 0, 'prop_offset4': 4, } assert rootprop_copy.offset_labels is not \ dt.root.props['rootprop'].offset_labels assert rootprop_copy.node is root_copy assert dt_copy.has_node('/node@1234') node_copy = dt_copy.get_node('/node@1234') assert node_copy is not dt.get_node('/node@1234') assert node_copy.labels == ['nodelabel'] assert node_copy.labels is not dt.get_node('/node@1234').labels assert node_copy.name == 'node@1234' assert node_copy.unit_addr == '1234' assert node_copy.path == '/node@1234' assert set(node_copy.props.keys()) == set(['nodeprop', 'phandle']) assert node_copy.props is not dt.get_node('/node@1234').props assert node_copy.props['nodeprop'].name == 'nodeprop' assert node_copy.props['nodeprop'].labels == [] assert node_copy.props['nodeprop'].offset_labels == {} assert node_copy.props['nodeprop'].node is node_copy assert node_copy.dt is dt_copy assert 'subnode' in node_copy.nodes subnode_copy = node_copy.nodes['subnode'] assert subnode_copy is not dt.get_node('/node@1234/subnode') assert subnode_copy.parent is node_copy # dt_copy.label2prop and .label2prop_offset checks: assert 'rootprop_label' in dt_copy.label2prop assert dt_copy.label2prop['rootprop_label'] is rootprop_copy assert list(dt_copy.label2prop_offset.keys()) == ['prop_offset0', 'prop_offset4'] assert dt_copy.label2prop_offset['prop_offset4'][0] is rootprop_copy assert dt_copy.label2prop_offset['prop_offset4'][1] == 4 # dt_copy.foo2node checks: def check_node_lookup_table(attr_name): original = getattr(dt, attr_name) copy = getattr(dt_copy, attr_name) assert original is not copy assert list(original.keys()) == list(copy.keys()) assert all([original_node.path == copy_node.path and original_node is not copy_node for original_node, copy_node in zip(original.values(), copy.values())]) check_node_lookup_table('alias2node') check_node_lookup_table('label2node') check_node_lookup_table('phandle2node') assert list(dt_copy.alias2node.keys()) == ['foo'] assert dt_copy.alias2node['foo'] is node_copy assert list(dt_copy.label2node.keys()) == ['nodelabel'] assert dt_copy.label2node['nodelabel'] is node_copy assert dt_copy.phandle2node # This is a little awkward because of the way dtlib allocates # phandles. phandle2node_copy_values = set(dt_copy.phandle2node.values()) assert node_copy in phandle2node_copy_values for node in dt.node_iter(): assert node not in phandle2node_copy_values def test_move_node(): # Test cases for DT.move_node(). dt = parse(''' /dts-v1/; / { aliases { parent-alias = &parent_label; }; parent_label: parent { child {}; }; bar { shouldbechosen { foo = "bar"; }; }; }; ''') parent = dt.get_node('/parent') child = dt.get_node('/parent/child') dt.move_node(parent, '/newpath') assert parent.path == '/newpath' assert child.path == '/newpath/child' assert child.parent is parent assert child.parent is dt.get_node('/newpath') assert dt.get_node('parent-alias') is parent assert dt.label2node['parent_label'] is parent assert not dt.has_node('/chosen') dt.move_node(dt.get_node('/bar/shouldbechosen'), '/chosen') assert dt.has_node('/chosen') assert 'foo' in dt.get_node('/chosen').props with dtlib_raises("the root node can't be moved"): dt.move_node(dt.root, '/somewhere/else') with dtlib_raises("can't move '/newpath' to '/aliases': " "destination node exists"): dt.move_node(parent, '/aliases') with dtlib_raises("path 'xyz' doesn't start with '/'"): dt.move_node(parent, 'xyz') with dtlib_raises("new path '/ invalid': bad character ' '"): dt.move_node(parent, '/ invalid') with dtlib_raises("can't move '/newpath' to '/foo/bar': " "parent node '/foo' doesn't exist"): dt.move_node(parent, '/foo/bar')