scripts: dts: convert test suites to pytest

Use the pytest test framework in the dtlib.py and edtlib.py test
suites (testdtlib.py and testedtlib.py respectively).

The goal here is not to change what is being tested. The existing test
suite is excellent and very thorough.

However, it is made up of executable scripts where all of the tests
are run using a hand-rolled framework in a single function per file.
This is a bit all-or-nothing and prevents various nice features
available in the de-facto standard pytest test framework from being
used.

In particular, pytest can:

- drop into a debugger (pdb) when there is a problem
- accept a pattern which specifies a subset of tests to run
- print very detailed error messages about the actual and expected
  results in various traceback formats from brief to very verbose
- gather coverage data for the python scripts being tested (via plugin)
- run tests in parallel (via plugin)
- It's easy in pytest to run tests with temporary directories
  using the tmp_path and other fixtures. This us avoid
  temporarily dirtying the working tree as is done now.

Moving to pytest lets us leverage all of these things without any loss
in ease of use (in fact, some things are nicer in pytest):

- Any function that starts with "test_" is automatically picked up and
  run. No need for rolling up lists of functions into a test suite.
- Tests are written using ordinary Python 'assert'
  statements.
- Pytest magic unpacks the AST of failed asserts to print details on
  what went wrong in really nice ways. For example, it will show you
  exactly what parts of two strings that are expected to be equal
  differ.

For the most part, this is a pretty mechanical conversion:

- extract helpers and test cases into separate functions
- insert temporary paths and adjust tests accordingly to not match
  file names exactly
- use 'assert CONDITION' instead of 'if not CONDITION: fail()'

There are a few cases where making this happen required slightly
larger changes than that, but they are limited.

Move the checks from check_compliance.py to a new GitHub workflow,
removing hacks that are no longer needed.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Martí Bolívar 2020-09-29 17:04:34 -07:00 committed by Carles Cufí
parent 0e7a76e462
commit a8612f75c5
6 changed files with 788 additions and 819 deletions

53
.github/workflows/devicetree_checks.yml vendored Normal file
View file

@ -0,0 +1,53 @@
# Copyright (c) 2020 Linaro Limited.
# Copyright (c) 2020 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
name: Devicetree script tests
on:
push:
paths:
- 'scripts/dts/**'
pull_request:
paths:
- 'scripts/dts/**'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
os: [ubuntu-latest, macos-latest]
steps:
- name: checkout
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: cache-pip-linux
if: startsWith(runner.os, 'Linux')
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}
- name: cache-pip-mac
if: startsWith(runner.os, 'macOS')
uses: actions/cache@v1
with:
path: ~/Library/Caches/pip
# Trailing '-' was just to get a different cache name
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
- name: install python dependencies
run: |
pip3 install wheel
pip3 install pytest pyyaml
- name: run pytest
working-directory: scripts/dts
run: |
python -m pytest testdtlib.py testedtlib.py

View file

@ -550,51 +550,6 @@ UNDEF_KCONFIG_WHITELIST = {
}
class DeviceTreeCheck(ComplianceTest):
"""
Runs the dtlib and edtlib test suites in scripts/dts/.
"""
name = "Devicetree"
doc = "See https://docs.zephyrproject.org/latest/guides/dts/index.html for more details"
path_hint = ZEPHYR_BASE
def run(self):
if not ZEPHYR_BASE:
self.skip("Not a Zephyr tree (ZEPHYR_BASE unset)")
scripts_path = os.path.join(ZEPHYR_BASE, "scripts", "dts")
sys.path.insert(0, scripts_path)
import testdtlib
import testedtlib
# Hack: The test suites expect to be run from the scripts/dts
# directory, because they compare repr() output that contains relative
# paths against an expected string. Temporarily change the working
# directory to scripts/dts/.
#
# Warning: This is not thread-safe, though the test suites run in a
# fraction of a second.
old_dir = os.getcwd()
os.chdir(scripts_path)
try:
logger.info("cd %s && ./testdtlib.py", scripts_path)
testdtlib.run()
logger.info("cd %s && ./testedtlib.py", scripts_path)
testedtlib.run()
except SystemExit as e:
# The dtlib and edtlib test suites call sys.exit() on failure,
# which raises SystemExit. Let any errors in the test scripts
# themselves trickle through and turn into an internal CI error.
self.add_failure(str(e))
except Exception as e:
# Report other exceptions as an internal test failure
self.error(str(e))
finally:
# Restore working directory
os.chdir(old_dir)
class Codeowners(ComplianceTest):
"""
Check if added files have an owner.

View file

@ -23,11 +23,7 @@ import re
import sys
import textwrap
# NOTE: testdtlib.py is the test suite for this library. It can be run directly
# as a script:
#
# ./testdtlib.py
# NOTE: testdtlib.py is the test suite for this library.
class DT:
"""

View file

@ -22,10 +22,7 @@ The top-level entry point of the library is the EDT class. EDT.__init__() takes
a .dts file to parse and a list of paths to directories containing bindings.
"""
# NOTE: testedtlib.py is the test suite for this library. It can be run
# directly as a script:
#
# ./testedtlib.py
# NOTE: testedtlib.py is the test suite for this library.
# Implementation notes
# --------------------

1116
scripts/dts/testdtlib.py Executable file → Normal file

File diff suppressed because it is too large Load diff

382
scripts/dts/testedtlib.py Executable file → Normal file
View file

@ -1,258 +1,251 @@
#!/usr/bin/env python3
# Copyright (c) 2019 Nordic Semiconductor ASA
# SPDX-License-Identifier: BSD-3-Clause
import io
import os
import sys
import pytest
import edtlib
# Test suite for edtlib.py. Run it directly as an executable, in this
# directory:
# Test suite for edtlib.py.
#
# $ ./testedtlib.py
# Run it using pytest (https://docs.pytest.org/en/stable/usage.html):
#
# $ pytest testedtlib.py
#
# See the comment near the top of testdtlib.py for additional pytest advice.
#
# test.dts is the main test file. test-bindings/ and test-bindings-2/ has
# bindings. The tests mostly use string comparisons via the various __repr__()
# methods.
def run():
"""
Runs all edtlib tests. Immediately exits with status 1 and a message on
stderr on test suite failures.
"""
def test_warnings():
'''Tests for situations that should cause warnings.'''
warnings = io.StringIO()
edt = edtlib.EDT("test.dts", ["test-bindings"], warnings)
edtlib.EDT("test.dts", ["test-bindings"], warnings)
# Verify warnings
verify_eq(warnings.getvalue(), """\
assert warnings.getvalue() == """\
warning: unit address and first address in 'reg' (0x1) don't match for /reg-zero-size-cells/node
warning: unit address and first address in 'reg' (0x5) don't match for /reg-ranges/parent/node
warning: unit address and first address in 'reg' (0x30000000200000001) don't match for /reg-nested-ranges/grandparent/parent/node
""")
"""
#
# Test interrupts
#
def test_interrupts():
'''Tests for the interrupts property.'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.get_node("/interrupt-parent-test/node").interrupts,
"[<ControllerAndData, name: foo, controller: <Node /interrupt-parent-test/controller in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 1), ('two', 2), ('three', 3)])>, <ControllerAndData, name: bar, controller: <Node /interrupt-parent-test/controller in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 4), ('two', 5), ('three', 6)])>]")
assert str(edt.get_node("/interrupt-parent-test/node").interrupts) == \
"[<ControllerAndData, name: foo, controller: <Node /interrupt-parent-test/controller in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 1), ('two', 2), ('three', 3)])>, <ControllerAndData, name: bar, controller: <Node /interrupt-parent-test/controller in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 4), ('two', 5), ('three', 6)])>]"
verify_streq(edt.get_node("/interrupts-extended-test/node").interrupts,
"[<ControllerAndData, controller: <Node /interrupts-extended-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, data: OrderedDict([('one', 1)])>, <ControllerAndData, controller: <Node /interrupts-extended-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 2), ('two', 3)])>, <ControllerAndData, controller: <Node /interrupts-extended-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 4), ('two', 5), ('three', 6)])>]")
assert str(edt.get_node("/interrupts-extended-test/node").interrupts) == \
"[<ControllerAndData, controller: <Node /interrupts-extended-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, data: OrderedDict([('one', 1)])>, <ControllerAndData, controller: <Node /interrupts-extended-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 2), ('two', 3)])>, <ControllerAndData, controller: <Node /interrupts-extended-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 4), ('two', 5), ('three', 6)])>]"
verify_streq(edt.get_node("/interrupt-map-test/node@0").interrupts,
"[<ControllerAndData, controller: <Node /interrupt-map-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, data: OrderedDict([('one', 0)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 0), ('two', 1)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 0), ('two', 0), ('three', 2)])>]")
assert str(edt.get_node("/interrupt-map-test/node@0").interrupts) == \
"[<ControllerAndData, controller: <Node /interrupt-map-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, data: OrderedDict([('one', 0)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 0), ('two', 1)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 0), ('two', 0), ('three', 2)])>]"
verify_streq(edt.get_node("/interrupt-map-test/node@1").interrupts,
"[<ControllerAndData, controller: <Node /interrupt-map-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, data: OrderedDict([('one', 3)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 0), ('two', 4)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 0), ('two', 0), ('three', 5)])>]")
assert str(edt.get_node("/interrupt-map-test/node@1").interrupts) == \
"[<ControllerAndData, controller: <Node /interrupt-map-test/controller-0 in 'test.dts', binding test-bindings/interrupt-1-cell.yaml>, data: OrderedDict([('one', 3)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-1 in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 0), ('two', 4)])>, <ControllerAndData, controller: <Node /interrupt-map-test/controller-2 in 'test.dts', binding test-bindings/interrupt-3-cell.yaml>, data: OrderedDict([('one', 0), ('two', 0), ('three', 5)])>]"
verify_streq(edt.get_node("/interrupt-map-bitops-test/node@70000000E").interrupts,
"[<ControllerAndData, controller: <Node /interrupt-map-bitops-test/controller in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 3), ('two', 2)])>]")
assert str(edt.get_node("/interrupt-map-bitops-test/node@70000000E").interrupts) == \
"[<ControllerAndData, controller: <Node /interrupt-map-bitops-test/controller in 'test.dts', binding test-bindings/interrupt-2-cell.yaml>, data: OrderedDict([('one', 3), ('two', 2)])>]"
#
# Test 'reg'
#
def test_reg():
'''Tests for the regs property'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.get_node("/reg-zero-address-cells/node").regs,
"[<Register, size: 0x1>, <Register, size: 0x2>]")
assert str(edt.get_node("/reg-zero-address-cells/node").regs) == \
"[<Register, size: 0x1>, <Register, size: 0x2>]"
verify_streq(edt.get_node("/reg-zero-size-cells/node").regs,
"[<Register, addr: 0x1>, <Register, addr: 0x2>]")
assert str(edt.get_node("/reg-zero-size-cells/node").regs) == \
"[<Register, addr: 0x1>, <Register, addr: 0x2>]"
verify_streq(edt.get_node("/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>]")
assert str(edt.get_node("/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_node("/reg-nested-ranges/grandparent/parent/node").regs,
"[<Register, addr: 0x30000000200000001, size: 0x1>]")
assert str(edt.get_node("/reg-nested-ranges/grandparent/parent/node").regs) == \
"[<Register, addr: 0x30000000200000001, size: 0x1>]"
#
# Test 'pinctrl-<index>'
#
def test_pinctrl():
'''Test 'pinctrl-<index>'.'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.get_node("/pinctrl/dev").pinctrls,
"[<PinCtrl, name: zero, configuration nodes: []>, <PinCtrl, name: one, configuration nodes: [<Node /pinctrl/pincontroller/state-1 in 'test.dts', no binding>]>, <PinCtrl, name: two, configuration nodes: [<Node /pinctrl/pincontroller/state-1 in 'test.dts', no binding>, <Node /pinctrl/pincontroller/state-2 in 'test.dts', no binding>]>]")
assert str(edt.get_node("/pinctrl/dev").pinctrls) == \
"[<PinCtrl, name: zero, configuration nodes: []>, <PinCtrl, name: one, configuration nodes: [<Node /pinctrl/pincontroller/state-1 in 'test.dts', no binding>]>, <PinCtrl, name: two, configuration nodes: [<Node /pinctrl/pincontroller/state-1 in 'test.dts', no binding>, <Node /pinctrl/pincontroller/state-2 in 'test.dts', no binding>]>]"
#
# Test Node.parent and Node.children
#
def test_hierarchy():
'''Test Node.parent and Node.children'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_eq(edt.get_node("/").parent, None)
assert edt.get_node("/").parent is None
verify_streq(edt.get_node("/parent/child-1").parent,
"<Node /parent in 'test.dts', no binding>")
assert str(edt.get_node("/parent/child-1").parent) == \
"<Node /parent in 'test.dts', no binding>"
verify_streq(edt.get_node("/parent/child-2/grandchild").parent,
"<Node /parent/child-2 in 'test.dts', no binding>")
assert str(edt.get_node("/parent/child-2/grandchild").parent) == \
"<Node /parent/child-2 in 'test.dts', no binding>"
verify_streq(edt.get_node("/parent").children,
"OrderedDict([('child-1', <Node /parent/child-1 in 'test.dts', no binding>), ('child-2', <Node /parent/child-2 in 'test.dts', no binding>)])")
assert str(edt.get_node("/parent").children) == \
"OrderedDict([('child-1', <Node /parent/child-1 in 'test.dts', no binding>), ('child-2', <Node /parent/child-2 in 'test.dts', no binding>)])"
verify_eq(edt.get_node("/parent/child-1").children, {})
assert edt.get_node("/parent/child-1").children == {}
#
# Test 'include:' and the legacy 'inherits: !include ...'
#
def test_include():
'''Test 'include:' and the legacy 'inherits: !include ...' in bindings'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.get_node("/binding-include").description,
"Parent binding")
assert str(edt.get_node("/binding-include").description) == \
"Parent binding"
verify_streq(edt.get_node("/binding-include").props,
"OrderedDict([('foo', <Property, name: foo, type: int, value: 0>), ('bar', <Property, name: bar, type: int, value: 1>), ('baz', <Property, name: baz, type: int, value: 2>), ('qaz', <Property, name: qaz, type: int, value: 3>)])")
assert str(edt.get_node("/binding-include").props) == \
"OrderedDict([('foo', <Property, name: foo, type: int, value: 0>), ('bar', <Property, name: bar, type: int, value: 1>), ('baz', <Property, name: baz, type: int, value: 2>), ('qaz', <Property, name: qaz, type: int, value: 3>)])"
#
# Test 'bus:' and 'on-bus:'
#
def test_bus():
'''Test 'bus:' and 'on-bus:' in bindings'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
assert edt.get_node("/buses/foo-bus").bus == "foo"
verify_eq(edt.get_node("/buses/foo-bus").bus, "foo")
# foo-bus does not itself appear on a bus
verify_eq(edt.get_node("/buses/foo-bus").on_bus, None)
verify_eq(edt.get_node("/buses/foo-bus").bus_node, None)
assert edt.get_node("/buses/foo-bus").on_bus is None
assert edt.get_node("/buses/foo-bus").bus_node is None
# foo-bus/node is not a bus node...
verify_eq(edt.get_node("/buses/foo-bus/node").bus, None)
assert edt.get_node("/buses/foo-bus/node").bus is None
# ...but is on a bus
verify_eq(edt.get_node("/buses/foo-bus/node").on_bus, "foo")
verify_eq(edt.get_node("/buses/foo-bus/node").bus_node.path,
"/buses/foo-bus")
assert edt.get_node("/buses/foo-bus/node").on_bus == "foo"
assert edt.get_node("/buses/foo-bus/node").bus_node.path == \
"/buses/foo-bus"
# Same compatible string, but different bindings from being on different
# buses
verify_streq(edt.get_node("/buses/foo-bus/node").binding_path,
"test-bindings/device-on-foo-bus.yaml")
verify_streq(edt.get_node("/buses/bar-bus/node").binding_path,
"test-bindings/device-on-bar-bus.yaml")
assert str(edt.get_node("/buses/foo-bus/node").binding_path) == \
"test-bindings/device-on-foo-bus.yaml"
assert str(edt.get_node("/buses/bar-bus/node").binding_path) == \
"test-bindings/device-on-bar-bus.yaml"
# foo-bus/node/nested also appears on the foo-bus bus
verify_eq(edt.get_node("/buses/foo-bus/node/nested").on_bus, "foo")
verify_streq(edt.get_node("/buses/foo-bus/node/nested").binding_path,
"test-bindings/device-on-foo-bus.yaml")
#
# Test 'child-binding:'
#
assert edt.get_node("/buses/foo-bus/node/nested").on_bus == "foo"
assert str(edt.get_node("/buses/foo-bus/node/nested").binding_path) == \
"test-bindings/device-on-foo-bus.yaml"
def test_child_binding():
'''Test 'child-binding:' in bindings'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
child1 = edt.get_node("/child-binding/child-1")
child2 = edt.get_node("/child-binding/child-2")
grandchild = edt.get_node("/child-binding/child-1/grandchild")
verify_streq(child1.binding_path, "test-bindings/child-binding.yaml")
verify_streq(child1.description, "child node")
verify_streq(child1.props, "OrderedDict([('child-prop', <Property, name: child-prop, type: int, value: 1>)])")
assert str(child1.binding_path) == "test-bindings/child-binding.yaml"
assert str(child1.description) == "child node"
assert str(child1.props) == "OrderedDict([('child-prop', <Property, name: child-prop, type: int, value: 1>)])"
verify_streq(child2.binding_path, "test-bindings/child-binding.yaml")
verify_streq(child2.description, "child node")
verify_streq(child2.props, "OrderedDict([('child-prop', <Property, name: child-prop, type: int, value: 3>)])")
assert str(child2.binding_path) == "test-bindings/child-binding.yaml"
assert str(child2.description) == "child node"
assert str(child2.props) == "OrderedDict([('child-prop', <Property, name: child-prop, type: int, value: 3>)])"
verify_streq(grandchild.binding_path, "test-bindings/child-binding.yaml")
verify_streq(grandchild.description, "grandchild node")
verify_streq(grandchild.props, "OrderedDict([('grandchild-prop', <Property, name: grandchild-prop, type: int, value: 2>)])")
assert str(grandchild.binding_path) == "test-bindings/child-binding.yaml"
assert str(grandchild.description) == "grandchild node"
assert str(grandchild.props) == "OrderedDict([('grandchild-prop', <Property, name: grandchild-prop, type: int, value: 2>)])"
#
# Test EDT.compat2enabled
#
def test_compat2enabled():
'''Test EDT.compat2enabled'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.compat2enabled["compat2enabled"], "[<Node /compat2enabled/foo-1 in 'test.dts', no binding>, <Node /compat2enabled/foo-2 in 'test.dts', no binding>]")
assert str(edt.compat2enabled["compat2enabled"]) == \
"[<Node /compat2enabled/foo-1 in 'test.dts', no binding>, <Node /compat2enabled/foo-2 in 'test.dts', no binding>]"
if "compat2enabled-disabled" in edt.compat2enabled:
fail("'compat2enabled-disabled' should not appear in edt.compat2enabled")
assert "compat2enabled-disabled" not in edt.compat2enabled
#
# Test Node.props (derived from DT and 'properties:' in the binding)
#
def test_props():
'''Test Node.props (derived from DT and 'properties:' in the binding)'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.get_node("/props").props["int"],
"<Property, name: int, type: int, value: 1>")
assert str(edt.get_node("/props").props["int"]) == \
"<Property, name: int, type: int, value: 1>"
verify_streq(edt.get_node("/props").props["existent-boolean"],
"<Property, name: existent-boolean, type: boolean, value: True>")
assert str(edt.get_node("/props").props["existent-boolean"]) == \
"<Property, name: existent-boolean, type: boolean, value: True>"
verify_streq(edt.get_node("/props").props["nonexistent-boolean"],
"<Property, name: nonexistent-boolean, type: boolean, value: False>")
assert str(edt.get_node("/props").props["nonexistent-boolean"]) == \
"<Property, name: nonexistent-boolean, type: boolean, value: False>"
verify_streq(edt.get_node("/props").props["array"],
"<Property, name: array, type: array, value: [1, 2, 3]>")
assert str(edt.get_node("/props").props["array"]) == \
"<Property, name: array, type: array, value: [1, 2, 3]>"
verify_streq(edt.get_node("/props").props["uint8-array"],
r"<Property, name: uint8-array, type: uint8-array, value: b'\x124'>")
assert str(edt.get_node("/props").props["uint8-array"]) == \
r"<Property, name: uint8-array, type: uint8-array, value: b'\x124'>"
verify_streq(edt.get_node("/props").props["string"],
"<Property, name: string, type: string, value: 'foo'>")
assert str(edt.get_node("/props").props["string"]) == \
"<Property, name: string, type: string, value: 'foo'>"
verify_streq(edt.get_node("/props").props["string-array"],
"<Property, name: string-array, type: string-array, value: ['foo', 'bar', 'baz']>")
assert str(edt.get_node("/props").props["string-array"]) == \
"<Property, name: string-array, type: string-array, value: ['foo', 'bar', 'baz']>"
verify_streq(edt.get_node("/props").props["phandle-ref"],
"<Property, name: phandle-ref, type: phandle, value: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>>")
assert str(edt.get_node("/props").props["phandle-ref"]) == \
"<Property, name: phandle-ref, type: phandle, value: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>>"
verify_streq(edt.get_node("/props").props["phandle-refs"],
"<Property, name: phandle-refs, type: phandles, value: [<Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>]>")
assert str(edt.get_node("/props").props["phandle-refs"]) == \
"<Property, name: phandle-refs, type: phandles, value: [<Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>]>"
verify_streq(edt.get_node("/props").props["phandle-array-foos"],
"<Property, name: phandle-array-foos, type: phandle-array, value: [<ControllerAndData, controller: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, data: OrderedDict([('one', 1)])>, <ControllerAndData, controller: <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>, data: OrderedDict([('one', 2), ('two', 3)])>]>")
assert str(edt.get_node("/props").props["phandle-array-foos"]) == \
"<Property, name: phandle-array-foos, type: phandle-array, value: [<ControllerAndData, controller: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, data: OrderedDict([('one', 1)])>, <ControllerAndData, controller: <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>, data: OrderedDict([('one', 2), ('two', 3)])>]>"
verify_streq(edt.get_node("/props").props["foo-gpios"],
"<Property, name: foo-gpios, type: phandle-array, value: [<ControllerAndData, controller: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, data: OrderedDict([('gpio-one', 1)])>]>")
assert str(edt.get_node("/props").props["foo-gpios"]) == \
"<Property, name: foo-gpios, type: phandle-array, value: [<ControllerAndData, controller: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, data: OrderedDict([('gpio-one', 1)])>]>"
verify_streq(edt.get_node("/props").props["path"],
"<Property, name: path, type: path, value: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>>")
assert str(edt.get_node("/props").props["path"]) == \
"<Property, name: path, type: path, value: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>>"
#
# Test <prefix>-map, via gpio-map (the most common case)
#
def test_nexus():
'''Test <prefix>-map via gpio-map (the most common case).'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.get_node("/gpio-map/source").props["foo-gpios"],
"<Property, name: foo-gpios, type: phandle-array, value: [<ControllerAndData, controller: <Node /gpio-map/destination in 'test.dts', binding test-bindings/gpio-dst.yaml>, data: OrderedDict([('val', 6)])>, <ControllerAndData, controller: <Node /gpio-map/destination in 'test.dts', binding test-bindings/gpio-dst.yaml>, data: OrderedDict([('val', 5)])>]>")
assert str(edt.get_node("/gpio-map/source").props["foo-gpios"]) == \
"<Property, name: foo-gpios, type: phandle-array, value: [<ControllerAndData, controller: <Node /gpio-map/destination in 'test.dts', binding test-bindings/gpio-dst.yaml>, data: OrderedDict([('val', 6)])>, <ControllerAndData, controller: <Node /gpio-map/destination in 'test.dts', binding test-bindings/gpio-dst.yaml>, data: OrderedDict([('val', 5)])>]>"
#
# Test property default values given in bindings
#
def test_prop_defaults():
'''Test property default values given in bindings'''
edt = edtlib.EDT("test.dts", ["test-bindings"])
verify_streq(edt.get_node("/defaults").props,
r"OrderedDict([('int', <Property, name: int, type: int, value: 123>), ('array', <Property, name: array, type: array, value: [1, 2, 3]>), ('uint8-array', <Property, name: uint8-array, type: uint8-array, value: b'\x89\xab\xcd'>), ('string', <Property, name: string, type: string, value: 'hello'>), ('string-array', <Property, name: string-array, type: string-array, value: ['hello', 'there']>), ('default-not-used', <Property, name: default-not-used, type: int, value: 234>)])")
assert str(edt.get_node("/defaults").props) == \
r"OrderedDict([('int', <Property, name: int, type: int, value: 123>), ('array', <Property, name: array, type: array, value: [1, 2, 3]>), ('uint8-array', <Property, name: uint8-array, type: uint8-array, value: b'\x89\xab\xcd'>), ('string', <Property, name: string, type: string, value: 'hello'>), ('string-array', <Property, name: string-array, type: string-array, value: ['hello', 'there']>), ('default-not-used', <Property, name: default-not-used, type: int, value: 234>)])"
#
# Test binding inference
#
def test_binding_inference():
'''Test inferred bindings for special zephyr-specific nodes.'''
warnings = io.StringIO()
edt = edtlib.EDT("test.dts", ["test-bindings"], warnings)
verify_streq(edt.get_node("/zephyr,user").props, r"OrderedDict()")
assert str(edt.get_node("/zephyr,user").props) == r"OrderedDict()"
edt = edtlib.EDT("test.dts", ["test-bindings"], warnings,
infer_binding_for_paths=["/zephyr,user"])
verify_streq(edt.get_node("/zephyr,user").props,
r"OrderedDict([('boolean', <Property, name: boolean, type: boolean, value: True>), ('bytes', <Property, name: bytes, type: uint8-array, value: b'\x81\x82\x83'>), ('number', <Property, name: number, type: int, value: 23>), ('numbers', <Property, name: numbers, type: array, value: [1, 2, 3]>), ('string', <Property, name: string, type: string, value: 'text'>), ('strings', <Property, name: strings, type: string-array, value: ['a', 'b', 'c']>), ('handle', <Property, name: handle, type: phandle, value: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>>), ('phandles', <Property, name: phandles, type: phandles, value: [<Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>]>), ('phandle-array-foos', <Property, name: phandle-array-foos, type: phandle-array, value: [<ControllerAndData, controller: <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>, data: OrderedDict([('one', 1), ('two', 2)])>]>)])")
#
# Test having multiple directories with bindings, with a different .dts file
#
assert str(edt.get_node("/zephyr,user").props) == \
r"OrderedDict([('boolean', <Property, name: boolean, type: boolean, value: True>), ('bytes', <Property, name: bytes, type: uint8-array, value: b'\x81\x82\x83'>), ('number', <Property, name: number, type: int, value: 23>), ('numbers', <Property, name: numbers, type: array, value: [1, 2, 3]>), ('string', <Property, name: string, type: string, value: 'text'>), ('strings', <Property, name: strings, type: string-array, value: ['a', 'b', 'c']>), ('handle', <Property, name: handle, type: phandle, value: <Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>>), ('phandles', <Property, name: phandles, type: phandles, value: [<Node /props/ctrl-1 in 'test.dts', binding test-bindings/phandle-array-controller-1.yaml>, <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>]>), ('phandle-array-foos', <Property, name: phandle-array-foos, type: phandle-array, value: [<ControllerAndData, controller: <Node /props/ctrl-2 in 'test.dts', binding test-bindings/phandle-array-controller-2.yaml>, data: OrderedDict([('one', 1), ('two', 2)])>]>)])"
def test_multi_bindings():
'''Test having multiple directories with bindings'''
edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"])
verify_streq(edt.get_node("/in-dir-1").binding_path,
"test-bindings/multidir.yaml")
assert str(edt.get_node("/in-dir-1").binding_path) == \
"test-bindings/multidir.yaml"
verify_streq(edt.get_node("/in-dir-2").binding_path,
"test-bindings-2/multidir.yaml")
assert str(edt.get_node("/in-dir-2").binding_path) == \
"test-bindings-2/multidir.yaml"
#
# Test dependency relations
#
def test_dependencies():
''''Test dependency relations'''
edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"])
verify_eq(edt.get_node("/").dep_ordinal, 0)
verify_eq(edt.get_node("/in-dir-1").dep_ordinal, 1)
if edt.get_node("/") not in edt.get_node("/in-dir-1").depends_on:
fail("/ should be a direct dependency of /in-dir-1")
if edt.get_node("/in-dir-1") not in edt.get_node("/").required_by:
fail("/in-dir-1 should directly depend on /")
assert edt.get_node("/").dep_ordinal == 0
assert edt.get_node("/in-dir-1").dep_ordinal == 1
assert edt.get_node("/") in edt.get_node("/in-dir-1").depends_on
assert edt.get_node("/in-dir-1") in edt.get_node("/").required_by
#
# Test error messages from _slice()
#
def test_slice_errs(tmp_path):
'''Test error messages from the internal _slice() helper'''
dts_file = tmp_path / "error.dts"
verify_error("""
/dts-v1/;
@ -265,7 +258,9 @@ warning: unit address and first address in 'reg' (0x30000000200000001) don't mat
reg = <3>;
};
};
""", "'reg' property in <Node /sub in 'error.dts'> has length 4, which is not evenly divisible by 12 (= 4*(<#address-cells> (= 1) + <#size-cells> (= 2))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
""",
dts_file,
f"'reg' property in <Node /sub in '{dts_file}'> has length 4, which is not evenly divisible by 12 (= 4*(<#address-cells> (= 1) + <#size-cells> (= 2))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
verify_error("""
/dts-v1/;
@ -280,7 +275,9 @@ warning: unit address and first address in 'reg' (0x30000000200000001) don't mat
#interrupt-cells = <2>;
};
};
""", "'interrupts' property in <Node /sub in 'error.dts'> has length 4, which is not evenly divisible by 8 (= 4*<#interrupt-cells>). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
""",
dts_file,
f"'interrupts' property in <Node /sub in '{dts_file}'> has length 4, which is not evenly divisible by 8 (= 4*<#interrupt-cells>). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
verify_error("""
/dts-v1/;
@ -298,49 +295,22 @@ warning: unit address and first address in 'reg' (0x30000000200000001) don't mat
};
};
};
""", "'ranges' property in <Node /sub-1 in 'error.dts'> has length 8, which is not evenly divisible by 24 (= 4*(<#address-cells> (= 2) + <#address-cells for parent> (= 1) + <#size-cells> (= 3))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
""",
dts_file,
f"'ranges' property in <Node /sub-1 in '{dts_file}'> has length 8, which is not evenly divisible by 24 (= 4*(<#address-cells> (= 2) + <#address-cells for parent> (= 1) + <#size-cells> (= 3))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').")
print("all tests passed")
def verify_error(dts, dts_file, expected_err):
# Verifies that parsing a file 'dts_file' with the contents 'dts'
# (a string) raises an EDTError with the message 'expected_err'.
#
# The path 'dts_file' is written with the string 'dts' before the
# test is run.
def verify_error(dts, error):
# Verifies that parsing a file with the contents 'dts' (a string) raises an
# EDTError with the message 'error'
# Could use the 'tempfile' module instead of 'error.dts', but having a
# consistent filename makes error messages consistent and easier to test.
# error.dts is kept if the test fails, which is helpful.
with open("error.dts", "w", encoding="utf-8") as f:
with open(dts_file, "w", encoding="utf-8") as f:
f.write(dts)
f.flush() # Can't have unbuffered text IO, so flush() instead
try:
edtlib.EDT("error.dts", [])
except edtlib.EDTError as e:
if str(e) != error:
fail(f"expected the EDTError '{error}', got the EDTError '{e}'")
except Exception as e:
fail(f"expected the EDTError '{error}', got the {type(e).__name__} '{e}'")
else:
fail(f"expected the error '{error}', got no error")
os.remove("error.dts")
with pytest.raises(edtlib.EDTError) as e:
edtlib.EDT(dts_file, [])
def fail(msg):
sys.exit("test failed: " + msg)
def verify_eq(actual, expected):
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))
def verify_streq(actual, expected):
verify_eq(str(actual), expected)
if __name__ == "__main__":
run()
assert str(e.value) == expected_err