Python testing: id range check functions (#33510)
* Python testing: id range check functions
* add test to workflow
* use range more directly
* change to enums
* isort
* fix workflow
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 8b3588c..678b7b1 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -561,6 +561,8 @@
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-rvc-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-rvc-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_file json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_RVCOPSTATE_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS examples/rvc-app/rvc-common/pics/rvc-app-pics-values --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"'
scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_DA_1_2.py'
scripts/run_in_python_env.sh out/venv './src/python_testing/test_testing/test_TC_ICDM_2_1.py'
+ scripts/run_in_python_env.sh out/venv 'python3 ./src/python_testing/TestIdChecks.py'
+
- name: Uploading core files
uses: actions/upload-artifact@v4
if: ${{ failure() && !env.ACT }}
diff --git a/src/python_testing/TestIdChecks.py b/src/python_testing/TestIdChecks.py
new file mode 100644
index 0000000..8969807
--- /dev/null
+++ b/src/python_testing/TestIdChecks.py
@@ -0,0 +1,214 @@
+#
+# Copyright (c) 2024 Project CHIP Authors
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from global_attribute_ids import (AttributeIdType, ClusterIdType, DeviceTypeIdType, attribute_id_type, cluster_id_type,
+ device_type_id_type, is_valid_attribute_id, is_valid_cluster_id, is_valid_device_type_id)
+from matter_testing_support import MatterBaseTest, default_matter_test_main
+from mobly import asserts
+
+
+class TestIdChecks(MatterBaseTest):
+ def test_device_type_ids(self):
+ standard_good = [0x0000_0000, 0x0000_BFFF]
+ standard_bad = [0x0000_C000]
+
+ manufacturer_good = [0x0001_0000, 0x0001_BFFF, 0xFFF0_0000, 0xFFF0_BFFF]
+ manufacturer_bad = [0x0001_C000, 0xFFF0_C000]
+
+ test_good = [0xFFF1_0000, 0xFFF1_BFFF, 0xFFF4_0000, 0xFFF4_BFFF]
+ test_bad = [0xFFF1_C000, 0xFFF4_C000]
+
+ prefix_bad = [0xFFF5_0000, 0xFFF5_BFFFF, 0xFFF5_C000]
+
+ def check_standard(id):
+ id_type = device_type_id_type(id)
+ msg = f"Incorrect device type range assessment, expecting standard {id:08x}, type = {id_type}"
+ asserts.assert_equal(device_type_id_type(id), DeviceTypeIdType.kStandard, msg)
+ asserts.assert_true(is_valid_device_type_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_device_type_id(id_type, allow_test=False), msg)
+
+ def check_manufacturer(id):
+ id_type = device_type_id_type(id)
+ msg = f"Incorrect device type range assessment, expecting manufacturer {id:08x}, type = {id_type}"
+ asserts.assert_equal(device_type_id_type(id), DeviceTypeIdType.kManufacturer, msg)
+ asserts.assert_true(is_valid_device_type_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_device_type_id(id_type, allow_test=False), msg)
+
+ def check_test(id):
+ id_type = device_type_id_type(id)
+ msg = f"Incorrect device type range assessment, expecting test {id:08x}, type = {id_type}"
+ asserts.assert_equal(device_type_id_type(id), DeviceTypeIdType.kTest, msg)
+ asserts.assert_true(is_valid_device_type_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_device_type_id(id_type, allow_test=False), msg)
+
+ def check_all_bad(id):
+ id_type = device_type_id_type(id)
+ msg = f"Incorrect device type range assessment, expecting invalid {id:08x}, type = {id_type}"
+ asserts.assert_equal(device_type_id_type(id), DeviceTypeIdType.kInvalid, msg)
+ asserts.assert_false(is_valid_device_type_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_device_type_id(id_type, allow_test=False), msg)
+
+ for id in standard_good:
+ check_standard(id)
+
+ for id in standard_bad:
+ check_all_bad(id)
+
+ for id in manufacturer_good:
+ check_manufacturer(id)
+
+ for id in manufacturer_bad:
+ check_all_bad(id)
+
+ for id in test_good:
+ check_test(id)
+
+ for id in test_bad:
+ check_all_bad(id)
+
+ for id in prefix_bad:
+ check_all_bad(id)
+
+ def test_cluster_ids(self):
+ standard_good = [0x0000_0000, 0x0000_7FFF]
+ standard_bad = [0x0000_8000]
+
+ manufacturer_good = [0x0001_FC00, 0x0001_FFFE, 0xFFF0_FC00, 0xFFF0_FFFE]
+ manufacturer_bad = [0x0001_0000, 0x0001_7FFF, 0x0001_FFFF, 0xFFF0_0000, 0xFFF0_7FFF, 0xFFF0_FFFF]
+
+ test_good = [0xFFF1_FC00, 0xFFF1_FFFE, 0xFFF4_FC00, 0xFFF4_FFFE]
+ test_bad = [0xFFF1_0000, 0xFFF1_7FFF, 0xFFF1_FFFF, 0xFFF4_0000, 0xFFF4_7FFF, 0xFFF4_FFFF]
+
+ prefix_bad = [0xFFF5_0000, 0xFFF5_FC00, 0xFFF5_FFFF]
+
+ def check_standard(id):
+ id_type = cluster_id_type(id)
+ msg = f"Incorrect cluster range assessment, expecting standard {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, ClusterIdType.kStandard, msg)
+ asserts.assert_true(is_valid_cluster_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_cluster_id(id_type, allow_test=False), msg)
+
+ def check_manufacturer(id):
+ id_type = cluster_id_type(id)
+ msg = f"Incorrect cluster range assessment, expecting manufacturer {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, ClusterIdType.kManufacturer, msg)
+ asserts.assert_true(is_valid_cluster_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_cluster_id(id_type, allow_test=False), msg)
+
+ def check_test(id):
+ id_type = cluster_id_type(id)
+ msg = f"Incorrect cluster range assessment, expecting test {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, ClusterIdType.kTest, msg)
+ asserts.assert_true(is_valid_cluster_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_cluster_id(id_type, allow_test=False), msg)
+
+ def check_all_bad(id):
+ id_type = cluster_id_type(id)
+ msg = f"Incorrect cluster range assessment, expecting invalid {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, ClusterIdType.kInvalid, msg)
+ asserts.assert_false(is_valid_cluster_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_cluster_id(id_type, allow_test=False), msg)
+
+ for id in standard_good:
+ check_standard(id)
+
+ for id in standard_bad:
+ check_all_bad(id)
+
+ for id in manufacturer_good:
+ check_manufacturer(id)
+
+ for id in manufacturer_bad:
+ check_all_bad(id)
+
+ for id in test_good:
+ check_test(id)
+
+ for id in test_bad:
+ check_all_bad(id)
+
+ for id in prefix_bad:
+ check_all_bad(id)
+
+ def test_attribute_ids(self):
+ standard_global_good = [0x0000_F000, 0x0000_FFFE]
+ standard_global_bad = [0x0000_FFFF]
+ standard_non_global_good = [0x0000_0000, 0x0000_4FFF]
+ standard_non_global_bad = [0x0000_5000]
+ manufacturer_good = [0x0001_0000, 0x0001_4FFF, 0xFFF0_0000, 0xFFF0_4FFF]
+ manufacturer_bad = [0x0001_5000, 0x0001_F000, 0x0001_FFFFF, 0xFFF0_5000, 0xFFF0_F000, 0xFFF0_FFFF]
+ test_good = [0xFFF1_0000, 0xFFF1_4FFF, 0xFFF4_0000, 0xFFF4_4FFF]
+ test_bad = [0xFFF1_5000, 0xFFF1_F000, 0xFFF1_FFFFF, 0xFFF4_5000, 0xFFF4_F000, 0xFFF4_FFFF]
+ prefix_bad = [0xFFF5_0000, 0xFFF5_4FFF, 0xFFF5_5000, 0xFFF5_F000, 0xFFF5_FFFF]
+
+ def check_standard_global(id):
+ id_type = attribute_id_type(id)
+ msg = f"Incorrect attribute range assessment, expecting standard global {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, AttributeIdType.kStandardGlobal, msg)
+ asserts.assert_true(is_valid_attribute_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_attribute_id(id_type, allow_test=False), msg)
+
+ def check_standard_non_global(id):
+ id_type = attribute_id_type(id)
+ msg = f"Incorrect attribute range assessment, expecting standard non-global {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, AttributeIdType.kStandardNonGlobal, msg)
+ asserts.assert_true(is_valid_attribute_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_attribute_id(id_type, allow_test=False), msg)
+
+ def check_manufacturer(id):
+ id_type = attribute_id_type(id)
+ msg = f"Incorrect attribute range assessment, expecting manufacturer {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, AttributeIdType.kManufacturer, msg)
+ asserts.assert_true(is_valid_attribute_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_attribute_id(id_type, allow_test=False), msg)
+
+ def check_test(id):
+ id_type = attribute_id_type(id)
+ msg = f"Incorrect attribute range assessment, expecting test {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, AttributeIdType.kTest, msg)
+ asserts.assert_true(is_valid_attribute_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_attribute_id(id_type, allow_test=False), msg)
+
+ def check_all_bad(id):
+ id_type = attribute_id_type(id)
+ msg = f"Incorrect attribute range assessment, expecting invalid {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, AttributeIdType.kInvalid, msg)
+ asserts.assert_false(is_valid_attribute_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_attribute_id(id_type, allow_test=False), msg)
+
+ for id in standard_global_good:
+ check_standard_global(id)
+ for id in standard_global_bad:
+ check_all_bad(id)
+ for id in standard_non_global_good:
+ check_standard_non_global(id)
+ for id in standard_non_global_bad:
+ check_all_bad(id)
+ for id in manufacturer_good:
+ check_manufacturer(id)
+ for id in manufacturer_bad:
+ check_all_bad(id)
+ for id in test_good:
+ check_test(id)
+ for id in test_bad:
+ check_all_bad(id)
+ for id in prefix_bad:
+ check_all_bad(id)
+
+
+if __name__ == "__main__":
+ default_matter_test_main()
diff --git a/src/python_testing/global_attribute_ids.py b/src/python_testing/global_attribute_ids.py
index 851148b..e692adf 100644
--- a/src/python_testing/global_attribute_ids.py
+++ b/src/python_testing/global_attribute_ids.py
@@ -17,7 +17,7 @@
# This file should be removed once we have a good way to get this from the codegen or XML
-from enum import IntEnum
+from enum import Enum, IntEnum, auto
class GlobalAttributeIds(IntEnum):
@@ -38,3 +38,110 @@
return "FeatureMap"
if self == GlobalAttributeIds.CLUSTER_REVISION_ID:
return "ClusterRevision"
+
+
+class DeviceTypeIdType(Enum):
+ kInvalid = auto(),
+ kStandard = auto(),
+ kManufacturer = auto(),
+ kTest = auto(),
+
+
+class ClusterIdType(Enum):
+ kInvalid = auto()
+ kStandard = auto(),
+ kManufacturer = auto(),
+ kTest = auto(),
+
+
+class AttributeIdType(Enum):
+ kInvalid = auto()
+ kStandardGlobal = auto(),
+ kStandardNonGlobal = auto(),
+ kManufacturer = auto(),
+ kTest = auto(),
+
+# ID helper classes - this allows us to use the values from the prefix and suffix table directly
+# because the class handles the non-inclusive range.
+
+
+class IdRange():
+ def __init__(self, min, max):
+ self.min_max = range(min, max+1)
+
+ def __contains__(self, key):
+ return key in self.min_max
+
+
+class PrefixIdRange(IdRange):
+ def __contains__(self, id: int):
+ return super().__contains__(id >> 16)
+
+
+class SuffixIdRange(IdRange):
+ def __contains__(self, id: int):
+ return super().__contains__(id & 0xFFFF)
+
+
+STANDARD_PREFIX = PrefixIdRange(0x0000, 0x0000)
+MANUFACTURER_PREFIX = PrefixIdRange(0x0001, 0xFFF0)
+TEST_PREFIX = PrefixIdRange(0xFFF1, 0xFFF4)
+
+DEVICE_TYPE_ID_RANGE_SUFFIX = SuffixIdRange(0x0000, 0xBFFF)
+CLUSTER_ID_STANDARD_RANGE_SUFFIX = SuffixIdRange(0x0000, 0x7FFF)
+CLUSTER_ID_MANUFACTURER_RANGE_SUFFIX = SuffixIdRange(0xFC00, 0xFFFE)
+ATTRIBUTE_ID_GLOBAL_RANGE_SUFFIX = SuffixIdRange(0xF000, 0xFFFE)
+ATTRIBUTE_ID_NON_GLOBAL_RANGE_SUFFIX = SuffixIdRange(0x0000, 0x4FFF)
+
+
+def device_type_id_type(id: int) -> DeviceTypeIdType:
+ if id in STANDARD_PREFIX and id in DEVICE_TYPE_ID_RANGE_SUFFIX:
+ return DeviceTypeIdType.kStandard
+ if id in MANUFACTURER_PREFIX and id in DEVICE_TYPE_ID_RANGE_SUFFIX:
+ return DeviceTypeIdType.kManufacturer
+ if id in TEST_PREFIX and id in DEVICE_TYPE_ID_RANGE_SUFFIX:
+ return DeviceTypeIdType.kTest
+ return DeviceTypeIdType.kInvalid
+
+
+def is_valid_device_type_id(id_type: DeviceTypeIdType, allow_test=False) -> bool:
+ valid = [DeviceTypeIdType.kStandard, DeviceTypeIdType.kManufacturer]
+ if allow_test:
+ valid.append(DeviceTypeIdType.kTest)
+ return id_type in valid
+
+
+def cluster_id_type(id: int) -> ClusterIdType:
+ if id in STANDARD_PREFIX and id in CLUSTER_ID_STANDARD_RANGE_SUFFIX:
+ return ClusterIdType.kStandard
+ if id in MANUFACTURER_PREFIX and id in CLUSTER_ID_MANUFACTURER_RANGE_SUFFIX:
+ return ClusterIdType.kManufacturer
+ if id in TEST_PREFIX and id in CLUSTER_ID_MANUFACTURER_RANGE_SUFFIX:
+ return ClusterIdType.kTest
+ return ClusterIdType.kInvalid
+
+
+def is_valid_cluster_id(id_type: ClusterIdType, allow_test: bool = False) -> bool:
+ valid = [ClusterIdType.kStandard, ClusterIdType.kManufacturer]
+ if allow_test:
+ valid.append(ClusterIdType.kTest)
+ return id_type in valid
+
+
+def attribute_id_type(id: int) -> AttributeIdType:
+ if id in STANDARD_PREFIX and id in ATTRIBUTE_ID_NON_GLOBAL_RANGE_SUFFIX:
+ return AttributeIdType.kStandardNonGlobal
+ if id in STANDARD_PREFIX and id in ATTRIBUTE_ID_GLOBAL_RANGE_SUFFIX:
+ return AttributeIdType.kStandardGlobal
+ if id in MANUFACTURER_PREFIX and id in ATTRIBUTE_ID_NON_GLOBAL_RANGE_SUFFIX:
+ return AttributeIdType.kManufacturer
+ if id in TEST_PREFIX and id in ATTRIBUTE_ID_NON_GLOBAL_RANGE_SUFFIX:
+ return AttributeIdType.kTest
+ return AttributeIdType.kInvalid
+
+
+def is_valid_attribute_id(id_type: AttributeIdType, allow_test: bool = False):
+ valid = [AttributeIdType.kStandardGlobal, AttributeIdType.kStandardNonGlobal, AttributeIdType.kManufacturer]
+ if allow_test:
+ valid.append(AttributeIdType.kTest)
+ return id_type in valid