python_testing: Add Checks for Command IDs (#36018)
* python_testing: Add Checks for Command IDs
This PR adds checks and unit tests for the command ID ranges defined in Table 79 of the spec.
These checks will be used on the various Python tests, for example,
TC_DeviceBasicComposition.py.
* Restyled by isort
* python_testing: Update command id error msg
This PR uses the newly added command id check.
It updates the error msg to indicate if it's a "Test Vendor" id.
* fix merge conflicts
* Restyled by isort
---------
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/python_testing/TC_DeviceBasicComposition.py b/src/python_testing/TC_DeviceBasicComposition.py
index 59182fa..0f227c2 100644
--- a/src/python_testing/TC_DeviceBasicComposition.py
+++ b/src/python_testing/TC_DeviceBasicComposition.py
@@ -108,7 +108,8 @@
from chip.clusters.ClusterObjects import ClusterAttributeDescriptor, ClusterObjectFieldDescriptor
from chip.interaction_model import InteractionModelError, Status
from chip.testing.basic_composition import BasicCompositionTests
-from chip.testing.global_attribute_ids import AttributeIdType, ClusterIdType, GlobalAttributeIds, attribute_id_type, cluster_id_type
+from chip.testing.global_attribute_ids import (AttributeIdType, ClusterIdType, CommandIdType, GlobalAttributeIds, attribute_id_type,
+ cluster_id_type, command_id_type)
from chip.testing.matter_testing import (AttributePathLocation, ClusterPathLocation, CommandPathLocation, MatterBaseTest, TestStep,
async_test_body, default_matter_test_main)
from chip.testing.taglist_and_topology_test import (create_device_type_list_for_root, create_device_type_lists,
@@ -503,7 +504,7 @@
location = CommandPathLocation(endpoint_id=endpoint_id, cluster_id=cluster_id, command_id=bad_cmd_id)
vendor_id = get_vendor_id(bad_cmd_id)
self.record_error(self.get_test_name(
- ), location=location, problem=f'Command 0x{bad_cmd_id:08x} with bad prefix 0x{vendor_id:04x} in cluster 0x{cluster_id:08x}', spec_location='Manufacturer Extensible Identifier (MEI)')
+ ), location=location, problem=f'Command 0x{bad_cmd_id:08x} with bad prefix 0x{vendor_id:04x} in cluster 0x{cluster_id:08x}' + (' (Test Vendor)' if command_id_type(bad_cmd_id) == CommandIdType.kTest else ''), spec_location='Manufacturer Extensible Identifier (MEI)')
success = False
self.print_step(7, "Validate that none of the MEI global attribute IDs contain values outside of the allowed suffix range")
diff --git a/src/python_testing/TestIdChecks.py b/src/python_testing/TestIdChecks.py
index eda01ef..c86d4bf 100644
--- a/src/python_testing/TestIdChecks.py
+++ b/src/python_testing/TestIdChecks.py
@@ -15,9 +15,9 @@
# limitations under the License.
#
-from chip.testing.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 chip.testing.global_attribute_ids import (AttributeIdType, ClusterIdType, CommandIdType, DeviceTypeIdType, attribute_id_type,
+ cluster_id_type, command_id_type, device_type_id_type, is_valid_attribute_id,
+ is_valid_cluster_id, is_valid_command_id, is_valid_device_type_id)
from chip.testing.matter_testing import MatterBaseTest, default_matter_test_main
from mobly import asserts
@@ -210,6 +210,71 @@
for id in prefix_bad:
check_all_bad(id)
+ def test_command_ids(self):
+ standard_global_good = [0x0000_00E0, 0x0000_00FF, 0x0000_00E1, 0x0000_00FE]
+ standard_global_bad = [0x0000_01E0, 0x0000_0FFF, 0x0000_AAE1, 0x0000_BBFE, 0x0000_FFFF]
+ scoped_non_global_good = [0x0000_0000, 0x0000_00DF, 0x0000_0001]
+ scoped_non_global_bad = [0x0000_0F00, 0x0000_01DF, 0x0000_0F01]
+ manufacturer_good = [0x0001_0000, 0x0001_00FF, 0xFFF0_0000, 0xFFF0_00FF, 0x0001_00FE]
+ manufacturer_bad = [0x0001_0A00, 0x0001_0BFF, 0x0001_FFFF, 0xFFF0_0C00, 0xFFF0_D0FF, 0x0001_F0FE]
+ test_good = [0xFFF1_0000, 0xFFF1_00E0, 0xFFF1_00FF, 0xFFF4_0000, 0xFFF4_00E0, 0xFFF4_00FF]
+ test_bad = [0xFFF1_5000, 0xFFF1_F000, 0xFFF1_FFFF, 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 = command_id_type(id)
+ msg = f"Incorrect command range assessment, expecting standard global {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, CommandIdType.kStandardGlobal, msg)
+ asserts.assert_true(is_valid_command_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_command_id(id_type, allow_test=False), msg)
+
+ def check_scoped_non_global(id):
+ id_type = command_id_type(id)
+ msg = f"Incorrect command range assessment, expecting scoped non-global {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, CommandIdType.kScopedNonGlobal, msg)
+ asserts.assert_true(is_valid_command_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_command_id(id_type, allow_test=False), msg)
+
+ def check_manufacturer(id):
+ id_type = command_id_type(id)
+ msg = f"Incorrect command range assessment, expecting manufacturer {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, CommandIdType.kManufacturer, msg)
+ asserts.assert_true(is_valid_command_id(id_type, allow_test=True), msg)
+ asserts.assert_true(is_valid_command_id(id_type, allow_test=False), msg)
+
+ def check_test(id):
+ id_type = command_id_type(id)
+ msg = f"Incorrect command range assessment, expecting test {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, CommandIdType.kTest, msg)
+ asserts.assert_true(is_valid_command_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_command_id(id_type, allow_test=False), msg)
+
+ def check_all_bad(id):
+ id_type = command_id_type(id)
+ msg = f"Incorrect command range assessment, expecting invalid {id:08x}, type = {id_type}"
+ asserts.assert_equal(id_type, CommandIdType.kInvalid, msg)
+ asserts.assert_false(is_valid_command_id(id_type, allow_test=True), msg)
+ asserts.assert_false(is_valid_command_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 scoped_non_global_good:
+ check_scoped_non_global(id)
+ for id in scoped_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/matter_testing_infrastructure/chip/testing/global_attribute_ids.py b/src/python_testing/matter_testing_infrastructure/chip/testing/global_attribute_ids.py
index e692adf..f9fbd2e 100644
--- a/src/python_testing/matter_testing_infrastructure/chip/testing/global_attribute_ids.py
+++ b/src/python_testing/matter_testing_infrastructure/chip/testing/global_attribute_ids.py
@@ -61,6 +61,15 @@
kManufacturer = auto(),
kTest = auto(),
+
+class CommandIdType(Enum):
+ kInvalid = auto()
+ kStandardGlobal = auto(),
+ kScopedNonGlobal = 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.
@@ -92,6 +101,9 @@
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)
+COMMAND_ID_GLOBAL_STANDARD_SUFFIX = SuffixIdRange(0x00E0, 0x00FF)
+COMMAND_ID_NON_GLOBAL_SCOPED_SUFFIX = SuffixIdRange(0x0000, 0x00DF)
+COMMAND_ID_SUFFIX = SuffixIdRange(0x0000, 0x00FF)
def device_type_id_type(id: int) -> DeviceTypeIdType:
@@ -145,3 +157,22 @@
if allow_test:
valid.append(AttributeIdType.kTest)
return id_type in valid
+
+
+def command_id_type(id: int) -> CommandIdType:
+ if id in STANDARD_PREFIX and id in COMMAND_ID_GLOBAL_STANDARD_SUFFIX:
+ return CommandIdType.kStandardGlobal
+ if id in STANDARD_PREFIX and id in COMMAND_ID_NON_GLOBAL_SCOPED_SUFFIX:
+ return CommandIdType.kScopedNonGlobal
+ if id in MANUFACTURER_PREFIX and id in COMMAND_ID_SUFFIX:
+ return CommandIdType.kManufacturer
+ if id in TEST_PREFIX and id in COMMAND_ID_SUFFIX:
+ return CommandIdType.kTest
+ return CommandIdType.kInvalid
+
+
+def is_valid_command_id(id_type: CommandIdType, allow_test: bool = False):
+ valid = [CommandIdType.kStandardGlobal, CommandIdType.kScopedNonGlobal, CommandIdType.kManufacturer]
+ if allow_test:
+ valid.append(CommandIdType.kTest)
+ return id_type in valid