zephyr/scripts/dts/python-devicetree/tests/test_dtlib.py
Martí Bolívar 5272e7f681 dtlib: add DT.move_node()
This helper lets you place a node (really the entire subtree rooted at
that node) elsewhere in the devicetree. This will be useful when
adding system devicetree support, when we'll want to be able to, for
example, move the CPU cluster node selected by the current execution
domain to /cpus.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
2023-02-27 17:44:45 +01:00

2469 lines
50 KiB
Python

# 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 >, &sub;
/delete-property/ missing;
/delete-property/ delete;
sub: sub {
y = < &sub >, &sub;
};
};
&sub {
/delete-property/ y;
};
""",
"""
/dts-v1/;
/ {
keep = < 0x1 >;
sub: sub {
};
};
""")
# Test node deletion
verify_parse("""
/dts-v1/;
/ {
sub1 {
x = < 1 >;
sub2 {
x = < &sub >, &sub;
};
/delete-node/ sub2;
};
sub3: sub3 {
x = < &sub >, &sub;
};
sub4 {
x = < &sub >, &sub;
};
};
/delete-node/ &sub3;
/delete-node/ &{/sub4};
""",
"""
/dts-v1/;
/ {
sub1 {
x = < 0x1 >;
};
};
""")
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
*/</**/1/***/>/****/;/**/}/*/**/;
""",
"""
/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 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("<Property 'x' at '/' in '.*'>",
repr(dt.root.props["x"]))
assert re.fullmatch("<Node /sub in '.*'>",
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/8<l7:1 l8:2>l9:,[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')