From 5ab38f1157c8fd581e9991bb745c5b035469d14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Wed, 17 Mar 2021 22:14:22 -0700 Subject: [PATCH] tests: initial tests import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From zephyr commit f5409dec01c84aa8eda1830c64309abe3d5868d0. Signed-off-by: Martí Bolívar --- tests/test-bindings-2/multidir.yaml | 5 + tests/test-bindings/bar-bus.yaml | 7 + .../child-binding-with-compat.yaml | 20 + tests/test-bindings/child-binding.yaml | 19 + tests/test-bindings/child.yaml | 8 + tests/test-bindings/defaults.yaml | 36 + tests/test-bindings/deprecated.yaml | 15 + tests/test-bindings/device-on-any-bus.yaml | 5 + tests/test-bindings/device-on-bar-bus.yaml | 7 + tests/test-bindings/device-on-foo-bus.yaml | 7 + tests/test-bindings/enums.yaml | 36 + tests/test-bindings/false-positive.yaml | 4 + tests/test-bindings/foo-bus.yaml | 7 + tests/test-bindings/foo-optional.yaml | 4 + tests/test-bindings/foo-required.yaml | 4 + tests/test-bindings/gpio-dst.yaml | 8 + tests/test-bindings/gpio-src.yaml | 9 + tests/test-bindings/grandchild-1.yaml | 10 + tests/test-bindings/grandchild-2.yaml | 6 + tests/test-bindings/grandchild-3.yaml | 6 + tests/test-bindings/interrupt-1-cell.yaml | 8 + tests/test-bindings/interrupt-2-cell.yaml | 9 + tests/test-bindings/interrupt-3-cell.yaml | 10 + tests/test-bindings/multidir.yaml | 5 + tests/test-bindings/order-1.yaml | 7 + tests/test-bindings/order-2.yaml | 7 + tests/test-bindings/parent.yaml | 13 + .../phandle-array-controller-0.yaml | 7 + .../phandle-array-controller-1.yaml | 11 + .../phandle-array-controller-2.yaml | 9 + tests/test-bindings/props.yaml | 50 + tests/test-multidir.dts | 19 + tests/test.dts | 430 ++++ tests/testdtlib.py | 2246 +++++++++++++++++ tests/testedtlib.py | 400 +++ 35 files changed, 3454 insertions(+) create mode 100644 tests/test-bindings-2/multidir.yaml create mode 100644 tests/test-bindings/bar-bus.yaml create mode 100644 tests/test-bindings/child-binding-with-compat.yaml create mode 100644 tests/test-bindings/child-binding.yaml create mode 100644 tests/test-bindings/child.yaml create mode 100644 tests/test-bindings/defaults.yaml create mode 100644 tests/test-bindings/deprecated.yaml create mode 100644 tests/test-bindings/device-on-any-bus.yaml create mode 100644 tests/test-bindings/device-on-bar-bus.yaml create mode 100644 tests/test-bindings/device-on-foo-bus.yaml create mode 100644 tests/test-bindings/enums.yaml create mode 100644 tests/test-bindings/false-positive.yaml create mode 100644 tests/test-bindings/foo-bus.yaml create mode 100644 tests/test-bindings/foo-optional.yaml create mode 100644 tests/test-bindings/foo-required.yaml create mode 100644 tests/test-bindings/gpio-dst.yaml create mode 100644 tests/test-bindings/gpio-src.yaml create mode 100644 tests/test-bindings/grandchild-1.yaml create mode 100644 tests/test-bindings/grandchild-2.yaml create mode 100644 tests/test-bindings/grandchild-3.yaml create mode 100644 tests/test-bindings/interrupt-1-cell.yaml create mode 100644 tests/test-bindings/interrupt-2-cell.yaml create mode 100644 tests/test-bindings/interrupt-3-cell.yaml create mode 100644 tests/test-bindings/multidir.yaml create mode 100644 tests/test-bindings/order-1.yaml create mode 100644 tests/test-bindings/order-2.yaml create mode 100644 tests/test-bindings/parent.yaml create mode 100644 tests/test-bindings/phandle-array-controller-0.yaml create mode 100644 tests/test-bindings/phandle-array-controller-1.yaml create mode 100644 tests/test-bindings/phandle-array-controller-2.yaml create mode 100644 tests/test-bindings/props.yaml create mode 100644 tests/test-multidir.dts create mode 100644 tests/test.dts create mode 100644 tests/testdtlib.py create mode 100644 tests/testedtlib.py diff --git a/tests/test-bindings-2/multidir.yaml b/tests/test-bindings-2/multidir.yaml new file mode 100644 index 0000000..19721b2 --- /dev/null +++ b/tests/test-bindings-2/multidir.yaml @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Binding in test-bindings-2/ + +compatible: "in-dir-2" diff --git a/tests/test-bindings/bar-bus.yaml b/tests/test-bindings/bar-bus.yaml new file mode 100644 index 0000000..e429e30 --- /dev/null +++ b/tests/test-bindings/bar-bus.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Bar bus controller + +compatible: "bar-bus" + +bus: "bar" diff --git a/tests/test-bindings/child-binding-with-compat.yaml b/tests/test-bindings/child-binding-with-compat.yaml new file mode 100644 index 0000000..1744ad2 --- /dev/null +++ b/tests/test-bindings/child-binding-with-compat.yaml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: child-binding with separate compatible than the parent + +compatible: "top-binding-with-compat" + +child-binding: + compatible: child-compat + description: child node + properties: + child-prop: + type: int + required: true + + child-binding: + description: grandchild node + properties: + grandchild-prop: + type: int + required: true diff --git a/tests/test-bindings/child-binding.yaml b/tests/test-bindings/child-binding.yaml new file mode 100644 index 0000000..5319dea --- /dev/null +++ b/tests/test-bindings/child-binding.yaml @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: child-binding test + +compatible: "top-binding" + +child-binding: + description: child node + properties: + child-prop: + type: int + required: true + + child-binding: + description: grandchild node + properties: + grandchild-prop: + type: int + required: true diff --git a/tests/test-bindings/child.yaml b/tests/test-bindings/child.yaml new file mode 100644 index 0000000..091c659 --- /dev/null +++ b/tests/test-bindings/child.yaml @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause + +include: [grandchild-1.yaml, grandchild-2.yaml, grandchild-3.yaml] + +properties: + bar: + required: true + type: int diff --git a/tests/test-bindings/defaults.yaml b/tests/test-bindings/defaults.yaml new file mode 100644 index 0000000..706067c --- /dev/null +++ b/tests/test-bindings/defaults.yaml @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Property default value test + +compatible: "defaults" + +properties: + int: + type: int + required: false + default: 123 + + array: + type: array + required: false + default: [1, 2, 3] + + uint8-array: + type: uint8-array + required: false + default: [0x89, 0xAB, 0xCD] + + string: + type: string + required: false + default: "hello" + + string-array: + type: string-array + required: false + default: ["hello", "there"] + + default-not-used: + type: int + required: false + default: 123 diff --git a/tests/test-bindings/deprecated.yaml b/tests/test-bindings/deprecated.yaml new file mode 100644 index 0000000..d8c72e5 --- /dev/null +++ b/tests/test-bindings/deprecated.yaml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Property deprecated value test + +compatible: "test-deprecated" + +properties: + oldprop: + type: int + deprecated: true + required: false + + curprop: + type: int + required: false diff --git a/tests/test-bindings/device-on-any-bus.yaml b/tests/test-bindings/device-on-any-bus.yaml new file mode 100644 index 0000000..06c29e4 --- /dev/null +++ b/tests/test-bindings/device-on-any-bus.yaml @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Device on any bus + +compatible: "on-any-bus" diff --git a/tests/test-bindings/device-on-bar-bus.yaml b/tests/test-bindings/device-on-bar-bus.yaml new file mode 100644 index 0000000..d2c72bb --- /dev/null +++ b/tests/test-bindings/device-on-bar-bus.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Device on bar bus + +compatible: "on-bus" + +on-bus: "bar" diff --git a/tests/test-bindings/device-on-foo-bus.yaml b/tests/test-bindings/device-on-foo-bus.yaml new file mode 100644 index 0000000..3ed4a75 --- /dev/null +++ b/tests/test-bindings/device-on-foo-bus.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Device on foo bus + +compatible: "on-bus" + +on-bus: "foo" diff --git a/tests/test-bindings/enums.yaml b/tests/test-bindings/enums.yaml new file mode 100644 index 0000000..f36b89b --- /dev/null +++ b/tests/test-bindings/enums.yaml @@ -0,0 +1,36 @@ +# Copyright (c) 2020 Nordic Semiconductor ASA +# SPDX-License-Identifier: BSD-3-Clause + +description: Property enum test + +compatible: "enums" + +properties: + int-enum: + type: int + enum: + - 1 + - 2 + - 3 + + string-enum: # not tokenizable + type: string + enum: + - foo bar + - foo_bar + + tokenizable-lower-enum: # tokenizable in lowercase only + type: string + enum: + - bar + - BAR + + tokenizable-enum: # tokenizable in lower and uppercase + type: string + enum: + - bar + - whitespace is ok + - 123 is ok + + no-enum: + type: string diff --git a/tests/test-bindings/false-positive.yaml b/tests/test-bindings/false-positive.yaml new file mode 100644 index 0000000..30de8fb --- /dev/null +++ b/tests/test-bindings/false-positive.yaml @@ -0,0 +1,4 @@ +# A file that mentions a 'compatible' string without actually implementing it. +# Used to check for issues with how we optimize binding loading. + +# props diff --git a/tests/test-bindings/foo-bus.yaml b/tests/test-bindings/foo-bus.yaml new file mode 100644 index 0000000..a3a98d3 --- /dev/null +++ b/tests/test-bindings/foo-bus.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Foo bus controller + +compatible: "foo-bus" + +bus: "foo" diff --git a/tests/test-bindings/foo-optional.yaml b/tests/test-bindings/foo-optional.yaml new file mode 100644 index 0000000..5383ade --- /dev/null +++ b/tests/test-bindings/foo-optional.yaml @@ -0,0 +1,4 @@ +properties: + foo: + type: int + required: false diff --git a/tests/test-bindings/foo-required.yaml b/tests/test-bindings/foo-required.yaml new file mode 100644 index 0000000..1544e1c --- /dev/null +++ b/tests/test-bindings/foo-required.yaml @@ -0,0 +1,4 @@ +properties: + foo: + type: int + required: true diff --git a/tests/test-bindings/gpio-dst.yaml b/tests/test-bindings/gpio-dst.yaml new file mode 100644 index 0000000..19db316 --- /dev/null +++ b/tests/test-bindings/gpio-dst.yaml @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: GPIO destination for mapping test + +compatible: "gpio-dst" + +gpio-cells: + - val diff --git a/tests/test-bindings/gpio-src.yaml b/tests/test-bindings/gpio-src.yaml new file mode 100644 index 0000000..b7e5c33 --- /dev/null +++ b/tests/test-bindings/gpio-src.yaml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: GPIO source for mapping test + +compatible: "gpio-src" + +properties: + foo-gpios: + type: phandle-array diff --git a/tests/test-bindings/grandchild-1.yaml b/tests/test-bindings/grandchild-1.yaml new file mode 100644 index 0000000..249158f --- /dev/null +++ b/tests/test-bindings/grandchild-1.yaml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause + +properties: + foo: + required: false + type: int + + baz: + required: true + type: int diff --git a/tests/test-bindings/grandchild-2.yaml b/tests/test-bindings/grandchild-2.yaml new file mode 100644 index 0000000..d7d0b5c --- /dev/null +++ b/tests/test-bindings/grandchild-2.yaml @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause + +properties: + baz: + required: true + type: int diff --git a/tests/test-bindings/grandchild-3.yaml b/tests/test-bindings/grandchild-3.yaml new file mode 100644 index 0000000..c1ba6b8 --- /dev/null +++ b/tests/test-bindings/grandchild-3.yaml @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause + +properties: + qaz: + required: true + type: int diff --git a/tests/test-bindings/interrupt-1-cell.yaml b/tests/test-bindings/interrupt-1-cell.yaml new file mode 100644 index 0000000..ab2db66 --- /dev/null +++ b/tests/test-bindings/interrupt-1-cell.yaml @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Interrupt controller with one cell + +compatible: "interrupt-one-cell" + +interrupt-cells: + - one diff --git a/tests/test-bindings/interrupt-2-cell.yaml b/tests/test-bindings/interrupt-2-cell.yaml new file mode 100644 index 0000000..2c3144d --- /dev/null +++ b/tests/test-bindings/interrupt-2-cell.yaml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Interrupt controller with two cells + +compatible: "interrupt-two-cell" + +interrupt-cells: + - one + - two diff --git a/tests/test-bindings/interrupt-3-cell.yaml b/tests/test-bindings/interrupt-3-cell.yaml new file mode 100644 index 0000000..846dee0 --- /dev/null +++ b/tests/test-bindings/interrupt-3-cell.yaml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Interrupt controller with three cells + +compatible: "interrupt-three-cell" + +interrupt-cells: + - one + - two + - three diff --git a/tests/test-bindings/multidir.yaml b/tests/test-bindings/multidir.yaml new file mode 100644 index 0000000..8504475 --- /dev/null +++ b/tests/test-bindings/multidir.yaml @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Binding in test-bindings/ + +compatible: "in-dir-1" diff --git a/tests/test-bindings/order-1.yaml b/tests/test-bindings/order-1.yaml new file mode 100644 index 0000000..e6024a8 --- /dev/null +++ b/tests/test-bindings/order-1.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Include ordering test + +compatible: "order-1" + +include: ["foo-required.yaml", "foo-optional.yaml"] diff --git a/tests/test-bindings/order-2.yaml b/tests/test-bindings/order-2.yaml new file mode 100644 index 0000000..cad1f9b --- /dev/null +++ b/tests/test-bindings/order-2.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Include ordering test + +compatible: "order-2" + +include: ["foo-optional.yaml", "foo-required.yaml"] diff --git a/tests/test-bindings/parent.yaml b/tests/test-bindings/parent.yaml new file mode 100644 index 0000000..8a0e0f4 --- /dev/null +++ b/tests/test-bindings/parent.yaml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Parent binding + +compatible: "binding-include-test" + +include: child.yaml + +properties: + foo: + # Changed from not being required in grandchild-1.yaml + required: true + # Type set in grandchild diff --git a/tests/test-bindings/phandle-array-controller-0.yaml b/tests/test-bindings/phandle-array-controller-0.yaml new file mode 100644 index 0000000..3b2430c --- /dev/null +++ b/tests/test-bindings/phandle-array-controller-0.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Controller with zero data values + +compatible: "phandle-array-controller-0" + +phandle-array-foo-cells: [] diff --git a/tests/test-bindings/phandle-array-controller-1.yaml b/tests/test-bindings/phandle-array-controller-1.yaml new file mode 100644 index 0000000..9d5c6c5 --- /dev/null +++ b/tests/test-bindings/phandle-array-controller-1.yaml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Controller with one data value + +compatible: "phandle-array-controller-1" + +phandle-array-foo-cells: + - one + +gpio-cells: + - gpio-one diff --git a/tests/test-bindings/phandle-array-controller-2.yaml b/tests/test-bindings/phandle-array-controller-2.yaml new file mode 100644 index 0000000..e7a3cab --- /dev/null +++ b/tests/test-bindings/phandle-array-controller-2.yaml @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Controller with two data values + +compatible: "phandle-array-controller-2" + +phandle-array-foo-cells: + - one + - two diff --git a/tests/test-bindings/props.yaml b/tests/test-bindings/props.yaml new file mode 100644 index 0000000..40b4cb9 --- /dev/null +++ b/tests/test-bindings/props.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: BSD-3-Clause + +description: Device.props test + +compatible: "props" + +properties: + nonexistent-boolean: + type: boolean + + existent-boolean: + type: boolean + + int: + type: int + const: 1 + + array: + type: array + + uint8-array: + type: uint8-array + + string: + type: string + const: "foo" + + string-array: + type: string-array + + phandle-ref: + type: phandle + + phandle-refs: + type: phandles + + phandle-array-foos: + type: phandle-array + + phandle-array-foo-names: + type: string-array + + # There's some slight special-casing for GPIOs in that 'foo-gpios = ...' + # gets resolved to #gpio-cells rather than #foo-gpio-cells, so test that + # too + foo-gpios: + type: phandle-array + + path: + type: path diff --git a/tests/test-multidir.dts b/tests/test-multidir.dts new file mode 100644 index 0000000..4bd0463 --- /dev/null +++ b/tests/test-multidir.dts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019, Nordic Semiconductor + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Used by testedtlib.py. Dedicated file for testing having multiple binding +// directories. + +/dts-v1/; + +/ { + in-dir-1 { + compatible = "in-dir-1"; + }; + in-dir-2 { + compatible = "in-dir-2"; + }; +}; diff --git a/tests/test.dts b/tests/test.dts new file mode 100644 index 0000000..0e077fd --- /dev/null +++ b/tests/test.dts @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2019, Nordic Semiconductor + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Used by testedtlib.py + +/dts-v1/; + +/ { + // + // Interrupts + // + + interrupt-parent-test { + controller { + compatible = "interrupt-three-cell"; + #interrupt-cells = <3>; + interrupt-controller; + }; + node { + interrupts = <1 2 3 4 5 6>; + interrupt-names = "foo", "bar"; + interrupt-parent = <&{/interrupt-parent-test/controller}>; + }; + }; + interrupts-extended-test { + controller-0 { + compatible = "interrupt-one-cell"; + #interrupt-cells = <1>; + interrupt-controller; + }; + controller-1 { + compatible = "interrupt-two-cell"; + #interrupt-cells = <2>; + interrupt-controller; + }; + controller-2 { + compatible = "interrupt-three-cell"; + #interrupt-cells = <3>; + interrupt-controller; + }; + node { + interrupts-extended = < + &{/interrupts-extended-test/controller-0} 1 + &{/interrupts-extended-test/controller-1} 2 3 + &{/interrupts-extended-test/controller-2} 4 5 6>; + }; + }; + interrupt-map-test { + #address-cells = <2>; + #size-cells = <0>; + + controller-0 { + compatible = "interrupt-one-cell"; + #address-cells = <1>; + #interrupt-cells = <1>; + interrupt-controller; + }; + controller-1 { + compatible = "interrupt-two-cell"; + #address-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + }; + controller-2 { + compatible = "interrupt-three-cell"; + #address-cells = <3>; + #interrupt-cells = <3>; + interrupt-controller; + }; + nexus { + #interrupt-cells = <2>; + interrupt-map = < + 0 0 0 0 &{/interrupt-map-test/controller-0} 0 0 + 0 0 0 1 &{/interrupt-map-test/controller-1} 0 0 0 1 + 0 0 0 2 &{/interrupt-map-test/controller-2} 0 0 0 0 0 2 + 0 1 0 0 &{/interrupt-map-test/controller-0} 0 3 + 0 1 0 1 &{/interrupt-map-test/controller-1} 0 0 0 4 + 0 1 0 2 &{/interrupt-map-test/controller-2} 0 0 0 0 0 5>; + }; + node@0 { + reg = <0 0>; + interrupts = <0 0 0 1 0 2>; + interrupt-parent = <&{/interrupt-map-test/nexus}>; + }; + node@1 { + reg = <0 1>; + interrupts-extended = < + &{/interrupt-map-test/nexus} 0 0 + &{/interrupt-map-test/nexus} 0 1 + &{/interrupt-map-test/nexus} 0 2>; + }; + }; + interrupt-map-bitops-test { + #address-cells = <2>; + #size-cells = <0>; + + controller { + compatible = "interrupt-two-cell"; + #address-cells = <0>; + #interrupt-cells = <2>; + interrupt-controller; + }; + nexus { + #interrupt-cells = <2>; + interrupt-map = < + 6 6 6 6 &{/interrupt-map-bitops-test/controller} 2 1 + >; + interrupt-map-mask = <0xE 0x7 0xE 0x7>; + // Not specified in the DT spec., but shows up due to + // common code with GPIO. Might as well test it here. + interrupt-map-pass-thru = <1 2 3 3>; + }; + // Child unit specifier: 00000007 0000000E 00000007 0000000E + // Mask: 0000000E 00000007 0000000E 00000007 + // Pass-thru: 00000001 00000002 00000003 00000003 + node@70000000E { + reg = <0x7 0xE>; + interrupt-parent = <&{/interrupt-map-bitops-test/nexus}>; + interrupts = <0x7 0xE>; + }; + }; + + // + // 'reg' + // + + reg-zero-address-cells { + #address-cells = <0>; + #size-cells = <1>; + + node { + reg = <1 2>; + }; + }; + reg-zero-size-cells { + #address-cells = <1>; + #size-cells = <0>; + + node { + reg = <1 2>; + }; + }; + // Use implied #size-cells = <1> + reg-ranges { + #address-cells = <2>; + + parent { + #address-cells = <1>; + ranges = <1 0xA 0xB 1 /* 1 -> 0xA 0xB */ + 2 0xC 0xD 2 /* 2..3 -> 0xC 0xD */ + 4 0xE 0xF 1 /* 4 -> 0xE 0xF */ + >; + + node { + reg = <5 1 /* Matches no range */ + 4 1 /* Matches third range */ + 3 1 /* Matches second range */ + 2 1 /* Matches second range */ + 1 1 /* Matches first range */ + 0 1 /* Matches no range */ + >; + }; + }; + }; + // Build up <3 2 1> address with nested 'ranges' + reg-nested-ranges { + #address-cells = <3>; + + grandparent { + #address-cells = <2>; + #size-cells = <2>; + ranges = <0 0 3 0 0 2 2>; + + parent { + #address-cells = <1>; + ranges = <0 2 0 2>; + + node { + reg = <1 1>; + }; + }; + }; + }; + + // + // 'pinctrl-' + // + + pinctrl { + dev { + pinctrl-0 = <>; + pinctrl-1 = <&{/pinctrl/pincontroller/state-1}>; + pinctrl-2 = <&{/pinctrl/pincontroller/state-1} + &{/pinctrl/pincontroller/state-2}>; + pinctrl-names = "zero", "one", "two"; + }; + pincontroller { + state-1 { + }; + state-2 { + }; + }; + }; + + // + // For testing Node.parent and Node.children + // + + parent { + child-1 { + }; + child-2 { + grandchild { + }; + }; + }; + + // + // For testing 'include:' + // + + binding-include { + compatible = "binding-include-test"; + foo = <0>; + bar = <1>; + baz = <2>; + qaz = <3>; + }; + + // + // For testing Node.props (derived from 'properties:' in the binding) + // + + props { + compatible = "props"; + existent-boolean; + int = <1>; + array = <1 2 3>; + uint8-array = [ 12 34 ]; + string = "foo"; + string-array = "foo", "bar", "baz"; + phandle-ref = < &{/ctrl-1} >; + phandle-refs = < &{/ctrl-1} &{/ctrl-2} >; + phandle-array-foos = < &{/ctrl-1} 1 &{/ctrl-2} 2 3 >; + foo-gpios = < &{/ctrl-1} 1 >; + path = &{/ctrl-1}; + }; + + ctrl-1 { + compatible = "phandle-array-controller-1"; + #phandle-array-foo-cells = <1>; + #gpio-cells = <1>; + }; + + ctrl-2 { + compatible = "phandle-array-controller-2"; + #phandle-array-foo-cells = <2>; + }; + + props-2 { + compatible = "props"; + phandle-array-foos = < &{/ctrl-0-1} 0 &{/ctrl-0-2} >; + phandle-array-foo-names = "a", "missing", "b"; + }; + + ctrl-0-1 { + compatible = "phandle-array-controller-0"; + #phandle-array-foo-cells = <0>; + }; + + ctrl-0-2 { + compatible = "phandle-array-controller-0"; + #phandle-array-foo-cells = <0>; + }; + + // + // Test -map, via gpio-map + // + + gpio-map { + source { + compatible = "gpio-src"; + foo-gpios = <&{/gpio-map/connector} 3 4 + &{/gpio-map/connector} 1 2>; + }; + connector { + #gpio-cells = <2>; + // Use different data lengths for source and + // destination to make it a bit trickier + gpio-map = <1 2 &{/gpio-map/destination} 5 + 3 4 &{/gpio-map/destination} 6>; + }; + destination { + compatible = "gpio-dst"; + gpio-controller; + #gpio-cells = <1>; + }; + }; + + // + // For testing Node.props with 'default:' values in binding + // + + defaults { + compatible = "defaults"; + // Should override the 'default:' in the binding + default-not-used = <234>; + }; + + // + // For testing 'enum:' + // + + enums { + compatible = "enums"; + int-enum = <1>; + string-enum = "foo_bar"; + tokenizable-enum = "123 is ok"; + tokenizable-lower-enum = "bar"; + no-enum = "baz"; + }; + + // + // For testing 'bus:' and 'on-bus:' + // + + buses { + // The 'node' nodes below will map to different bindings since + // they appear on different buses + foo-bus { + compatible = "foo-bus"; + node1 { + compatible = "on-bus", "on-any-bus"; + nested { + compatible = "on-bus"; + }; + }; + node2 { + compatible = "on-any-bus", "on-bus"; + }; + }; + bar-bus { + compatible = "bar-bus"; + node { + compatible = "on-bus"; + }; + }; + no-bus-node { + compatible = "on-any-bus"; + }; + }; + + // + // Node with 'child-binding:' in binding (along with a recursive + // 'child-binding:') + // + + child-binding { + compatible = "top-binding"; + child-1 { + child-prop = <1>; + grandchild { + grandchild-prop = <2>; + }; + }; + child-2 { + child-prop = <3>; + }; + }; + + // + // zephyr,user binding inference + // + + zephyr,user { + boolean; + bytes = [81 82 83]; + number = <23>; + numbers = <1>, <2>, <3>; + string = "text"; + strings = "a", "b", "c"; + handle = <&{/ctrl-1}>; + phandles = <&{/ctrl-1}>, <&{/ctrl-2}>; + phandle-array-foos = <&{/ctrl-2} 1 2>; + }; + + // + // For testing that neither 'include: [foo.yaml, bar.yaml]' nor + // 'include: [bar.yaml, foo.yaml]' causes errors when one of the files + // has 'required: true' and the other 'required: false' + // + + include-order { + node-1 { + compatible = "order-1"; + foo = <1>; + }; + node-2 { + compatible = "order-2"; + foo = <2>; + }; + }; + + // + // For testing deprecated property + // + test-deprecated { + compatible = "test-deprecated"; + oldprop = <4>; /* deprecated property */ + curprop = <5>; + }; + + + // + // For testing deprecated features + // + + deprecated { + compatible = "deprecated"; + required = <1>; + required-2 = <2>; + #foo-cells = <2>; + sub-node { + foos = <&{/deprecated} 1 2>; + }; + }; +}; diff --git a/tests/testdtlib.py b/tests/testdtlib.py new file mode 100644 index 0000000..61ba4df --- /dev/null +++ b/tests/testdtlib.py @@ -0,0 +1,2246 @@ +# Copyright (c) 2019, Nordic Semiconductor +# SPDX-License-Identifier: BSD-3-Clause + +import contextlib +import os +import re +import tempfile + +import pytest + +import dtlib + +# Test suite for dtlib.py. +# +# Run it using pytest (https://docs.pytest.org/en/stable/usage.html): +# +# $ pytest testdtlib.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=()): + '''Parse a DTS string 'dts', using the given include path.''' + + fd, path = tempfile.mkstemp(prefix='pytest-', suffix='.dts') + try: + os.write(fd, dts.encode('utf-8')) + return dtlib.DT(path, include_path) + 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 pytest.raises(dtlib.DTError) as e: + parse(dts[1:]) + actual_msg = str(e.value) + assert actual_msg == expected_msg, f'wrong error from {dts}' + +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 pytest.raises(dtlib.DTError) as e: + parse(dts[1:]) + actual_msg = str(e.value) + assert actual_msg.endswith(expected_msg), f'wrong error from {dts}' + +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 pytest.raises(dtlib.DTError) as e: + parse(dts[1:]) + actual_msg = str(e.value) + assert re.fullmatch(expected_re, actual_msg), \ + f'wrong error from {dts}' \ + f'actual message:\n{actual_msg!r}\n' \ + f'does not match:\n{expected_re!r}' + +@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) + +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/; + +/ { + 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_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 pytest.raises(dtlib.DTError) as e: + dtlib.DT("tmp.dts") + + assert str(e.value) == \ + "tmp2.dts:3 (column 3): parse error: expected '/dts-v1/;' at start of file" + +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') + with pytest.raises(dtlib.DTError) as e: + dtlib.DT("tmp.dts") + + expected_err = """\ +tmp3.dts:1 (column 1): parse error: recursive /include/: +tmp.dts:1 -> +tmp2.dts:1 -> +tmp3.dts:1 -> +tmp.dts""" + assert str(e.value) == expected_err + + with open("tmp.dts", "w") as f: + f.write('/include/ "tmp.dts"\n') + with pytest.raises(dtlib.DTError) as e: + dtlib.DT("tmp.dts") + expected_err = """\ +tmp.dts:1 (column 1): parse error: recursive /include/: +tmp.dts:1 -> +tmp.dts""" + assert str(e.value) == expected_err + +def test_omit_if_no_ref(): + '''The /omit-if-no-ref/ marker is a bit of undocumented + dtc magic that removes a node from the tree if it isn't + referred to elsewhere. + + https://elinux.org/Device_Tree_Source_Undocumented + ''' + + verify_parse(""" +/dts-v1/; + +/ { + x = < &{/referenced} >, &referenced2; + + /omit-if-no-ref/ referenced { + }; + + referenced2: referenced2 { + }; + + /omit-if-no-ref/ unreferenced { + }; + + l1: /omit-if-no-ref/ unreferenced2 { + }; + + /omit-if-no-ref/ l2: unreferenced3 { + }; + + unreferenced4: unreferenced4 { + }; + + unreferenced5 { + }; +}; + +/omit-if-no-ref/ &referenced2; +/omit-if-no-ref/ &unreferenced4; +/omit-if-no-ref/ &{/unreferenced5}; +""", +""" +/dts-v1/; + +/ { + x = < &{/referenced} >, &referenced2; + referenced { + phandle = < 0x1 >; + }; + referenced2: referenced2 { + }; +}; +""") + + verify_error_endswith(""" +/dts-v1/; + +/ { + /omit-if-no-ref/ x = ""; +}; +""", +":4 (column 21): parse error: /omit-if-no-ref/ can only be used on nodes") + + verify_error_endswith(""" +/dts-v1/; + +/ { + /omit-if-no-ref/ x; +}; +""", +":4 (column 20): parse error: /omit-if-no-ref/ can only be used on nodes") + + verify_error_endswith(""" +/dts-v1/; + +/ { + /omit-if-no-ref/ { + }; +}; +""", +":4 (column 19): parse error: expected node or property name") + + verify_error_endswith(""" +/dts-v1/; + +/ { + /omit-if-no-ref/ = < 0 >; +}; +""", +":4 (column 19): parse error: expected node or property name") + + verify_error_endswith(""" +/dts-v1/; + +/ { +}; + +/omit-if-no-ref/ &missing; +""", +":6 (column 18): parse error: undefined node label 'missing'") + + verify_error_endswith(""" +/dts-v1/; + +/omit-if-no-ref/ { +""", +":3 (column 18): parse error: expected label (&foo) or path (&{/foo/bar}) reference") + +def test_expr(): + '''Property values may contain expressions.''' + + verify_parse(""" +/dts-v1/; + +/ { + ter1 = < (0 ? 1 : 0 ? 2 : 3) >; + ter2 = < (0 ? 1 : 1 ? 2 : 3) >; + ter3 = < (1 ? 1 : 0 ? 2 : 3) >; + ter4 = < (1 ? 1 : 1 ? 2 : 3) >; + or1 = < (0 || 0) >; + or2 = < (0 || 1) >; + or3 = < (1 || 0) >; + or4 = < (1 || 1) >; + and1 = < (0 && 0) >; + and2 = < (0 && 1) >; + and3 = < (1 && 0) >; + and4 = < (1 && 1) >; + bitor = < (1 | 2) >; + bitxor = < (7 ^ 2) >; + bitand = < (3 & 6) >; + eq1 = < (1 == 0) >; + eq2 = < (1 == 1) >; + neq1 = < (1 != 0) >; + neq2 = < (1 != 1) >; + lt1 = < (1 < 2) >; + lt2 = < (2 < 2) >; + lt3 = < (3 < 2) >; + lteq1 = < (1 <= 2) >; + lteq2 = < (2 <= 2) >; + lteq3 = < (3 <= 2) >; + gt1 = < (1 > 2) >; + gt2 = < (2 > 2) >; + gt3 = < (3 > 2) >; + gteq1 = < (1 >= 2) >; + gteq2 = < (2 >= 2) >; + gteq3 = < (3 >= 2) >; + lshift = < (2 << 3) >; + rshift = < (16 >> 3) >; + add = < (3 + 4) >; + sub = < (7 - 4) >; + mul = < (3 * 4) >; + div = < (11 / 3) >; + mod = < (11 % 3) >; + unary_minus = < (-3) >; + bitnot = < (~1) >; + not0 = < (!-1) >; + not1 = < (!0) >; + not2 = < (!1) >; + not3 = < (!2) >; + nest = < (((--3) + (-2)) * (--(-2))) >; + char_lits = < ('a' + 'b') >; +}; +""", +""" +/dts-v1/; + +/ { + ter1 = < 0x3 >; + ter2 = < 0x2 >; + ter3 = < 0x1 >; + ter4 = < 0x1 >; + or1 = < 0x0 >; + or2 = < 0x1 >; + or3 = < 0x1 >; + or4 = < 0x1 >; + and1 = < 0x0 >; + and2 = < 0x0 >; + and3 = < 0x0 >; + and4 = < 0x1 >; + bitor = < 0x3 >; + bitxor = < 0x5 >; + bitand = < 0x2 >; + eq1 = < 0x0 >; + eq2 = < 0x1 >; + neq1 = < 0x1 >; + neq2 = < 0x0 >; + lt1 = < 0x1 >; + lt2 = < 0x0 >; + lt3 = < 0x0 >; + lteq1 = < 0x1 >; + lteq2 = < 0x1 >; + lteq3 = < 0x0 >; + gt1 = < 0x0 >; + gt2 = < 0x0 >; + gt3 = < 0x1 >; + gteq1 = < 0x0 >; + gteq2 = < 0x1 >; + gteq3 = < 0x1 >; + lshift = < 0x10 >; + rshift = < 0x2 >; + add = < 0x7 >; + sub = < 0x3 >; + mul = < 0xc >; + div = < 0x3 >; + mod = < 0x2 >; + unary_minus = < 0xfffffffd >; + bitnot = < 0xfffffffe >; + not0 = < 0x0 >; + not1 = < 0x1 >; + not2 = < 0x0 >; + not3 = < 0x0 >; + nest = < 0xfffffffe >; + char_lits = < 0xc3 >; +}; +""") + + verify_error_endswith(""" +/dts-v1/; + +/ { + a = < (1/(-1 + 1)) >; +}; +""", +":4 (column 18): parse error: division by zero") + + verify_error_endswith(""" +/dts-v1/; + +/ { + a = < (1%0) >; +}; +""", +":4 (column 11): parse error: division by zero") + +def test_comment_removal(): + '''Comments should be removed when round-tripped to a str.''' + + verify_parse(""" +/**//dts-v1//**/;// +// +// foo +/ /**/{// foo +x/**/=/* +foo +*//****/;/**/}/*/**/; +""", +""" +/dts-v1/; + +/ { + x = < 0x1 >; +}; +""") + +def verify_path_is(path, node_name, dt): + '''Verify 'node.name' matches 'node_name' in 'dt'.''' + + try: + node = dt.get_node(path) + assert node.name == node_name, f'unexpected path {path}' + except dtlib.DTError: + assert False, f'no node found for path {path}' + +def verify_path_error(path, msg, dt): + '''Verify that an attempt to get node 'path' from 'dt' raises + a DTError whose str is 'msg'.''' + + with pytest.raises(dtlib.DTError) as e: + dt.get_node(path) + assert str(e.value) == msg, f"'{path}' gives the wrong error" + +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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_num() + actual_msg = str(e.value) + assert re.fullmatch(expected_re, actual_msg), \ + f"'{prop}' to_num gives the wrong error: " \ + f"actual message:\n{actual_msg!r}\n" \ + f"does not match:\n{expected_re!r}" + + 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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_nums() + assert re.fullmatch(expected_re, str(e.value)), \ + f"'{prop}' to_nums gives the wrong error" + + 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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_bytes() + assert re.fullmatch(expected_re, str(e.value)), \ + f"'{prop}' gives the wrong error" + + 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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_string() + assert re.fullmatch(expected_re, str(e.value)), \ + f"'{prop}' gives the wrong error" + + 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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_strings() + assert re.fullmatch(expected_re, str(e.value)), \ + f"'{prop}' gives the wrong error" + + 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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_node() + assert re.fullmatch(expected_re, str(e.value)), \ + f"'{prop} gives the wrong error" + + 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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_nodes() + assert re.fullmatch(expected_re, str(e.value)), \ + f"'{prop} gives wrong error" + + 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 pytest.raises(dtlib.DTError) as e: + dt.root.props[prop].to_path() + assert re.fullmatch(expected_re, str(e.value)), \ + f"'{prop} gives the wrong error" + + 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): + with pytest.raises(dtlib.DTError) as e: + fn(data, length) + assert str(e.value) == msg, \ + (f"{fn.__name__}() called with data='{data}', length='{length}' " + "gives the wrong error") + + verify_raw_to_num(dtlib.to_num, "u", None, False, 1) + verify_raw_to_num(dtlib.to_num, "u", 4, False, 1) + verify_raw_to_num(dtlib.to_num, "s", None, False, 0xFFFFFFFF) + verify_raw_to_num(dtlib.to_num, "s", None, True, -1) + verify_raw_to_num(dtlib.to_nums, "empty", 4, False, []) + verify_raw_to_num(dtlib.to_nums, "u16", 2, False, [1, 2]) + verify_raw_to_num(dtlib.to_nums, "two_s", 4, False, [0xFFFFFFFF, 0xFFFFFFFE]) + verify_raw_to_num(dtlib.to_nums, "two_s", 4, True, [-1, -2]) + + verify_raw_to_num_error(dtlib.to_num, 0, 0, "'0' has type 'int', expected 'bytes'") + verify_raw_to_num_error(dtlib.to_num, b"", 0, "'length' must be greater than zero, was 0") + verify_raw_to_num_error(dtlib.to_num, b"foo", 2, "b'foo' is 3 bytes long, expected 2") + verify_raw_to_num_error(dtlib.to_nums, 0, 0, "'0' has type 'int', expected 'bytes'") + verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'length' must be greater than zero, was 0") + verify_raw_to_num_error(dtlib.to_nums, b"foooo", 2, "b'foooo' is 5 bytes long, expected a length that's a a multiple of 2") + +def test_duplicate_labels(): + ''' + It is an error to duplicate labels in most conditions, but there + are some exceptions where it's OK. + ''' + + verify_error(""" +/dts-v1/; + +/ { + sub1 { + label: foo { + }; + }; + + sub2 { + label: bar { + }; + }; +}; +""", +"Label 'label' appears on /sub1/foo and on /sub2/bar") + + verify_error(""" +/dts-v1/; + +/ { + sub { + label: foo { + }; + }; +}; +/ { + sub { + label: bar { + }; + }; +}; +""", +"Label 'label' appears on /sub/bar and on /sub/foo") + + verify_error(""" +/dts-v1/; + +/ { + foo: a = < 0 >; + foo: node { + }; +}; +""", +"Label 'foo' appears on /node and on property 'a' of node /") + + verify_error(""" +/dts-v1/; + +/ { + foo: a = < 0 >; + node { + foo: b = < 0 >; + }; +}; +""", +"Label 'foo' appears on property 'a' of node / and on property 'b' of node /node") + + verify_error(""" +/dts-v1/; + +/ { + foo: a = foo: < 0 >; +}; +""", +"Label 'foo' appears in the value of property 'a' of node / and on property 'a' of node /") + + # Giving the same label twice for the same node is fine + verify_parse(""" +/dts-v1/; + +/ { + sub { + label: foo { + }; + }; +}; +/ { + + sub { + label: foo { + }; + }; +}; +""", +""" +/dts-v1/; + +/ { + sub { + label: foo { + }; + }; +}; +""") + + # Duplicate labels are fine if one of the nodes is deleted + verify_parse(""" +/dts-v1/; + +/ { + label: foo { + }; + label: bar { + }; +}; + +/delete-node/ &{/bar}; +""", +""" +/dts-v1/; + +/ { + label: foo { + }; +}; +""") + + # + # Test overriding/deleting a property with references + # + + verify_parse(""" +/dts-v1/; + +/ { + x = &foo, < &foo >; + y = &foo, < &foo >; + foo: foo { + }; +}; + +/ { + x = < 1 >; + /delete-property/ y; +}; +""", +""" +/dts-v1/; + +/ { + x = < 0x1 >; + foo: foo { + }; +}; +""") + + # + # Test self-referential node + # + + verify_parse(""" +/dts-v1/; + +/ { + label: foo { + x = &{/foo}, &label, < &label >; + }; +}; +""", +""" +/dts-v1/; + +/ { + label: foo { + x = &{/foo}, &label, < &label >; + phandle = < 0x1 >; + }; +}; +""") + + # + # Test /memreserve/ + # + + dt = verify_parse(""" +/dts-v1/; + +l1: l2: /memreserve/ (1 + 1) (2 * 2); +/memreserve/ 0x100 0x200; + +/ { +}; +""", +""" +/dts-v1/; + +l1: l2: /memreserve/ 0x0000000000000002 0x0000000000000004; +/memreserve/ 0x0000000000000100 0x0000000000000200; + +/ { +}; +""") + + expected = [(["l1", "l2"], 2, 4), ([], 0x100, 0x200)] + assert dt.memreserves == expected + + verify_error_endswith(""" +/dts-v1/; + +foo: / { +}; +""", +":3 (column 6): parse error: expected /memreserve/ after labels at beginning of file") + +def test_reprs(): + '''Test the __repr__() functions.''' + + dt = parse(""" +/dts-v1/; + +/ { + x = < 0 >; + sub { + y = < 1 >; + }; +}; +""", + include_path=("foo", "bar")) + + assert re.fullmatch(r"DT\(filename='.*', include_path=\('foo', 'bar'\)\)", + repr(dt)) + assert re.fullmatch("", + repr(dt.root.props["x"])) + assert re.fullmatch("", + repr(dt.root.nodes["sub"])) + +def test_names(): + '''Tests for node/property names.''' + + # The C tools disallow '@' in property names, but otherwise accept the same + # characters in node and property names. Emulate that instead of the DT spec + # (v0.2), which gives different characters for nodes and properties. + 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 ]; + + _: \aA0,._+*#?@- { + }; + + 0 { + }; +}; +""", +""" +/dts-v1/; + +/ { + aA0,._+*#?- = &_, &{/aA0,._+*#?@-}; + + = [ 00 ]; + * = [ 02 ]; + - = [ 01 ]; + ? = [ 03 ]; + 0 = [ 04 ]; + 0x123 = [ 05 ]; + _: aA0,._+*#?@- { + }; + 0 { + }; +}; +""") + + verify_error_endswith(r""" +/dts-v1/; + +/ { + foo@3; +}; +""", +":4 (column 7): parse error: '@' is only allowed in node names") + + verify_error_endswith(r""" +/dts-v1/; + +/ { + foo@3 = < 0 >; +}; +""", +":4 (column 8): parse error: '@' is only allowed in node names") + + verify_error_endswith(r""" +/dts-v1/; + +/ { + foo@2@3 { + }; +}; +""", +":4 (column 10): parse error: multiple '@' in node name") + +def test_dense_input(): + ''' + Test that a densely written DTS input round-trips to something + readable. + ''' + + verify_parse(""" +/dts-v1/;/{l1:l2:foo{l3:l4:bar{l5:x=l6:/bits/8l9:,[03],"a";};};}; +""", +""" +/dts-v1/; + +/ { + l1: l2: foo { + l3: l4: bar { + l5: x = l6: [ l7: 01 l8: 02 l9: ], [ 03 ], "a"; + }; + }; +}; +""") + +def test_misc(): + '''Test miscellaneous errors and non-errors.''' + + verify_error_endswith("", ":1 (column 1): parse error: expected '/dts-v1/;' at start of file") + + verify_error_endswith(""" +/dts-v1/; +""", +":2 (column 1): parse error: no root node defined") + + verify_error_endswith(""" +/dts-v1/; /plugin/; +""", +":1 (column 11): parse error: /plugin/ is not supported") + + verify_error_endswith(""" +/dts-v1/; + +/ { + foo: foo { + }; +}; + +// Only one label supported before label references at the top level +l1: l2: &foo { +}; +""", +":9 (column 5): parse error: expected label reference (&foo)") + + verify_error_endswith(""" +/dts-v1/; + +/ { + foo: {}; +}; +""", +":4 (column 14): parse error: expected node or property name") + + # Multiple /dts-v1/ at the start of a file is fine + verify_parse(""" +/dts-v1/; +/dts-v1/; + +/ { +}; +""", +""" +/dts-v1/; + +/ { +}; +""") diff --git a/tests/testedtlib.py b/tests/testedtlib.py new file mode 100644 index 0000000..6a180ba --- /dev/null +++ b/tests/testedtlib.py @@ -0,0 +1,400 @@ +# Copyright (c) 2019 Nordic Semiconductor ASA +# SPDX-License-Identifier: BSD-3-Clause + +import io +from logging import WARNING +import os +from pathlib import Path + +import pytest + +import edtlib + +# Test suite for edtlib.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 hpath(filename): + '''Convert 'filename' to the host path syntax.''' + return os.fspath(Path(filename)) + +def test_warnings(caplog): + '''Tests for situations that should cause warnings.''' + + edtlib.EDT("test.dts", ["test-bindings"]) + + enums_hpath = hpath('test-bindings/enums.yaml') + expected_warnings = [ + f"'oldprop' is marked as deprecated in 'properties:' in {hpath('test-bindings/deprecated.yaml')} for node /test-deprecated.", + "unit address and first address in 'reg' (0x1) don't match for /reg-zero-size-cells/node", + "unit address and first address in 'reg' (0x5) don't match for /reg-ranges/parent/node", + "unit address and first address in 'reg' (0x30000000200000001) don't match for /reg-nested-ranges/grandparent/parent/node", + f"compatible 'enums' in binding '{enums_hpath}' has non-tokenizable enum for property 'string-enum': 'foo bar', 'foo_bar'", + f"compatible 'enums' in binding '{enums_hpath}' has enum for property 'tokenizable-lower-enum' that is only tokenizable in lowercase: 'bar', 'BAR'", + ] + assert caplog.record_tuples == [('edtlib', WARNING, warning_message) + for warning_message in expected_warnings] + +def test_interrupts(): + '''Tests for the interrupts property.''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + filenames = {i: hpath(f'test-bindings/interrupt-{i}-cell.yaml') + for i in range(1, 4)} + + assert str(edt.get_node("/interrupt-parent-test/node").interrupts) == \ + f"[, data: OrderedDict([('one', 1), ('two', 2), ('three', 3)])>, , data: OrderedDict([('one', 4), ('two', 5), ('three', 6)])>]" + + assert str(edt.get_node("/interrupts-extended-test/node").interrupts) == \ + f"[, data: OrderedDict([('one', 1)])>, , data: OrderedDict([('one', 2), ('two', 3)])>, , data: OrderedDict([('one', 4), ('two', 5), ('three', 6)])>]" + + assert str(edt.get_node("/interrupt-map-test/node@0").interrupts) == \ + f"[, data: OrderedDict([('one', 0)])>, , data: OrderedDict([('one', 0), ('two', 1)])>, , data: OrderedDict([('one', 0), ('two', 0), ('three', 2)])>]" + + assert str(edt.get_node("/interrupt-map-test/node@1").interrupts) == \ + f"[, data: OrderedDict([('one', 3)])>, , data: OrderedDict([('one', 0), ('two', 4)])>, , data: OrderedDict([('one', 0), ('two', 0), ('three', 5)])>]" + + assert str(edt.get_node("/interrupt-map-bitops-test/node@70000000E").interrupts) == \ + f"[, data: OrderedDict([('one', 3), ('two', 2)])>]" + +def test_reg(): + '''Tests for the regs property''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + + assert str(edt.get_node("/reg-zero-address-cells/node").regs) == \ + "[, ]" + + assert str(edt.get_node("/reg-zero-size-cells/node").regs) == \ + "[, ]" + + assert str(edt.get_node("/reg-ranges/parent/node").regs) == \ + "[, , , , , ]" + + assert str(edt.get_node("/reg-nested-ranges/grandparent/parent/node").regs) == \ + "[]" + +def test_pinctrl(): + '''Test 'pinctrl-'.''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + + assert str(edt.get_node("/pinctrl/dev").pinctrls) == \ + "[, ]>, , ]>]" + +def test_hierarchy(): + '''Test Node.parent and Node.children''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + + assert edt.get_node("/").parent is None + + assert str(edt.get_node("/parent/child-1").parent) == \ + "" + + assert str(edt.get_node("/parent/child-2/grandchild").parent) == \ + "" + + assert str(edt.get_node("/parent").children) == \ + "OrderedDict([('child-1', ), ('child-2', )])" + + assert edt.get_node("/parent/child-1").children == {} + +def test_include(): + '''Test 'include:' and the legacy 'inherits: !include ...' in bindings''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + + assert str(edt.get_node("/binding-include").description) == \ + "Parent binding" + + assert str(edt.get_node("/binding-include").props) == \ + "OrderedDict([('foo', ), ('bar', ), ('baz', ), ('qaz', )])" + +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" + + # foo-bus does not itself appear on a bus + assert edt.get_node("/buses/foo-bus").on_bus is None + assert edt.get_node("/buses/foo-bus").bus_node is None + + # foo-bus/node1 is not a bus node... + assert edt.get_node("/buses/foo-bus/node1").bus is None + # ...but is on a bus + assert edt.get_node("/buses/foo-bus/node1").on_bus == "foo" + assert edt.get_node("/buses/foo-bus/node1").bus_node.path == \ + "/buses/foo-bus" + + # foo-bus/node2 is not a bus node... + assert edt.get_node("/buses/foo-bus/node2").bus is None + # ...but is on a bus + assert edt.get_node("/buses/foo-bus/node2").on_bus == "foo" + + # no-bus-node is not a bus node... + assert edt.get_node("/buses/no-bus-node").bus is None + # ... and is not on a bus + assert edt.get_node("/buses/no-bus-node").on_bus is None + + # Same compatible string, but different bindings from being on different + # buses + assert str(edt.get_node("/buses/foo-bus/node1").binding_path) == \ + hpath("test-bindings/device-on-foo-bus.yaml") + assert str(edt.get_node("/buses/foo-bus/node2").binding_path) == \ + hpath("test-bindings/device-on-any-bus.yaml") + assert str(edt.get_node("/buses/bar-bus/node").binding_path) == \ + hpath("test-bindings/device-on-bar-bus.yaml") + assert str(edt.get_node("/buses/no-bus-node").binding_path) == \ + hpath("test-bindings/device-on-any-bus.yaml") + + # foo-bus/node/nested also appears on the foo-bus bus + assert edt.get_node("/buses/foo-bus/node1/nested").on_bus == "foo" + assert str(edt.get_node("/buses/foo-bus/node1/nested").binding_path) == \ + hpath("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") + + assert str(child1.binding_path) == hpath("test-bindings/child-binding.yaml") + assert str(child1.description) == "child node" + assert str(child1.props) == "OrderedDict([('child-prop', )])" + + assert str(child2.binding_path) == hpath("test-bindings/child-binding.yaml") + assert str(child2.description) == "child node" + assert str(child2.props) == "OrderedDict([('child-prop', )])" + + assert str(grandchild.binding_path) == hpath("test-bindings/child-binding.yaml") + assert str(grandchild.description) == "grandchild node" + assert str(grandchild.props) == "OrderedDict([('grandchild-prop', )])" + + binding_file = Path("test-bindings/child-binding.yaml").resolve() + top = edtlib.Binding(binding_file, {}) + child = top.child_binding + assert Path(top.path) == binding_file + assert Path(child.path) == binding_file + assert top.compatible == 'top-binding' + assert child.compatible is None + + binding_file = Path("test-bindings/child-binding-with-compat.yaml").resolve() + top = edtlib.Binding(binding_file, {}) + child = top.child_binding + assert Path(top.path) == binding_file + assert Path(child.path) == binding_file + assert top.compatible == 'top-binding-with-compat' + assert child.compatible == 'child-compat' + +def test_props(): + '''Test Node.props (derived from DT and 'properties:' in the binding)''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + filenames = {i: hpath(f'test-bindings/phandle-array-controller-{i}.yaml') + for i in range(0, 4)} + + assert str(edt.get_node("/props").props["int"]) == \ + "" + + assert str(edt.get_node("/props").props["existent-boolean"]) == \ + "" + + assert str(edt.get_node("/props").props["nonexistent-boolean"]) == \ + "" + + assert str(edt.get_node("/props").props["array"]) == \ + "" + + assert str(edt.get_node("/props").props["uint8-array"]) == \ + r"" + + assert str(edt.get_node("/props").props["string"]) == \ + "" + + assert str(edt.get_node("/props").props["string-array"]) == \ + "" + + assert str(edt.get_node("/props").props["phandle-ref"]) == \ + f">" + + assert str(edt.get_node("/props").props["phandle-refs"]) == \ + f", ]>" + + assert str(edt.get_node("/props").props["phandle-array-foos"]) == \ + f", data: OrderedDict([('one', 1)])>, , data: OrderedDict([('one', 2), ('two', 3)])>]>" + + assert str(edt.get_node("/props-2").props["phandle-array-foos"]) == \ + (", data: OrderedDict()>, " + "None, " + f", data: OrderedDict()>]>") + + assert str(edt.get_node("/props").props["foo-gpios"]) == \ + f", data: OrderedDict([('gpio-one', 1)])>]>" + + assert str(edt.get_node("/props").props["path"]) == \ + f">" + +def test_nexus(): + '''Test -map via gpio-map (the most common case).''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + filename = hpath('test-bindings/gpio-dst.yaml') + + assert str(edt.get_node("/gpio-map/source").props["foo-gpios"]) == \ + f", data: OrderedDict([('val', 6)])>, , data: OrderedDict([('val', 5)])>]>" + +def test_prop_defaults(): + '''Test property default values given in bindings''' + edt = edtlib.EDT("test.dts", ["test-bindings"]) + + assert str(edt.get_node("/defaults").props) == \ + r"OrderedDict([('int', ), ('array', ), ('uint8-array', ), ('string', ), ('string-array', ), ('default-not-used', )])" + +def test_prop_enums(): + '''test properties with enum: in the binding''' + + edt = edtlib.EDT("test.dts", ["test-bindings"]) + props = edt.get_node('/enums').props + int_enum = props['int-enum'] + string_enum = props['string-enum'] + tokenizable_enum = props['tokenizable-enum'] + tokenizable_lower_enum = props['tokenizable-lower-enum'] + no_enum = props['no-enum'] + + assert int_enum.val == 1 + assert int_enum.enum_index == 0 + assert not int_enum.spec.enum_tokenizable + assert not int_enum.spec.enum_upper_tokenizable + + assert string_enum.val == 'foo_bar' + assert string_enum.enum_index == 1 + assert not string_enum.spec.enum_tokenizable + assert not string_enum.spec.enum_upper_tokenizable + + assert tokenizable_enum.val == '123 is ok' + assert tokenizable_enum.val_as_token == '123_is_ok' + assert tokenizable_enum.enum_index == 2 + assert tokenizable_enum.spec.enum_tokenizable + assert tokenizable_enum.spec.enum_upper_tokenizable + + assert tokenizable_lower_enum.val == 'bar' + assert tokenizable_lower_enum.val_as_token == 'bar' + assert tokenizable_lower_enum.enum_index == 0 + assert tokenizable_lower_enum.spec.enum_tokenizable + assert not tokenizable_lower_enum.spec.enum_upper_tokenizable + + assert no_enum.enum_index is None + assert not no_enum.spec.enum_tokenizable + assert not no_enum.spec.enum_upper_tokenizable + +def test_binding_inference(): + '''Test inferred bindings for special zephyr-specific nodes.''' + warnings = io.StringIO() + edt = edtlib.EDT("test.dts", ["test-bindings"], warnings) + + assert str(edt.get_node("/zephyr,user").props) == r"OrderedDict()" + + edt = edtlib.EDT("test.dts", ["test-bindings"], warnings, + infer_binding_for_paths=["/zephyr,user"]) + filenames = {i: hpath(f'test-bindings/phandle-array-controller-{i}.yaml') + for i in range(1, 3)} + + assert str(edt.get_node("/zephyr,user").props) == \ + rf"OrderedDict([('boolean', ), ('bytes', ), ('number', ), ('numbers', ), ('string', ), ('strings', ), ('handle', >), ('phandles', , ]>), ('phandle-array-foos', , 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"]) + + assert str(edt.get_node("/in-dir-1").binding_path) == \ + hpath("test-bindings/multidir.yaml") + + assert str(edt.get_node("/in-dir-2").binding_path) == \ + hpath("test-bindings-2/multidir.yaml") + +def test_dependencies(): + ''''Test dependency relations''' + edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"]) + + 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 + +def test_slice_errs(tmp_path): + '''Test error messages from the internal _slice() helper''' + + dts_file = tmp_path / "error.dts" + + verify_error(""" +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <2>; + + sub { + reg = <3>; + }; +}; +""", + dts_file, + f"'reg' property in 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/; + +/ { + sub { + interrupts = <1>; + interrupt-parent = < &{/controller} >; + }; + controller { + interrupt-controller; + #interrupt-cells = <2>; + }; +}; +""", + dts_file, + f"'interrupts' property in 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/; + +/ { + #address-cells = <1>; + + sub-1 { + #address-cells = <2>; + #size-cells = <3>; + ranges = <4 5>; + + sub-2 { + reg = <1 2 3 4 5>; + }; + }; +}; +""", + dts_file, + f"'ranges' property in 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').") + +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. + + with open(dts_file, "w", encoding="utf-8") as f: + f.write(dts) + f.flush() # Can't have unbuffered text IO, so flush() instead + + with pytest.raises(edtlib.EDTError) as e: + edtlib.EDT(dts_file, []) + + assert str(e.value) == expected_err