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 00000000000000..7535b28b544595 --- /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 00000000000000..bd24de099c83db --- /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 00000000000000..eec7711c7a4137 --- /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 00000000000000..c713f1b8d3a66a --- /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/compat_desc.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc.yaml new file mode 100644 index 00000000000000..1bd5bce1c81e42 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc.yaml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Test inheritance rules applied to compatible strings +# and bindings descriptions. + +description: Binding description. + +compatible: vnd,compat-desc + +include: compat_desc_base.yaml + +child-binding: + description: Child-binding description. + compatible: vnd,child-compat-desc + + child-binding: + description: Grandchild-binding description. + compatible: vnd,grandchild-compat-desc diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc_base.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc_base.yaml new file mode 100644 index 00000000000000..263b5adcbb9c3a --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc_base.yaml @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Base file for testing inheritance rules applied to +# compatible strings and bindings descriptions. + +description: Binding description (base). + +compatible: vnd,compat-desc-base + +child-binding: + description: Child-binding description (base). + compatible: vnd,child-compat-desc-base + + child-binding: + description: Grandchild-binding description (base). + compatible: vnd,grandchild-compat-desc-base diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc_multi.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc_multi.yaml new file mode 100644 index 00000000000000..c729875d02bc9f --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/compat_desc_multi.yaml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# Test consequences of inclusion order on inherited +# compatible strings and descriptions. + +description: Binding description (multi). + +compatible: vnd,compat-desc-multi + +# Descriptions at the child-binding level and bellow +# will depend on inclusion order: the first wins. +include: + - compat_desc_base.yaml + - compat_desc.yaml 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 00000000000000..0a3865506eaa64 --- /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/filter_allows_notblocked.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/filter_allows_notblocked.yaml new file mode 100644 index 00000000000000..1af2b2d3463646 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/filter_allows_notblocked.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding file allows a property which is not blocked +# in simple_blocklist.yaml: we should end up with this property. + +include: + - name: simple_blocklist.yaml + property-allowlist: [prop-2] + child-binding: + property-allowlist: [child-prop-2] + child-binding: + property-allowlist: [grandchild-prop-2] diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/filter_among_allowed.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/filter_among_allowed.yaml new file mode 100644 index 00000000000000..3faa647926bd98 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/filter_among_allowed.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding file allows only properties that are not allowed +# in simple_allowlist.yaml: we should end up with no property at all. + +include: + - name: simple_allowlist.yaml + property-allowlist: [prop-3] + child-binding: + property-allowlist: [child-prop-3] + child-binding: + property-allowlist: [grandchild-prop-3] diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/filter_among_notblocked.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/filter_among_notblocked.yaml new file mode 100644 index 00000000000000..873f762ed1d5bc --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/filter_among_notblocked.yaml @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: BSD-3-Clause +# +# This binding file blocks all properties not blocked +# in simple_filter.yaml: we should end up with no property at all. + +include: + - name: simple_blocklist.yaml + property-blocklist: [prop-2, prop-3] + child-binding: + property-blocklist: [child-prop-2, child-prop-3] + child-binding: + property-blocklist: [grandchild-prop-2, grandchild-prop-3] 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 00000000000000..20739bad14aab0 --- /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 00000000000000..fd1ec813612590 --- /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 00000000000000..73947b3007e9cc --- /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 00000000000000..298a3624ffb53f --- /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 00000000000000..8f80ec81e38553 --- /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 00000000000000..5b8a1cb0d51785 --- /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 00000000000000..f2f69d31e4b7eb --- /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 00000000000000..637ba811e3dfb7 --- /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 00000000000000..dc9273a7adc5fa --- /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 00000000000000..4135db67f1d155 --- /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 00000000000000..9eaa11e31d5492 --- /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 00000000000000..8b621a24098f16 --- /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 00000000000000..38839826536c74 --- /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 00000000000000..fd0412c505be0f --- /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 00000000000000..fca2c752676e32 --- /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 00000000000000..27f6ce8120d91c --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/simple.yaml @@ -0,0 +1,30 @@ +# 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 + prop-3: + type: int + +child-binding: + properties: + child-prop-1: + type: int + child-prop-2: + type: int + child-prop-3: + type: int + + child-binding: + properties: + grandchild-prop-1: + type: int + grandchild-prop-2: + type: int + grandchild-prop-3: + type: int diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/simple_allowlist.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_allowlist.yaml new file mode 100644 index 00000000000000..7294393a2a2e1c --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_allowlist.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, prop-2] + child-binding: + property-allowlist: [child-prop-1, child-prop-2] + child-binding: + property-allowlist: [grandchild-prop-1, grandchild-prop-2] diff --git a/scripts/dts/python-devicetree/tests/test-bindings-init/simple_blocklist.yaml b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_blocklist.yaml new file mode 100644 index 00000000000000..c9d581e2c077d5 --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test-bindings-init/simple_blocklist.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-blocklist: [prop-1] + child-binding: + property-blocklist: [child-prop-1] + child-binding: + property-blocklist: [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 00000000000000..8a95ef38f95115 --- /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 00000000000000..82a31aaa391173 --- /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 00000000000000..30015716a7d3d7 --- /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 +# descriptions 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 00000000000000..d4ded2965c34ac --- /dev/null +++ b/scripts/dts/python-devicetree/tests/test_edtlib_binding_init.py @@ -0,0 +1,1160 @@ +# 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 +- check the rules applied when overwriting a binding's description + or compatible string (at the binding, child-binding, etc, levels) + +At any level, an including binding is permitted to: +- filter the properties it chooses to inherit with either "property:allowlist" + or "property:blocklist" but not both +- extend inherited properties: + - override (implicit or) explicit "required: false" with "required: true" + - add constraints to the possible value(s) of a property with "const:" + or "enum:" + - add a "default:" value to a property + - add or overwrite a property's "description:"; when overwritten multiple + times by several included binding files, "include:"ed first "wins" +- define new properties + +At any level, an including binding is NOT permitted to: +- remove a requirement by overriding "required: true" with "required: false" +- change existing constrains applied to the possible values of + an inherited property with "const:" or "enum:" +- change the "default:" value of an inherited property +- change the "type:" of an inherited property + +Rules applying to bindings' descriptions and compatible strings: +- included files can't overwrite the description or compatible string set + by the including binding (despite that "description:" appears before + "include:"): this seems consistent, the top-level binding file "wins" +- an including binding can overwrite descriptions and compatible strings + inherited at the child-binding levels: this seems consistent, + the top-level binding file "wins" +- when we include multiple files overwriting a description or compatible + string inherited at the child-binding levels, order of inclusion matters, + the first "wins"; this is consistent with property descriptions + +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). +""" + +# pylint: disable=too-many-statements + +import contextlib +import os +from collections.abc import Generator +from typing import Any + +import pytest +from devicetree import edtlib + +YAML_KNOWN_BASE_BINDINGS: 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 when "A includes B includes C". + "simple.yaml": "test-bindings-init/simple.yaml", + "simple_inherit.yaml": "test-bindings-init/simple_inherit.yaml", + "simple_allowlist.yaml": "test-bindings-init/simple_allowlist.yaml", + "simple_blocklist.yaml": "test-bindings-init/simple_blocklist.yaml", + # Test applied rules for compatible strings and descriptions. + "compat_desc_base.yaml": "test-bindings-init/compat_desc_base.yaml", + "compat_desc.yaml": "test-bindings-init/compat_desc.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_KNOWN_BASE_BINDINGS, + raw=None, + require_compatible=False, + require_description=False, + ) + 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: str | None = None, + expect_enum: list[int | str] | None = None, + expect_const: Any | None = None, + expect_default: Any | None = 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 expected 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 we do not assume here. + """ + if binding.prop2specs: + assert set(binding.raw["properties"].keys()) == set(binding.prop2specs.keys()) + assert 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, + # Included first wins. + expect_desc="Overwritten description.", + # From thing.yaml. + expect_default=1, + ) + 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, + # Included first wins. + expect_desc="Overwritten description (child).", + # From thing.yaml. + expect_default=2, + ) + + 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, + # Included first wins. + expect_desc="Overwritten description (grandchild).", + # From thing.yaml. + expect_default=3, + ) + + 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_description_overwrite() -> None: + """Test whether we can overwrite a binding's description. + + Included files can't overwrite the description set by the including binding + (despite that "description:" appears before "include:"). + + This seems consistent: the top-level binding file "wins". + """ + binding = load_binding("test-bindings-init/compat_desc.yaml") + assert binding.description == "Binding description." + + binding = load_binding("test-bindings-init/compat_desc_multi.yaml") + assert binding.description == "Binding description (multi)." + + +def test_binding_compat_overwrite() -> None: + """Test whether we can overwrite a binding's compatible string. + + Included files can't overwrite the compatible string set by the + including binding (despite that "compatible:" appears before "include:"). + + This seems consistent: the top-level binding file "wins". + """ + binding = load_binding("test-bindings-init/compat_desc.yaml") + assert binding.compatible == "vnd,compat-desc" + + binding = load_binding("test-bindings-init/compat_desc_multi.yaml") + assert binding.compatible == "vnd,compat-desc-multi" + + +def test_child_binding_description_overwrite() -> None: + """Test whether we can overwrite a child-binding's description. + + An including binding can overwrite an inherited child-binding's description. + + When we include multiple files overwriting the description + at the child-binding level, the first "wins". + """ + child_binding = child_binding_of("test-bindings-init/compat_desc.yaml") + # Overwrite inherited description. + assert child_binding.description == "Child-binding description." + + child_binding = child_binding_of("test-bindings-init/compat_desc_multi.yaml") + # When inherited multiple times, the first "description:" wins. + assert child_binding.description == "Child-binding description (base)." + + +def test_child_binding_compat_overwrite() -> None: + """Test whether we can overwrite a child-binding's compatible string. + + An including binding can overwrite an inherited child-binding's + compatible string. + + When we include multiple files overwriting the compatible string + at the child-binding level, the first "wins". + """ + child_binding = child_binding_of("test-bindings-init/compat_desc.yaml") + # Overwrite inherited description. + assert child_binding.compatible == "vnd,child-compat-desc" + + child_binding = child_binding_of("test-bindings-init/compat_desc_multi.yaml") + # When inherited multiple times, the first "compatible:" wins. + assert child_binding.compatible == "vnd,child-compat-desc-base" + + +def test_grandchild_binding_description_overwrite() -> None: + """Test whether we can overwrite a grandchild-binding's description. + + See also: test_child_binding_description_overwrite() + """ + grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc.yaml") + assert grandchild_binding.description == "Grandchild-binding description." + + grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc_multi.yaml") + assert grandchild_binding.description == "Grandchild-binding description (base)." + + +def test_grandchild_binding_compat_overwrite() -> None: + """Test whether we can overwrite a grandchild-binding's compatible string. + + See also: test_child_binding_compat_overwrite() + """ + grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc.yaml") + # Overwrite inherited description. + assert grandchild_binding.compatible == "vnd,grandchild-compat-desc" + + grandchild_binding = grandchild_binding_of("test-bindings-init/compat_desc_multi.yaml") + # When inherited multiple times, the first "compatible:" wins. + assert grandchild_binding.compatible == "vnd,grandchild-compat-desc-base" + + +def test_filter_inherited_propspecs_basics() -> None: + """Test the basics of filtering properties inherited via an intermediary + binding file. + + Use-case "B filters I includes X": + - X is a base binding file, specifying common properties + - I is an intermediary binding file, which includes X without modification + nor filter + - B includes I, filtering the properties it chooses to inherit + with an allowlist or a blocklist + + Checks, up to the grandchild-binding level, that the properties inherited + from X via I are actually filtered as B intends to. + """ + # Binding level. + binding = load_binding("test-bindings-init/simple_allowlist.yaml") + # Allowed properties. + assert {"prop-1", "prop-2"} == set(binding.prop2specs.keys()) + binding = load_binding("test-bindings-init/simple_blocklist.yaml") + # Non blocked properties. + assert {"prop-2", "prop-3"} == set(binding.prop2specs.keys()) + + # Child-binding level. + child_binding = child_binding_of("test-bindings-init/simple_allowlist.yaml") + # Allowed properties. + assert {"child-prop-1", "child-prop-2"} == set(child_binding.prop2specs.keys()) + child_binding = child_binding_of("test-bindings-init/simple_blocklist.yaml") + # Non blocked properties. + assert {"child-prop-2", "child-prop-3"} == set(child_binding.prop2specs.keys()) + + # GrandChild-binding level. + grandchild_binding = grandchild_binding_of("test-bindings-init/simple_allowlist.yaml") + # Allowed properties. + assert {"grandchild-prop-1", "grandchild-prop-2"} == set(grandchild_binding.prop2specs.keys()) + grandchild_binding = grandchild_binding_of("test-bindings-init/simple_blocklist.yaml") + # Non blocked properties. + assert {"grandchild-prop-2", "grandchild-prop-3"} == set(grandchild_binding.prop2specs.keys()) + + +def test_filter_inherited_propspecs_among_allowed() -> None: + """Test filtering properties which have been allowed by an intermediary + binding file. + + Complementary to test_filter_inherited_propspecs_basics(). + + Use-case "B filters I filters X": + - X is a base binding file, specifying common properties + - I is an intermediary binding file, filtering the properties specified + in X with an allowlist + - B includes I, filtering the properties it chooses to inherit + also with an allowlist + + Checks, up to the grandchild-binding level, that B inherits the properties + specified in X which are first allowed in I, then also allowed in B. + + For that, we check that if B allows only properties that are not allowed in I, + we then end up with no property at all. + """ + binding = load_binding("test-bindings-init/filter_among_allowed.yaml") + assert not set(binding.prop2specs.keys()) + assert binding.child_binding + child_binding = binding.child_binding + assert not set(child_binding.prop2specs.keys()) + assert child_binding.child_binding + grandchild_binding = child_binding.child_binding + assert not set(grandchild_binding.prop2specs.keys()) + + +def test_filter_inherited_propspecs_among_notblocked() -> None: + """Test filtering properties which have not been blocked by an intermediary + binding file. + + Complementary to test_filter_inherited_propspecs_basics(). + + Use-case "B filters I filters X": + - X is a base binding file, specifying common properties + - I is an intermediary binding file, filtering the properties specified + in X with a blocklist + - B includes I, filtering the properties it chooses to inherit + also with a blocklist + + Checks, up to the grandchild-binding level, that B inherits the properties + specified in X which are not blocked in I, then neither blocked in B. + + For that, we check that if B blocks all properties that are not blocked in I, + we then end up with no property at all. + """ + binding = load_binding("test-bindings-init/filter_among_notblocked.yaml") + assert not set(binding.prop2specs.keys()) + assert binding.child_binding + child_binding = binding.child_binding + assert not set(child_binding.prop2specs.keys()) + assert child_binding.child_binding + grandchild_binding = child_binding.child_binding + assert not set(grandchild_binding.prop2specs.keys()) + + +def test_filter_inherited_propspecs_allows_notblocked() -> None: + """Test allowing properties which have not been blocked by an intermediary + binding file. + + Complementary to test_filter_inherited_propspecs_basics(). + + Use-case "B filters I filters X": + - X is a base binding file, specifying common properties + - I is an intermediary binding file, filtering the properties specified + in X with a blocklist + - B includes I, filtering the properties it chooses to inherit + also with an allowlist + + Checks, up to the grandchild-binding level, that B inherits the properties + specified in X which are not blocked in I, then allowed in B. + + Permits to verify we can apply both "property-allowlist" + and "property-blocklist" filters to the same file, + as long as they're not applied simultaneously + (i.e. within the same YAML "include:" map). + """ + binding = load_binding("test-bindings-init/filter_allows_notblocked.yaml") + assert {"prop-2"} == set(binding.prop2specs.keys()) + assert binding.child_binding + child_binding = binding.child_binding + assert {"child-prop-2"} == set(child_binding.prop2specs.keys()) + assert child_binding.child_binding + grandchild_binding = child_binding.child_binding + assert {"grandchild-prop-2"} == set(grandchild_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: str | None) -> str: + return os.path.basename(path or "?")