From efdbace8d808b7f93ab742aa8362a73c808df5c7 Mon Sep 17 00:00:00 2001 From: Christophe Dufaza Date: Thu, 10 Oct 2024 18:13:05 +0200 Subject: [PATCH 1/2] edtlib: tests: refine bindings initialization coverage Bindings initialization supports a rich include mechanism: - a base binding may be "include:"ed at any level: binding, child-binding, grandchild-binding, etc, the defined properties being injected (in cascade) at the level of inclusion - whenever an "include:" appears, the including binding may filter the properties it expects at the binding, child-binding, etc, levels; these filters must apply transitively across included files - when base bindings are inherited which contain definitions about a same property, rules apply: sometimes the last one "wins", but not always, and quite a few things are not allowed (e.g. downgrading a "required: true" or changing a "const:" value) All this happens (recursively) in the Binding constructor, independently of any actual devicetree model (EDT instance). These additional unit tests try to cover somewhat systematically what we finally get at the exit of the constructor once the object is completely initialized. Signed-off-by: Christophe Dufaza --- .../tests/test-bindings-init/base.yaml | 104 ++ .../tests/test-bindings-init/base_amend.yaml | 96 ++ .../test-bindings-init/base_inherit.yaml | 5 + .../tests/test-bindings-init/base_multi.yaml | 103 ++ .../tests/test-bindings-init/diamond.yaml | 93 ++ .../invalid_child_propconst.yaml | 11 + .../invalid_child_propdefault.yaml | 11 + .../invalid_child_propenum.yaml | 13 + .../invalid_child_propreq.yaml | 11 + .../invalid_child_proptype.yaml | 11 + .../invalid_grandchild_propconst.yaml | 12 + .../invalid_grandchild_propdefault.yaml | 12 + .../invalid_grandchild_propenum.yaml | 14 + .../invalid_grandchild_propreq.yaml | 12 + .../invalid_grandchild_proptype.yaml | 12 + .../test-bindings-init/invalid_propconst.yaml | 15 + .../invalid_propdefault.yaml | 15 + .../test-bindings-init/invalid_propenum.yaml | 17 + .../test-bindings-init/invalid_propreq.yaml | 15 + .../test-bindings-init/invalid_proptype.yaml | 15 + .../tests/test-bindings-init/simple.yaml | 24 + .../test-bindings-init/simple_filter.yaml | 12 + .../test-bindings-init/simple_inherit.yaml | 5 + .../tests/test-bindings-init/thing.yaml | 72 ++ .../tests/test-bindings-init/vnd,thing.yaml | 20 + .../tests/test_edtlib_binding_init.py | 1023 +++++++++++++++++ 26 files changed, 1753 insertions(+) create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/base.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/base_amend.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/base_inherit.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/base_multi.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/diamond.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propconst.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propdefault.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propenum.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propreq.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_proptype.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propconst.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propdefault.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propenum.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propreq.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_proptype.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propconst.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propdefault.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propenum.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propreq.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/invalid_proptype.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/simple.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/simple_filter.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/simple_inherit.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/thing.yaml create mode 100644 scripts/dts/python-devicetree/tests/test-bindings-init/vnd,thing.yaml create mode 100644 scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/base.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/base.yaml new file mode 100644 index 0000000000000..7535b28b54459 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/base.yaml @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Base include file for testing bindings initialization. +# +# Involves base property definitions ("type:", "description:", "const:", +# "required:", "enum:" and "default:") up to the grandchild-binding level. +# +# Binding: +# + prop-1 +# + prop-2 +# + prop-enum +# + prop-req +# + prop-const +# + prop-default +# +# Child-binding: +# + child-prop-1 +# + child-prop-2 +# + child-prop-enum +# + child-prop-req +# + child-prop-const +# + child-prop-default +# +# Grandchild-binding: +# + grandchild-prop-1 +# + grandchild-prop-2 +# + grandchild-prop-enum +# + grandchild-prop-req +# + grandchild-prop-const +# + grandchild-prop-default + +description: Base property specifications. + +properties: + prop-1: + description: Base property 1. + type: int + prop-2: + type: string + prop-enum: + type: string + required: false + enum: + - FOO + - BAR + prop-const: + type: int + const: 8 + prop-req: + type: int + required: true + prop-default: + type: int + default: 1 + +child-binding: + description: Base child-binding description. + + properties: + child-prop-1: + description: Base child-prop 1. + type: int + child-prop-2: + type: string + child-prop-enum: + type: string + required: false + enum: + - CHILD_FOO + - CHILD_BAR + child-prop-const: + type: int + const: 16 + child-prop-req: + type: int + required: true + child-prop-default: + type: int + default: 2 + + child-binding: + description: Base grandchild-binding description. + + properties: + grandchild-prop-1: + description: Base grandchild-prop 1. + type: int + grandchild-prop-2: + type: string + grandchild-prop-enum: + type: string + required: false + enum: + - GRANDCHILD_FOO + - GRANDCHILD_BAR + grandchild-prop-const: + type: int + const: 32 + grandchild-prop-req: + type: int + required: true + grandchild-prop-default: + type: int + default: 3 diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/base_amend.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/base_amend.yaml new file mode 100644 index 0000000000000..bd24de099c83d --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/base_amend.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Amends base properties specifications: +# - extends property specifications by adding definitions, +# e.g. setting a "default:" value +# - overwrites existing definitions of a property, +# e.g. change its "description:" +# - specify new properties +# +# The same kind of amendments are applied to the same properties +# at each level (binding, child-binding, grandchild-binding). +# +# | Definition | Extended for | Overwritten for | +# |----------------|--------------|-----------------| +# | description: | prop-2 | prop-1 | +# | required: | | prop-enum | +# | enum: | prop-2 | | +# | const: | prop-1 | | +# | default: | prop-2 | | +# +# Non authorized amendments, e.g. changing a "const:" value +# or downgrading a "required: true" definition are tested separately. + +description: Amended description. + +include: base.yaml + +properties: + prop-1: + # The including binding is permitted to overwrite a property description. + description: Overwritten description. + # The including binding is permitted to set a "const:" value. + const: 0xf0 + + prop-2: + # The including binding is permitted to add a property description. + description: New description. + # The including binding is permitted to limit property values + # to an enumeration. + enum: + - EXT_FOO + - EXT_BAR + # The including binding is permitted to set a default value. + default: EXT_FOO + + # The including binding is permitted to promote a property + # to requirement. + prop-enum: + required: true + + # The including binding is permitted to define a new property. + prop-new: + type: int + +# Same amendments at the child-binding level. +child-binding: + properties: + child-prop-1: + description: Overwritten description (child). + const: 0xf1 + + child-prop-2: + description: New description (child). + enum: + - CHILD_EXT_FOO + - CHILD_EXT_BAR + default: CHILD_EXT_FOO + + child-prop-enum: + required: true + + child-prop-new: + type: int + + # Same amendments at the grandchild-binding level. + child-binding: + # Plus amended grandchild-binding description. + description: Amended grandchild-binding description. + + properties: + grandchild-prop-1: + description: Overwritten description (grandchild). + const: 0xf2 + + grandchild-prop-2: + description: New description (grandchild). + enum: + - GRANDCHILD_EXT_FOO + - GRANDCHILD_EXT_BAR + default: GRANDCHILD_EXT_FOO + + grandchild-prop-enum: + required: true + + grandchild-prop-new: + type: int diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/base_inherit.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/base_inherit.yaml new file mode 100644 index 0000000000000..eec7711c7a413 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/base_inherit.yaml @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Inherit base specifications without modification. + +include: base.yaml diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/base_multi.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/base_multi.yaml new file mode 100644 index 0000000000000..c713f1b8d3a66 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/base_multi.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Includes base bindings at multiple levels (binding, +# child-binding, grandchild-binding): +# +# include: base.yaml +# child-binding: +# include: base.yaml +# child-binding: +# include: base.yaml +# +# Which properties are specified at which levels is summarized bellow +# for convenience. +# +# Child-binding level: +# From top-level "include:" element. +# - child-prop-1 (amended) +# - child-prop-2 +# - child-prop-enum +# From "child-binding: include:" element. +# - prop-1 (amended) +# - prop-2 (amended) +# - prop-enum (amended) +# +# Grandchild-binding level: +# From top-level "include:" element. +# - grandchild-prop-1 (amended) +# - grandchild-prop-2 +# - grandchild-prop-enum +# From "child-binding: include:" element. +# - child-prop-1 (amended) +# - child-prop-2 +# - child-prop-enum +# From "child-binding: child-binding: include:" element. +# - prop-1 (amended) +# - prop-2 (amended) +# - prop-enum (amended) +# +# Grand-grandchild-binding level: +# From "child-binding: include:" element. +# - child-prop-1 +# - child-prop-2 +# - child-prop-enum +# From "child-binding: child-binding: include:" element. +# - grandchild-prop-1 +# - grandchild-prop-2 +# - grandchild-prop-enum + +description: Description of 'base_multi.yaml'. + +include: + - name: base.yaml + child-binding: + property-allowlist: [child-prop-1, child-prop-2, child-prop-enum] + child-binding: + property-allowlist: [grandchild-prop-1, grandchild-prop-2, grandchild-prop-enum] + +child-binding: + include: + - name: base.yaml + property-allowlist: [prop-1, prop-2, prop-enum] + child-binding: + property-allowlist: [child-prop-1, child-prop-2, child-prop-enum] + child-binding: + property-allowlist: [grandchild-prop-1, grandchild-prop-2, grandchild-prop-enum] + + properties: + # Amend top-level "include:" element. + child-prop-1: + const: 0xf1 + # Amend this "child-binding: include:" element. + prop-1: + const: 0xf1 + prop-2: + description: New description (child). + prop-enum: + required: true + default: FOO + + child-binding: + include: + - name: base.yaml + property-allowlist: [prop-1, prop-2, prop-enum] + child-binding: + property-allowlist: [child-prop-1, child-prop-2, child-prop-enum] + child-binding: + property-allowlist: [grandchild-prop-1, grandchild-prop-2, grandchild-prop-enum] + + properties: + # Amend above top-level "include:" element. + grandchild-prop-1: + const: 0xf2 + # Amend above "child-binding: include:" element. + child-prop-1: + const: 0xf2 + # Amend this "child-binding: child-binding: include:" element. + prop-1: + const: 0xf2 + prop-2: + description: New description (grandchild). + prop-enum: + required: true + default: FOO diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/diamond.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/diamond.yaml new file mode 100644 index 0000000000000..0a3865506eaa6 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/diamond.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Binding file for testing diamond inheritance (top-bottom). +# +# diamond.yaml +# / \ +# / \ +# base_amend.yaml thing.yaml +# \ / +# \ / +# base.yaml +# +# Which properties are specified at which levels is summarized bellow +# for convenience. +# +# * Binding level. +# Diamond's left: +# - prop-1 (amended in base_amend.yaml) +# - prop-enum (amended in base_amend.yaml) +# - prop-default (inherited from base.yaml) +# Diamond's right: +# - prop-1 (last amended in thing.yaml) +# - prop-enum (amended in thing.yaml) +# - prop-thing (inherited from thing.yaml) +# Diamond's top: +# - prop-enum (last amended here) +# - prop-diamond +# +# * Child-binding level: +# Diamond's left: +# - child-prop-1 (amended in base_amend.yaml) +# - child-prop-enum (amended in base_amend.yaml) +# - child-prop-default (inherited from base.yaml) +# Diamond's right: +# - child-prop-1 (last amended in thing.yaml) +# - child-prop-enum (amended in thing.yaml) +# - child-prop-thing (inherited from thing.yaml) +# Diamond's top: +# - child-prop-enum (last amended here) +# - child-prop-diamond +# +# * Grandchild-binding level: +# Diamond's left: +# - grandchild-prop-1 (amended in base_amend.yaml) +# - grandchild-prop-enum (amended in base_amend.yaml) +# - grandchild-prop-default (inherited from base.yaml) +# Diamond's right: +# - grandchild-prop-1 (last amended in thing.yaml) +# - grandchild-prop-enum (amended in thing.yaml) +# - grandchild-prop-thing (inherited from thing.yaml) +# Diamond's top: +# - grandchild-prop-enum (last amended here) +# - grandchild-prop-diamond + +description: Diamond's top. + +compatible: diamond + +include: + # Diamond's left. + - name: base_amend.yaml + property-allowlist: [prop-1, prop-enum, prop-default] + child-binding: + property-allowlist: [child-prop-1, child-prop-enum, child-prop-default] + child-binding: + property-allowlist: [grandchild-prop-1, grandchild-prop-enum, grandchild-prop-default] + # Diamond's right. + - name: thing.yaml + +properties: + prop-diamond: + type: int + prop-enum: + description: Overwritten in diamond.yaml. + default: FOO + +child-binding: + description: Diamond's child-binding. + + properties: + child-prop-diamond: + type: int + child-prop-enum: + description: Overwritten in diamond.yaml (child). + default: CHILD_FOO + + child-binding: + properties: + grandchild-prop-diamond: + type: int + grandchild-prop-enum: + description: Overwritten in diamond.yaml (grandchild). + default: GRANDCHILD_FOO diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propconst.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propconst.yaml new file mode 100644 index 0000000000000..20739bad14aab --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propconst.yaml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "const:" value +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + properties: + child-prop-const: + const: 999 diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propdefault.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propdefault.yaml new file mode 100644 index 0000000000000..fd1ec81361259 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propdefault.yaml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "default:" value +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + properties: + child-prop-default: + default: 999 diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propenum.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propenum.yaml new file mode 100644 index 0000000000000..73947b3007e9c --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propenum.yaml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to change the "enum:" values +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + properties: + child-prop-enum: + enum: + - OTHER_FOO + - OTHER_BAR diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propreq.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propreq.yaml new file mode 100644 index 0000000000000..298a3624ffb53 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_propreq.yaml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override "required: true" +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + properties: + child-prop-req: + required: false diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_proptype.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_proptype.yaml new file mode 100644 index 0000000000000..8f80ec81e3855 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_child_proptype.yaml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "type:" +# of an inherited property. + +include: base.yaml + +child-binding: + properties: + child-prop-1: + type: string diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propconst.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propconst.yaml new file mode 100644 index 0000000000000..5b8a1cb0d5178 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propconst.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "const:" value +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + child-binding: + properties: + grandchild-prop-const: + const: 999 diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propdefault.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propdefault.yaml new file mode 100644 index 0000000000000..f2f69d31e4b7e --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propdefault.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "default:" value +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + child-binding: + properties: + grandchild-prop-default: + default: 999 diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propenum.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propenum.yaml new file mode 100644 index 0000000000000..637ba811e3dfb --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propenum.yaml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to change the "enum:" values +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + child-binding: + properties: + grandchild-prop-enum: + enum: + - OTHER_FOO + - OTHER_BAR diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propreq.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propreq.yaml new file mode 100644 index 0000000000000..dc9273a7adc5f --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_propreq.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override "required: true" +# in a property specification inherited from an included file. + +include: base.yaml + +child-binding: + child-binding: + properties: + grandchild-prop-req: + required: false diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_proptype.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_proptype.yaml new file mode 100644 index 0000000000000..4135db67f1d15 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_grandchild_proptype.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "type:" +# of an inherited property. + +include: base.yaml + +child-binding: + child-binding: + properties: + grandchild-prop-1: + type: string diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propconst.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propconst.yaml new file mode 100644 index 0000000000000..9eaa11e31d549 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propconst.yaml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "const:" value +# in a property specification inherited from an included file. +# +# Bindings, child-bindings and grandchild-bindings have +# to be tested separately, see also: +# - invalid_child_propconst.yaml +# - invalid_grandchild_propconst.yaml + +include: base.yaml + +properties: + prop-const: + const: 999 diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propdefault.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propdefault.yaml new file mode 100644 index 0000000000000..8b621a24098f1 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propdefault.yaml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "default:" value +# in a property specification inherited from an included file. +# +# Bindings, child-bindings and grandchild-bindings have +# to be tested separately, see also: +# - invalid_child_propdefault.yaml +# - invalid_grandchild_propdefault.yaml + +include: base.yaml + +properties: + prop-default: + default: 999 diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propenum.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propenum.yaml new file mode 100644 index 0000000000000..38839826536c7 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propenum.yaml @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to change the "enum:" values +# in a property specification inherited from an included file. +# +# Bindings, child-bindings and grandchild-bindings have +# to be tested separately, see also: +# - invalid_child_propenum.yaml +# - invalid_grandchild_propenum.yaml + +include: base.yaml + +properties: + prop-enum: + enum: + - OTHER_FOO + - OTHER_BAR diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propreq.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propreq.yaml new file mode 100644 index 0000000000000..fd0412c505be0 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_propreq.yaml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override "required: true" +# in a property specification inherited from an included file. +# +# Bindings, child-bindings and grandchild-bindings have +# to be tested separately, see also: +# - invalid_child_propreq.yaml +# - invalid_grandchild_propreq.yaml + +include: base.yaml + +properties: + prop-req: + required: false diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_proptype.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_proptype.yaml new file mode 100644 index 0000000000000..fca2c752676e3 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/invalid_proptype.yaml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding should not try to override the "type:" +# of an inherited property. +# +# Bindings, child-bindings and grandchild-bindings have +# to be tested separately, see also: +# - invalid_child_protype.yaml +# - invalid_grandchild_proptype.yaml + +include: base.yaml + +properties: + prop-1: + type: string diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/simple.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/simple.yaml new file mode 100644 index 0000000000000..007f1e1b849f2 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/simple.yaml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Base properties for testing property filters propagation +# up to the grandchild-binding level. + +properties: + prop-1: + type: int + prop-2: + type: int + +child-binding: + properties: + child-prop-1: + type: int + child-prop-2: + type: int + + child-binding: + properties: + grandchild-prop-1: + type: int + grandchild-prop-2: + type: int diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/simple_filter.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_filter.yaml new file mode 100644 index 0000000000000..cae1cb2800acc --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_filter.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Filter inherited property specifications +# up to the grandchild-binding level. + +include: + - name: simple_inherit.yaml + property-allowlist: [prop-1] + child-binding: + property-allowlist: [child-prop-1] + child-binding: + property-allowlist: [grandchild-prop-1] diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/simple_inherit.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_inherit.yaml new file mode 100644 index 0000000000000..8a95ef38f9511 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_inherit.yaml @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Inherits property specifications without modification. + +include: simple.yaml diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/thing.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/thing.yaml new file mode 100644 index 0000000000000..82a31aaa39117 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/thing.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Right (included last) YAML file for testing diamond inheritance. +# +# Amends base.yaml. +# +# Binding level: +# - prop-1 (amended) +# - prop-enum (amended) +# - prop-thing (new property) +# +# Child-binding level: +# - child-prop-1 (amended) +# - child-prop-enum (amended) +# - child-prop-thing (new property) +# +# Grandchild-binding level: +# - grandchild-prop-1 (amended) +# - grandchild-prop-enum (amended) +# - grandchild-prop-thing (new property) + +description: Description of 'thing.yaml'. + +include: + - name: base.yaml + property-allowlist: [prop-1, prop-enum] + child-binding: + property-allowlist: [child-prop-1, child-prop-enum] + child-binding: + property-allowlist: [grandchild-prop-1, grandchild-prop-enum] + +properties: + prop-1: + default: 1 + # Diamond inheritance in diamond.yaml: should overwrite + # the amended description from base_amend.yaml. + description: Overwritten in thing.yaml. + + prop-enum: + # This is the definition inherited from base.yaml. + # + # Diamond inheritance in diamond.yaml: should be ORed + # with the definition inherited via base_amend.yaml. + required: false + + prop-thing: + description: Thing property. + type: int + +child-binding: + description: Child-binding description (thing). + properties: + child-prop-1: + description: Overwritten in thing.yaml (child). + default: 2 + child-prop-enum: + required: false + child-prop-thing: + description: Thing child-binding property. + type: int + + child-binding: + description: Grandchild-binding description (thing). + properties: + grandchild-prop-1: + description: Overwritten in thing.yaml (grandchild). + default: 3 + grandchild-prop-enum: + required: false + grandchild-prop-thing: + description: Thing grandchild-binding property. + type: int diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/vnd,thing.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/vnd,thing.yaml new file mode 100644 index 0000000000000..021c50481a8cb --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/vnd,thing.yaml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Top level binding file (device binding) for testing +# descirptions and compatible strings. + +description: The Thing device. + +compatible: "vnd,thing" + +include: + - name: base_amend.yaml + - name: thing.yaml + +child-binding: + compatible: "vnd,thing-child" + description: The Thing's child-binding. + + child-binding: + compatible: "vnd,thing-grandchild" + description: The Thing's grandchild-binding. diff --git a/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py b/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py new file mode 100644 index 0000000000000..d593121404a7a --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py @@ -0,0 +1,1023 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2024 Christophe Dufaza + +"""Unit tests dedicated to edtlib.Binding objects initialization. + +Running the assumption that any (valid) YAML binding file is +something we can make a Binding instance with: +- check which properties are defined at which level (binding, child-binding, + grandchild-binding, etc) and their specifications once the binding + is initialized. +- check how including bindings are permitted to specialize + the specifications of inherited properties + +At any level, an including binding is permitted to: +- filter the properties it chooses to inherit +- extend inherited properties: + - override implicit or explicit "required: false" with "required: true" + - constrain possible values with an "enum:" + - set a "const:" or "default:" value to a property which does not have one + - set or change a property "description:" +- define new properties + +At any level, an including binding is NOT permitted to: +- remove a requirement by overriding "required: true" with "required: false" +- change the values in an "enum:" specifying an inherited property +- change the "const:" and "default:" values of an inherited property + +For all tests, the entry point is a Binding instance initialized +by loading the YAML file which represents the test case: our focus here +really is what happens when we (recursively) call Binding's constructor, +independently of any actual devicetree model (edtlib.EDT instance). +""" + +from typing import Any, Dict, Generator, List, Optional, Union +import contextlib +import os + +import pytest + +from devicetree import edtlib + +YAML_BASE: Dict[str, str] = { + # Base properties, bottom of the diamond test case. + "base.yaml": "test-bindings-init/base.yaml", + # Amended properties, left (first) "include:" in the diamond test case. + "base_amend.yaml": "test-bindings-init/base_amend.yaml", + # Amended properties, right (last) "include:" in the diamond test case. + "thing.yaml": "test-bindings-init/thing.yaml", + # Used for testing property filters propagation within + # nested "include:" files. + "simple.yaml": "test-bindings-init/simple.yaml", + "simple_inherit.yaml": "test-bindings-init/simple_inherit.yaml", +} + + +def load_binding(path: str) -> edtlib.Binding: + """Load YAML file as Binding instance, + using YAML_BASE to resolve includes. + + Args: + path: Path relative to ZEPHYR_BASE/scripts/dts/python-devicetree/tests. + """ + with _from_here(): + binding = edtlib.Binding( + path=path, + fname2path=YAML_BASE, + raw=None, + require_compatible=False, + require_description=False, + inc_allowlist=None, + inc_blocklist=None, + ) + return binding + + +def child_binding_of(path: str) -> edtlib.Binding: + """Load YAML file as Binding instance, and returns its child-binding. + The child-binding must exist. + + Args: + path: Path relative to ZEPHYR_BASE/scripts/dts/python-devicetree/tests. + """ + binding = load_binding(path) + assert binding.child_binding + return binding.child_binding + + +def grandchild_binding_of(path: str) -> edtlib.Binding: + """Load YAML file as Binding instance, and returns its grandchild-binding. + The grandchild-binding must exist. + + Args: + path: Path relative to ZEPHYR_BASE/scripts/dts/python-devicetree/tests. + """ + child_binding = child_binding_of(path) + assert child_binding.child_binding + return child_binding.child_binding + + +def verify_expected_propspec( + propspec: edtlib.PropertySpec, + /, + *, + # Most properties are integers. + expect_type: str = "int", + expect_req: bool = False, + expect_desc: Optional[str] = None, + expect_enum: Optional[List[Union[int, str]]] = None, + expect_const: Optional[Any] = None, + expect_default: Optional[Any] = None, +) -> None: + """Compare a property specification with the definitions + we (finally) expect. + + All definitions are tested for equality. + + Args: + propsec: The property specification to verify. + expect_type: The exepected property type definition. + expect_req: Whether the property is expected to be required. + expect_desc: The expected property description. + expect_enum: The expected property "enum:" definition. + expect_const: The expected property "const:" definition. + expect_default: The expected property "default:" definition. + """ + assert expect_type == propspec.type + assert expect_req == propspec.required + assert expect_desc == propspec.description + assert expect_enum == propspec.enum + assert expect_const == propspec.const + assert expect_default == propspec.default + + +def verify_binding_propspecs_consistency(binding: edtlib.Binding) -> None: + """Verify consistency between what's in Binding.prop2specs + and Binding.raw. + + Asserts that: + Binding.raw["properties"][prop] == Binding.prop2specs[prop]._raw + + If the binding has a child-binding, also recursively verify child-bindings. + + NOTE: do not confuse with binding.prop2specs[prop].binding == binding, + which is not always true, since we use Binding as a PropertySpec factory + when moving the "last modified here" semantic. + """ + if binding.prop2specs: + assert set(binding.raw["properties"].keys()) == set( + binding.prop2specs.keys() + ) + all( + binding.raw["properties"][prop] == propspec._raw + for prop, propspec in binding.prop2specs.items() + ) + if binding.child_binding: + verify_binding_propspecs_consistency(binding.child_binding) + + +def test_expect_propspecs_inherited_bindings() -> None: + """Test the basics of including property specifications. + + Specifications are simply inherited without modifications + up to the grandchild-binding level. + + Check that we actually inherit all expected definitions as-is. + """ + binding = load_binding("test-bindings-init/base_inherit.yaml") + + # Binding level. + assert { + "prop-1", + "prop-2", + "prop-enum", + "prop-req", + "prop-const", + "prop-default", + } == set(binding.prop2specs.keys()) + propspec = binding.prop2specs["prop-1"] + verify_expected_propspec(propspec, expect_desc="Base property 1.") + propspec = binding.prop2specs["prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = binding.prop2specs["prop-enum"] + verify_expected_propspec( + propspec, expect_type="string", expect_enum=["FOO", "BAR"] + ) + propspec = binding.prop2specs["prop-const"] + verify_expected_propspec(propspec, expect_const=8) + propspec = binding.prop2specs["prop-req"] + verify_expected_propspec(propspec, expect_req=True) + propspec = binding.prop2specs["prop-default"] + verify_expected_propspec(propspec, expect_default=1) + + # Child-Binding level. + assert binding.child_binding + child_binding = binding.child_binding + assert { + "child-prop-1", + "child-prop-2", + "child-prop-enum", + "child-prop-req", + "child-prop-const", + "child-prop-default", + } == set(child_binding.prop2specs.keys()) + propspec = child_binding.prop2specs["child-prop-1"] + verify_expected_propspec(propspec, expect_desc="Base child-prop 1.") + propspec = child_binding.prop2specs["child-prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = child_binding.prop2specs["child-prop-enum"] + verify_expected_propspec( + propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"] + ) + propspec = child_binding.prop2specs["child-prop-const"] + verify_expected_propspec(propspec, expect_const=16) + propspec = child_binding.prop2specs["child-prop-req"] + verify_expected_propspec(propspec, expect_req=True) + propspec = child_binding.prop2specs["child-prop-default"] + verify_expected_propspec(propspec, expect_default=2) + + # GrandChild-Binding level. + assert child_binding.child_binding + grandchild_binding = child_binding.child_binding + assert { + "grandchild-prop-1", + "grandchild-prop-2", + "grandchild-prop-enum", + "grandchild-prop-req", + "grandchild-prop-const", + "grandchild-prop-default", + } == set(grandchild_binding.prop2specs.keys()) + propspec = grandchild_binding.prop2specs["grandchild-prop-1"] + verify_expected_propspec(propspec, expect_desc="Base grandchild-prop 1.") + propspec = grandchild_binding.prop2specs["grandchild-prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = grandchild_binding.prop2specs["grandchild-prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], + ) + propspec = grandchild_binding.prop2specs["grandchild-prop-const"] + verify_expected_propspec(propspec, expect_const=32) + propspec = grandchild_binding.prop2specs["grandchild-prop-req"] + verify_expected_propspec(propspec, expect_req=True) + propspec = grandchild_binding.prop2specs["grandchild-prop-default"] + verify_expected_propspec(propspec, expect_default=3) + + +def test_expect_propspecs_amended_bindings() -> None: + """Test the basics of including and amending property specifications. + + Base specifications are included once at the binding level: + + include: base.yaml + properties: + # Amend base.yaml + child-binding: + properties: + # Amend base.yaml + child-binding: + properties: + # Amend base.yaml + + Check that we finally get the expected property specifications + up to the grandchild-binding level. + """ + binding = load_binding("test-bindings-init/base_amend.yaml") + + # Binding level. + # + assert { + "prop-1", + "prop-2", + "prop-enum", + "prop-req", + "prop-const", + "prop-default", + "prop-new", + } == set(binding.prop2specs.keys()) + propspec = binding.prop2specs["prop-1"] + verify_expected_propspec( + propspec, + # Amended in base_amend.yaml. + expect_desc="Overwritten description.", + expect_const=0xF0, + ) + propspec = binding.prop2specs["prop-2"] + verify_expected_propspec( + propspec, + expect_type="string", + # Amended in base_amend.yaml. + expect_desc="New description.", + expect_enum=["EXT_FOO", "EXT_BAR"], + expect_default="EXT_FOO", + ) + propspec = binding.prop2specs["prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["FOO", "BAR"], + # Amended in base_amend.yaml. + expect_req=True, + ) + # Inherited from base.yaml without modification. + propspec = binding.prop2specs["prop-const"] + verify_expected_propspec(propspec, expect_const=8) + propspec = binding.prop2specs["prop-req"] + verify_expected_propspec(propspec, expect_req=True) + propspec = binding.prop2specs["prop-default"] + verify_expected_propspec(propspec, expect_default=1) + + # New property in base_amend.yaml. + propspec = binding.prop2specs["prop-new"] + verify_expected_propspec(propspec) + + # Child-Binding level. + # + assert binding.child_binding + child_binding = binding.child_binding + assert { + "child-prop-1", + "child-prop-2", + "child-prop-enum", + "child-prop-req", + "child-prop-const", + "child-prop-default", + "child-prop-new", + } == set(child_binding.prop2specs.keys()) + propspec = child_binding.prop2specs["child-prop-1"] + verify_expected_propspec( + propspec, + # Amended in base_amend.yaml. + expect_desc="Overwritten description (child).", + expect_const=0xF1, + ) + propspec = child_binding.prop2specs["child-prop-2"] + verify_expected_propspec( + propspec, + expect_type="string", + # Amended in base_amend.yaml. + expect_desc="New description (child).", + expect_enum=["CHILD_EXT_FOO", "CHILD_EXT_BAR"], + expect_default="CHILD_EXT_FOO", + ) + propspec = child_binding.prop2specs["child-prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["CHILD_FOO", "CHILD_BAR"], + # Amended in base_amend.yaml. + expect_req=True, + ) + # Inherited from base.yaml without modification. + propspec = child_binding.prop2specs["child-prop-const"] + verify_expected_propspec(propspec, expect_const=16) + propspec = child_binding.prop2specs["child-prop-req"] + verify_expected_propspec(propspec, expect_req=True) + propspec = child_binding.prop2specs["child-prop-default"] + verify_expected_propspec(propspec, expect_default=2) + + # New property in base_amend.yaml. + propspec = child_binding.prop2specs["child-prop-new"] + verify_expected_propspec(propspec) + + # GrandChild-Binding level. + # + assert child_binding.child_binding + grandchild_binding = child_binding.child_binding + assert { + "grandchild-prop-1", + "grandchild-prop-2", + "grandchild-prop-enum", + "grandchild-prop-req", + "grandchild-prop-const", + "grandchild-prop-default", + "grandchild-prop-new", + } == set(grandchild_binding.prop2specs.keys()) + propspec = grandchild_binding.prop2specs["grandchild-prop-1"] + verify_expected_propspec( + propspec, + # Amended in base_amend.yaml. + expect_desc="Overwritten description (grandchild).", + expect_const=0xF2, + ) + propspec = grandchild_binding.prop2specs["grandchild-prop-2"] + verify_expected_propspec( + propspec, + expect_type="string", + # Amended in base_amend.yaml. + expect_desc="New description (grandchild).", + expect_enum=["GRANDCHILD_EXT_FOO", "GRANDCHILD_EXT_BAR"], + expect_default="GRANDCHILD_EXT_FOO", + ) + propspec = grandchild_binding.prop2specs["grandchild-prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], + # Amended in base_amend.yaml. + expect_req=True, + ) + # Inherited from base.yaml without modification. + propspec = grandchild_binding.prop2specs["grandchild-prop-const"] + verify_expected_propspec(propspec, expect_const=32) + propspec = grandchild_binding.prop2specs["grandchild-prop-req"] + verify_expected_propspec(propspec, expect_req=True) + propspec = grandchild_binding.prop2specs["grandchild-prop-default"] + verify_expected_propspec(propspec, expect_default=3) + + # New property in base_amend.yaml. + propspec = grandchild_binding.prop2specs["grandchild-prop-new"] + verify_expected_propspec(propspec) + + +def test_expect_propspecs_multi_child_binding() -> None: + """Test including base bindings at multiple levels. + + Base specifications are included at the binding, child-binding + and child-binding levels: + + include: base.yaml + child-binding: + include: base.yaml + child-binding: + include: base.yaml + + This test checks that we finally get the expected property specifications + at the child-binding level. + """ + binding = child_binding_of("test-bindings-init/base_multi.yaml") + + assert { + # From top-level "include:" element. + "child-prop-1", + "child-prop-2", + "child-prop-enum", + # From "child-binding: include:" element. + "prop-1", + "prop-2", + "prop-enum", + } == set(binding.prop2specs.keys()) + + # Inherited from base.yaml without modification. + propspec = binding.prop2specs["child-prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = binding.prop2specs["child-prop-enum"] + verify_expected_propspec( + propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"] + ) + + propspec = binding.prop2specs["child-prop-1"] + verify_expected_propspec( + propspec, + expect_desc="Base child-prop 1.", + # Amended in base_multi.yaml. + expect_const=0xF1, + ) + propspec = binding.prop2specs["prop-1"] + verify_expected_propspec( + propspec, + expect_desc="Base property 1.", + # Amended in base_multi.yaml. + expect_const=0xF1, + ) + propspec = binding.prop2specs["prop-2"] + verify_expected_propspec( + propspec, + expect_type="string", + # Amended in base_multi.yaml. + expect_desc="New description (child).", + ) + propspec = binding.prop2specs["prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["FOO", "BAR"], + # Amended in base_multi.yaml. + expect_default="FOO", + expect_req=True, + ) + + +def test_expect_propspecs_multi_grandchild_binding() -> None: + """Test including base bindings at multiple levels. + + This test checks that we finally get the expected property specifications + at the grandchild-binding level. + + See also: test_expect_propspecs_multi_child_binding() + """ + binding = grandchild_binding_of("test-bindings-init/base_multi.yaml") + + assert { + # From top-level "include:" element. + "grandchild-prop-1", + "grandchild-prop-2", + "grandchild-prop-enum", + # From "child-binding: include:" element. + "child-prop-1", + "child-prop-2", + "child-prop-enum", + # From "child-binding: child-binding: include:" element. + "prop-1", + "prop-2", + "prop-enum", + } == set(binding.prop2specs.keys()) + + # Inherited from base.yaml without modification. + propspec = binding.prop2specs["grandchild-prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = binding.prop2specs["grandchild-prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], + ) + propspec = binding.prop2specs["child-prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = binding.prop2specs["child-prop-enum"] + verify_expected_propspec( + propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"] + ) + + propspec = binding.prop2specs["grandchild-prop-1"] + verify_expected_propspec( + propspec, + expect_desc="Base grandchild-prop 1.", + # Amended in base_multi.yaml. + expect_const=0xF2, + ) + propspec = binding.prop2specs["child-prop-1"] + verify_expected_propspec( + propspec, + expect_desc="Base child-prop 1.", + # Amended in base_multi.yaml. + expect_const=0xF2, + ) + propspec = binding.prop2specs["prop-1"] + verify_expected_propspec( + propspec, + expect_desc="Base property 1.", + # Amended in base_amend.yaml. + expect_const=0xF2, + ) + propspec = binding.prop2specs["prop-2"] + verify_expected_propspec( + propspec, + expect_type="string", + # Amended in base_amend.yaml. + expect_desc="New description (grandchild).", + ) + propspec = binding.prop2specs["prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["FOO", "BAR"], + # Amended in base_amend.yaml. + expect_req=True, + expect_default="FOO", + ) + + +def test_expect_propspecs_multi_grand_grandchild_binding() -> None: + """Test including base bindings at multiple levels. + + This test checks that we finally get the expected property specifications + at the grand-grandchild-binding level. + + See also: test_expect_propspecs_multi_child_binding() + """ + binding = grandchild_binding_of( + "test-bindings-init/base_multi.yaml" + ).child_binding + assert binding + + assert { + # From "child-binding: include:" element. + "child-prop-1", + "child-prop-2", + "child-prop-enum", + # From "child-binding: child-binding: include:" element. + "grandchild-prop-1", + "grandchild-prop-2", + "grandchild-prop-enum", + } == set(binding.prop2specs.keys()) + + # Inherited from base.yaml without modification. + propspec = binding.prop2specs["child-prop-1"] + verify_expected_propspec(propspec, expect_desc="Base child-prop 1.") + propspec = binding.prop2specs["child-prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = binding.prop2specs["child-prop-enum"] + verify_expected_propspec( + propspec, expect_type="string", expect_enum=["CHILD_FOO", "CHILD_BAR"] + ) + propspec = binding.prop2specs["grandchild-prop-1"] + verify_expected_propspec(propspec, expect_desc="Base grandchild-prop 1.") + propspec = binding.prop2specs["grandchild-prop-2"] + verify_expected_propspec(propspec, expect_type="string") + propspec = binding.prop2specs["grandchild-prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], + ) + + +def test_expect_propspecs_diamond_binding() -> None: + """Test property specifications produced by diamond inheritance. + + This test checks that we finally get the expected property specifications + at the binding level. + """ + binding = load_binding("test-bindings-init/diamond.yaml") + + assert { + # From base.yaml, amended in base_amend.yaml (left), + # last modified in thing.yaml (right). + "prop-1", + # From base.yaml, amended in base_amend.yaml (left), + # and thing.yaml (right), last modified in diamond.yaml(top). + "prop-enum", + # From base.yaml, inherited in base_amend.yaml (left). + "prop-default", + # From thing.yaml (right). + "prop-thing", + # From diamond.yaml (top). + "prop-diamond", + } == set(binding.prop2specs.keys()) + + # Inherited from base.yaml without modification. + propspec = binding.prop2specs["prop-default"] + verify_expected_propspec(propspec, expect_default=1) + # Inherited from thing.yaml without modification. + propspec = binding.prop2specs["prop-thing"] + verify_expected_propspec(propspec, expect_desc="Thing property.") + + # New property in diamond.yaml. + propspec = binding.prop2specs["prop-diamond"] + verify_expected_propspec(propspec) + + propspec = binding.prop2specs["prop-1"] + verify_expected_propspec( + propspec, + # From base_amend.yaml. + expect_const=0xF0, + # From thing.yaml. + expect_default=1, + expect_desc="Overwritten in thing.yaml.", + ) + propspec = binding.prop2specs["prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["FOO", "BAR"], + # From base_amend.yaml. + expect_req=True, + # From diamond.yaml. + expect_desc="Overwritten in diamond.yaml.", + expect_default="FOO", + ) + + +def test_expect_propspecs_diamond_child_binding() -> None: + """Test property specifications produced by diamond inheritance. + + This test checks that we finally get the expected property specifications + at the child-binding level. + """ + binding = child_binding_of("test-bindings-init/diamond.yaml") + + assert { + # From base.yaml, amended in base_amend.yaml (left), + # last modified in thing.yaml (right). + "child-prop-1", + # From base.yaml, amended in base_amend.yaml (left), + # and thing.yaml (right), last modified in diamond.yaml(top). + "child-prop-enum", + # From base.yaml, inherited in base_amend.yaml (left). + "child-prop-default", + # From thing.yaml (right). + "child-prop-thing", + # From diamond.yaml (top). + "child-prop-diamond", + } == set(binding.prop2specs.keys()) + + propspec = binding.prop2specs["child-prop-1"] + verify_expected_propspec( + propspec, + # From base_amend.yaml. + expect_const=0xF1, + # From thing.yaml. + expect_default=2, + expect_desc="Overwritten in thing.yaml (child).", + ) + + propspec = binding.prop2specs["child-prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["CHILD_FOO", "CHILD_BAR"], + # From base_amend.yaml. + # ORed with thing.yaml. + expect_req=True, + # From diamond.yaml. + expect_default="CHILD_FOO", + expect_desc="Overwritten in diamond.yaml (child).", + ) + + # Inherited from base.yaml without modification. + propspec = binding.prop2specs["child-prop-default"] + verify_expected_propspec(propspec, expect_default=2) + # Inherited from thing.yaml without modification. + propspec = binding.prop2specs["child-prop-thing"] + verify_expected_propspec( + propspec, expect_desc="Thing child-binding property." + ) + + # New property in diamond.yaml. + propspec = binding.prop2specs["child-prop-diamond"] + verify_expected_propspec(propspec) + + +def test_expect_propspecs_diamond_grandchild_binding() -> None: + """Test property specifications produced by diamond inheritance. + + This test checks that we finally get the expected property specifications + at the grandchild-binding level. + """ + binding = grandchild_binding_of("test-bindings-init/diamond.yaml") + + assert { + # From base.yaml, amended in base_amend.yaml (left), + # last modified in thing.yaml (right). + "grandchild-prop-1", + # From base.yaml, amended in base_amend.yaml (left), + # last modified in diamond.yaml (top). + "grandchild-prop-enum", + # From base.yaml, inherited in base_amend.yaml (left). + "grandchild-prop-default", + # From thing.yaml (right). + "grandchild-prop-thing", + # From diamond.yaml (top). + "grandchild-prop-diamond", + } == set(binding.prop2specs.keys()) + + propspec = binding.prop2specs["grandchild-prop-1"] + verify_expected_propspec( + propspec, + # From base_amend.yaml. + expect_const=0xF2, + # From thing.yaml. + expect_default=3, + expect_desc="Overwritten in thing.yaml (grandchild).", + ) + + propspec = binding.prop2specs["grandchild-prop-enum"] + verify_expected_propspec( + propspec, + expect_type="string", + expect_enum=["GRANDCHILD_FOO", "GRANDCHILD_BAR"], + # From base_amend.yaml. + # ORed with thing.yaml. + expect_req=True, + # From diamond.yaml. + expect_default="GRANDCHILD_FOO", + expect_desc="Overwritten in diamond.yaml (grandchild).", + ) + + # Inherited from base.yaml without modification. + propspec = binding.prop2specs["grandchild-prop-default"] + verify_expected_propspec(propspec, expect_default=3) + # Inherited from thing.yaml without modification. + propspec = binding.prop2specs["grandchild-prop-thing"] + verify_expected_propspec( + propspec, expect_desc="Thing grandchild-binding property." + ) + + # New property in diamond.yaml. + propspec = binding.prop2specs["grandchild-prop-diamond"] + verify_expected_propspec(propspec) + + +def test_binding_compat_desc() -> None: + """Check which compatible strings and descriptions we finally + get at the binding level. + """ + binding = load_binding("test-bindings-init/base_inherit.yaml") + assert "Base property specifications." == binding.description + assert not binding.compatible + + binding = load_binding("test-bindings-init/base_amend.yaml") + assert "Amended description." == binding.description + assert not binding.compatible + + binding = load_binding("test-bindings-init/diamond.yaml") + assert "Diamond's top." == binding.description + assert "diamond" == binding.compatible + + binding = load_binding("test-bindings-init/vnd,thing.yaml") + assert "The Thing device." == binding.description + assert "vnd,thing" == binding.compatible + + +def test_child_binding_compat_desc() -> None: + """Check which compatible strings and descriptions we finally + get at the child-binding level. + """ + binding = child_binding_of("test-bindings-init/base_inherit.yaml") + assert "Base child-binding description." == binding.description + assert not binding.compatible + + binding = child_binding_of("test-bindings-init/base_amend.yaml") + assert "Base child-binding description." == binding.description + assert not binding.compatible + + binding = child_binding_of("test-bindings-init/diamond.yaml") + assert "Diamond's child-binding." == binding.description + assert not binding.compatible + + binding = child_binding_of("test-bindings-init/vnd,thing.yaml") + assert "The Thing's child-binding." == binding.description + assert "vnd,thing-child" == binding.compatible + + +def test_grandchild_binding_compat_desc() -> None: + """Check which compatible strings and descriptions we finally + get at the grandchild-binding level. + """ + binding = grandchild_binding_of("test-bindings-init/base_inherit.yaml") + assert "Base grandchild-binding description." == binding.description + assert not binding.compatible + + binding = grandchild_binding_of("test-bindings-init/base_amend.yaml") + assert "Amended grandchild-binding description." == binding.description + assert not binding.compatible + + binding = grandchild_binding_of("test-bindings-init/diamond.yaml") + assert "Grandchild-binding description (thing)." == binding.description + assert not binding.compatible + + binding = grandchild_binding_of("test-bindings-init/vnd,thing.yaml") + assert "The Thing's grandchild-binding." == binding.description + assert "vnd,thing-grandchild" == binding.compatible + + +def test_filter_inherited_propspecs_basics() -> None: + """Test basics of property filters propagation up to + the grandchild-binding level. + + Checks that: + - when B includes X in Y + - property filters set by B are actually applied when including Y + """ + binding = load_binding("test-bindings-init/simple_filter.yaml") + assert {"prop-1"} == set(binding.prop2specs.keys()) + assert binding.child_binding + binding = binding.child_binding + assert binding.prop2specs["child-prop-1"].binding is binding + assert {"child-prop-1"} == set(binding.prop2specs.keys()) + assert binding.child_binding + binding = binding.child_binding + assert {"grandchild-prop-1"} == set(binding.prop2specs.keys()) + + +def test_invalid_binding_type_override() -> None: + """An including binding should not try to override the "type:" + of an inherited property. + + Tested up to the grandchild-binding level. + """ + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_proptype.yaml") + assert "prop-1" in str(e) + assert "'int' replaced with 'string'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_child_proptype.yaml") + assert "child-prop-1" in str(e) + assert "'int' replaced with 'string'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_grandchild_proptype.yaml") + assert "grandchild-prop-1" in str(e) + assert "'int' replaced with 'string'" in str(e) + + +def test_invalid_binding_const_override() -> None: + """An including binding should not try to override the "const:" value + in a property specification inherited from an included file. + + Tested up to the grandchild-binding level. + """ + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_propconst.yaml") + assert "prop-const" in str(e) + assert "'8' replaced with '999'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_child_propconst.yaml") + assert "child-prop-const" in str(e) + assert "'16' replaced with '999'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_grandchild_propconst.yaml") + assert "grandchild-prop-const" in str(e) + assert "'32' replaced with '999'" in str(e) + + +def test_invalid_binding_required_override() -> None: + """An including binding should not try to override "required: true" + in a property specification inherited from an included file. + + Tested up to the grandchild-binding level. + """ + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_propreq.yaml") + assert "prop-req" in str(e) + assert "'True' replaced with 'False'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_child_propreq.yaml") + assert "child-prop-req" in str(e) + assert "'True' replaced with 'False'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_grandchild_propreq.yaml") + assert "grandchild-prop-req" in str(e) + assert "'True' replaced with 'False'" in str(e) + + +def test_invalid_binding_default_override() -> None: + """An including binding should not try to override the "default:" value + in a property specification inherited from an included file. + + Tested up to the grandchild-binding level. + """ + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_propdefault.yaml") + assert "prop-default" in str(e) + assert "'1' replaced with '999'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_child_propdefault.yaml") + assert "child-prop-default" in str(e) + assert "'2' replaced with '999'" in str(e) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_grandchild_propdefault.yaml") + assert "grandchild-prop-default" in str(e) + assert "'3' replaced with '999'" in str(e) + + +def test_invalid_binding_enum_override() -> None: + """An including binding should not try to override the "enum:" values + in a property specification inherited from an included file. + + Tested up to the grandchild-binding level. + """ + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_propenum.yaml") + assert "prop-enum" in str(e) + assert "'['FOO', 'BAR']' replaced with '['OTHER_FOO', 'OTHER_BAR']'" in str( + e + ) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_child_propenum.yaml") + assert "child-prop-enum" in str(e) + assert ( + "'['CHILD_FOO', 'CHILD_BAR']' replaced with '['OTHER_FOO', 'OTHER_BAR']'" + in str(e) + ) + + with pytest.raises(edtlib.EDTError) as e: + load_binding("test-bindings-init/invalid_grandchild_propenum.yaml") + assert "grandchild-prop-enum" in str(e) + assert ( + "'['GRANDCHILD_FOO', 'GRANDCHILD_BAR']' replaced with '['OTHER_FOO', 'OTHER_BAR']'" + in str(e) + ) + + +def test_bindings_propspecs_consistency() -> None: + """Verify property specifications consistency. + + Consistency is recursively checked for all defined properties, + from top-level binding files down to their child bindings. + + Consistency is checked with: + Binding.raw["properties"][prop] == Binding.prop2specs[prop]._raw + + See verify_binding_propspecs_consistency(). + """ + binding = load_binding("test-bindings-init/base_inherit.yaml") + verify_binding_propspecs_consistency(binding) + + binding = load_binding("test-bindings-init/base_amend.yaml") + verify_binding_propspecs_consistency(binding) + + binding = load_binding("test-bindings-init/base_multi.yaml") + verify_binding_propspecs_consistency(binding) + + binding = load_binding("test-bindings-init/thing.yaml") + verify_binding_propspecs_consistency(binding) + + binding = load_binding("test-bindings-init/diamond.yaml") + verify_binding_propspecs_consistency(binding) + + +# Borrowed from test_edtlib.py. +@contextlib.contextmanager +def _from_here() -> Generator[None, None, None]: + cwd = os.getcwd() + try: + os.chdir(os.path.dirname(__file__)) + yield + finally: + os.chdir(cwd) + + +def _basename(path: Optional[str]) -> str: + return os.path.basename(path or "?") From 4208824d46444506e20bc710bfc4bf00023f2b9e Mon Sep 17 00:00:00 2001 From: Christophe Dufaza Date: Thu, 10 Oct 2024 19:06:42 +0200 Subject: [PATCH 2/2] edtlib: tests: cover last modified of PropertySpec.path These tests show a known issue and will fail with the version of edtlib at the time of writing. See also: #78095 Signed-off-by: Christophe Dufaza --- .../tests/test_edtlib_binding_init.py | 132 +++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py b/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py index d593121404a7a..06dcd1678e52f 100644 --- a/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py +++ b/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# -# Copyright (c) 2024 Christophe Dufaza +# Copyright (c) 2024, Christophe Dufaza """Unit tests dedicated to edtlib.Binding objects initialization. @@ -1007,6 +1006,135 @@ def test_bindings_propspecs_consistency() -> None: binding = load_binding("test-bindings-init/diamond.yaml") verify_binding_propspecs_consistency(binding) +def test_binding_propspecs_last_modified() -> None: + """Verify the Last Modified Here semantic of PropertySpec.path. + This test verifies the binding level. + """ + binding = load_binding("test-bindings-init/base_inherit.yaml") + # All properties are inherited without modifications. + assert all( + "base.yaml" == _basename(propspec.path) + for propspec in binding.prop2specs.values() + ) + + binding = load_binding("test-bindings-init/base_amend.yaml") + assert all( + "base.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ( + "prop-default", + "prop-const", + "prop-req", + ) + ) + assert all( + "base_amend.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ( + # Amended. + "prop-1", + "prop-2", + "prop-enum", + # New. + "prop-new", + ) + ) + + binding = load_binding("test-bindings-init/diamond.yaml") + assert "base.yaml" == _basename(binding.prop2specs["prop-default"].path) + assert all( + "thing.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ("prop-1", "prop-thing") + ) + assert all( + "diamond.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ("prop-enum", "prop-diamond") + ) + +def test_child_binding_propspecs_last_modified() -> None: + """Verify the Last Modified Here semantic of PropertySpec.path. + This test verifies the child-binding level. + """ + binding = child_binding_of("test-bindings-init/base_inherit.yaml") + # All properties are inherited without modifications. + assert all( + _basename(propspec.path) == "base.yaml" + for propspec in binding.prop2specs.values() + ) + + binding = child_binding_of("test-bindings-init/base_amend.yaml") + assert all( + _basename(binding.prop2specs[prop].path) == "base.yaml" + for prop in ( + "child-prop-default", + "child-prop-const", + "child-prop-req", + ) + ) + assert all( + _basename(binding.prop2specs[prop].path) == "base_amend.yaml" + for prop in ( + "child-prop-1", + "child-prop-2", + "child-prop-enum", + "child-prop-new", + ) + ) + + binding = child_binding_of("test-bindings-init/diamond.yaml") + assert "base.yaml" == _basename( + binding.prop2specs["child-prop-default"].path + ) + assert all( + "thing.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ("child-prop-1", "child-prop-thing") + ) + assert all( + "diamond.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ("child-prop-enum", "child-prop-diamond") + ) + +def test_grandchild_binding_propspecs_last_modified() -> None: + """Verify the Last Modified Here semantic of PropertySpec.path. + This test verifies the grandchild-binding level. + """ + binding = grandchild_binding_of("test-bindings-init/base_inherit.yaml") + # All properties are inherited without modifications. + assert all( + _basename(propspec.path) == "base.yaml" + for propspec in binding.prop2specs.values() + ) + + binding = grandchild_binding_of("test-bindings-init/base_amend.yaml") + assert all( + _basename(binding.prop2specs[prop].path) == "base.yaml" + for prop in ( + "grandchild-prop-default", + "grandchild-prop-const", + "grandchild-prop-req", + ) + ) + assert all( + _basename(binding.prop2specs[prop].path) == "base_amend.yaml" + for prop in ( + "grandchild-prop-1", + "grandchild-prop-2", + "grandchild-prop-enum", + "grandchild-prop-new", + ) + ) + + binding = grandchild_binding_of("test-bindings-init/diamond.yaml") + assert "base.yaml" == _basename( + binding.prop2specs["grandchild-prop-default"].path + ) + assert all( + "thing.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ("grandchild-prop-1", "grandchild-prop-thing") + ) + assert all( + "diamond.yaml" == _basename(binding.prop2specs[prop].path) + for prop in ("grandchild-prop-enum", "grandchild-prop-diamond") + ) + # Borrowed from test_edtlib.py. @contextlib.contextmanager