You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2501 lines
50 KiB
2501 lines
50 KiB
# 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 |
|
*/</**/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 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')
|
|
|