zephyr/scripts/dts/testedtlib.py
Ulf Magnusson 06b746cc58 dts: dtlib/edtlib: Add a syntax-based type-checking system
Property type-checking has been pretty rudimentary until now, only
checking things like the length being divisible by 4 for 'type: array',
and strings being null-terminated. In particular, no checking was done
for 'type: uint8-array', letting

  jedec-id = < 0xc8 0x28 0x17 >;

slip through when

  jedec-id = [ 0xc8 0x28 0x17 ];

was intended.

Fix it by adding a syntax-based type checker:

  1. Add Property.type, which gives a high-level type for the property,
     derived from the markers added in the previous commit.

     This includes types like TYPE_EMPTY ('foo;'),
     TYPE_NUM ('foo = < 3 >;'), TYPE_BYTES ('foo = [ 01 02 ];'),
     TYPE_STRINGS ('foo = "bar", "baz"'),
     TYPE_PHANDLE ('foo = < &bar >;'), and TYPE_COMPOUND (everything not
     recognized).

     See the Property.type docstring in dtlib for more info.

  2. Use the high-level type in
     Property.to_num()/to_string()/to_node()/etc. to verify that the
     property was assigned in an expected way for the type.

     If the assignment looks bad, give a helpful error:

       expected property 'nums' on /foo/bar in some.dts to be assigned
       with 'nums = < (number) (number) ... >', not 'nums = "oops";'

Some other related changes are included as well:

  - There's a new Property.to_bytes() function that works like accessing
    Property.bytes, except with an added check for the value being
    assigned like 'foo = [ ... ]'.

    This function solves problems like the jedec-id one.

  - There's a new Property.to_path() function for fetching the
    referenced node for assignments like 'foo = &node;', with type
    checking. (Strings are accepted too, as long as they give the path
    to an existing node.)

    This function is used for /chosen and /aliases.

  - A new 'type: phandle' type can now be given in bindings, for
    properties that are assigned like 'foo = < &node >;'.

  - Property.__str__() now displays phandles and path references as they
    were written (e.g. '< &foo >' instead of '< 0x1 >', if the
    allocated phandle happened to be 1).

  - Property.to_num() and Property.to_nums() no longer take a 'length'
    parameter, because it makes no sense with the type checking.

  - The global dtlib.to_string() and dtlib.to_strings() functions were
    removed, because they're not that useful.

  - More tests were added, along with misc. minor cleanup in various
    places.

  - Probably other stuff I forgot.

The more strict type checking in dtlib indirectly makes some parts of
edtlib more strict as well (wherever Property.to_*() is used).

Fixes: #18131

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
2019-08-13 07:41:45 -05:00

143 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2019 Nordic Semiconductor ASA
# SPDX-License-Identifier: BSD-3-Clause
import edtlib
# Test suite for edtlib.py. Mostly uses string comparisons via the various
# __repr__() methods. Can be run directly as an executable.
#
# This script expects to be run from the directory its in. This simplifies
# things, as paths in the output can be assumed below.
#
# test.dts is the test file, and test-bindings/ has bindings.
def run():
"""
Runs all edtlib tests. Immediately exits with status 1 and a message on
stderr on test suite failures.
"""
def fail(msg):
raise Exception("test failed: " + msg)
def verify_streq(actual, expected):
actual = str(actual)
if actual != expected:
# Put values on separate lines to make it easy to spot differences
fail("not equal (expected value last):\n'{}'\n'{}'"
.format(actual, expected))
edt = edtlib.EDT("test.dts", ["test-bindings"])
#
# Test interrupts
#
verify_streq(edt.get_dev("/interrupt-parent-test/node").interrupts,
"[<Interrupt, name: foo, target: <Device /interrupt-parent-test/controller in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, specifier: {'one': 1, 'two': 2, 'three': 3}>, <Interrupt, name: bar, target: <Device /interrupt-parent-test/controller in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, specifier: {'one': 4, 'two': 5, 'three': 6}>]")
verify_streq(edt.get_dev("/interrupts-extended-test/node").interrupts,
"[<Interrupt, target: <Device /interrupts-extended-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, specifier: {'one': 1}>, <Interrupt, target: <Device /interrupts-extended-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, specifier: {'one': 2, 'two': 3}>, <Interrupt, target: <Device /interrupts-extended-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, specifier: {'one': 4, 'two': 5, 'three': 6}>]")
verify_streq(edt.get_dev("/interrupt-map-test/node@0").interrupts,
"[<Interrupt, target: <Device /interrupt-map-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, specifier: {'one': 0}>, <Interrupt, target: <Device /interrupt-map-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, specifier: {'one': 0, 'two': 1}>, <Interrupt, target: <Device /interrupt-map-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, specifier: {'one': 0, 'two': 0, 'three': 2}>]")
verify_streq(edt.get_dev("/interrupt-map-test/node@1").interrupts,
"[<Interrupt, target: <Device /interrupt-map-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, specifier: {'one': 3}>, <Interrupt, target: <Device /interrupt-map-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, specifier: {'one': 0, 'two': 4}>, <Interrupt, target: <Device /interrupt-map-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, specifier: {'one': 0, 'two': 0, 'three': 5}>]")
verify_streq(edt.get_dev("/interrupt-map-bitops-test/node@70000000E").interrupts,
"[<Interrupt, target: <Device /interrupt-map-bitops-test/controller in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, specifier: {'one': 3, 'two': 2}>]")
#
# Test GPIOs
#
verify_streq(edt.get_dev("/gpio-test/node").gpios,
"{'': [<GPIO, name: , target: <Device /gpio-test/controller-0 in 'test.dts', binding test-bindings/gpio-1-cell.yaml>, specifier: {'one': 1}>, <GPIO, name: , target: <Device /gpio-test/controller-1 in 'test.dts', binding test-bindings/gpio-2-cell.yaml>, specifier: {'one': 2, 'two': 3}>], 'foo': [<GPIO, name: foo, target: <Device /gpio-test/controller-1 in 'test.dts', binding test-bindings/gpio-2-cell.yaml>, specifier: {'one': 4, 'two': 5}>], 'bar': [<GPIO, name: bar, target: <Device /gpio-test/controller-1 in 'test.dts', binding test-bindings/gpio-2-cell.yaml>, specifier: {'one': 6, 'two': 7}>]}")
#
# Test clocks
#
verify_streq(edt.get_dev("/clock-test/node").clocks,
"[<Clock, name: fixed, frequency: 123, target: <Device /clock-test/fixed-clock in 'test.dts', binding test-bindings/fixed-clock.yaml>, specifier: {}>, <Clock, name: one-cell, target: <Device /clock-test/clock-1 in 'test.dts', binding test-bindings/clock-1-cell.yaml>, specifier: {'one': 1}>, <Clock, name: two-cell, target: <Device /clock-test/clock-2 in 'test.dts', binding test-bindings/clock-2-cell.yaml>, specifier: {'one': 1, 'two': 2}>]")
#
# Test PWMs
#
verify_streq(edt.get_dev("/pwm-test/node").pwms,
"[<PWM, name: zero-cell, target: <Device /pwm-test/pwm-0 in 'test.dts', binding test-bindings/pwm-0-cell.yaml>, specifier: {}>, <PWM, name: one-cell, target: <Device /pwm-test/pwm-1 in 'test.dts', binding test-bindings/pwm-1-cell.yaml>, specifier: {'one': 1}>]")
#
# Test IO channels
#
verify_streq(edt.get_dev("/io-channel-test/node").iochannels,
"[<IOChannel, name: io-channel, target: <Device /io-channel-test/io-channel in 'test.dts', binding test-bindings/io-channel.yaml>, specifier: {'one': 1}>]")
#
# Test 'reg'
#
verify_streq(edt.get_dev("/reg-zero-address-cells/node").regs,
"[<Register, addr: 0x0, size: 0x1>, <Register, addr: 0x0, size: 0x2>]")
verify_streq(edt.get_dev("/reg-zero-size-cells/node").regs,
"[<Register, addr: 0x1, size: 0x0>, <Register, addr: 0x2, size: 0x0>]")
verify_streq(edt.get_dev("/reg-ranges/parent/node").regs,
"[<Register, addr: 0x5, size: 0x1>, <Register, addr: 0xe0000000f, size: 0x1>, <Register, addr: 0xc0000000e, size: 0x1>, <Register, addr: 0xc0000000d, size: 0x1>, <Register, addr: 0xa0000000b, size: 0x1>, <Register, addr: 0x0, size: 0x1>]")
verify_streq(edt.get_dev("/reg-nested-ranges/grandparent/parent/node").regs,
"[<Register, addr: 0x30000000200000001, size: 0x1>]")
#
# Test !include in bindings
#
verify_streq(edt.get_dev("/binding-include").description,
"Parent binding")
verify_streq(edt.get_dev("/binding-include").props,
"{'compatible': <Property, name: compatible, value: ['binding-include-test']>, 'foo': <Property, name: foo, value: 0>, 'bar': <Property, name: bar, value: 1>, 'baz': <Property, name: baz, value: 2>}")
#
# Test 'sub-node:' in binding
#
verify_streq(edt.get_dev("/parent-with-sub-node/node").description,
"Sub-node test")
verify_streq(edt.get_dev("/parent-with-sub-node/node").props,
"{'foo': <Property, name: foo, value: 1>, 'bar': <Property, name: bar, value: 2>}")
#
# Test Device.property (derived from DT and 'properties:' in the binding)
#
verify_streq(edt.get_dev("/props").props,
r"{'compatible': <Property, name: compatible, value: ['props']>, 'nonexistent-boolean': <Property, name: nonexistent-boolean, value: False>, 'existent-boolean': <Property, name: existent-boolean, value: True>, 'int': <Property, name: int, value: 1>, 'array': <Property, name: array, value: [1, 2, 3]>, 'uint8-array': <Property, name: uint8-array, value: b'\x124'>, 'string': <Property, name: string, value: 'foo'>, 'string-array': <Property, name: string-array, value: ['foo', 'bar', 'baz']>, 'phandle-ref': <Property, name: phandle-ref, value: <Device /props/node in 'test.dts', no binding>>}")
#
# Test having multiple directories with bindings, with a different .dts file
#
edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"])
verify_streq(edt.get_dev("/in-dir-1").binding_path,
"test-bindings/multidir.yaml")
verify_streq(edt.get_dev("/in-dir-2").binding_path,
"test-bindings-2/multidir.yaml")
print("all tests passed")
if __name__ == "__main__":
run()