From fbf03c4a6a0a0fc7ac559094a1fc95473b452365 Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Wed, 11 Dec 2024 15:04:45 +0100 Subject: [PATCH 01/11] add dataclass to support PIXIT validations --- .../chip/testing/matter_testing.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index c8a819261ffb0b..1bd01322eed2e5 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -956,6 +956,100 @@ class TestInfo: pics: list[str] +class PIXITType(Enum): + INT = "int" # Maps to the command line flag --int-arg, below fields will be mapped to the corresponding flag + BOOL = "bool" + FLOAT = "float" + STRING = "string" + JSON = "json" + HEX = "hex" + + @property + def arg_flag(self) -> str: + """Maps PIXIT type to command line flag""" + return f"--{self.value}-arg" + + +@dataclass +class PIXITDefinition: + name: str + pixit_type: PIXITType + description: str + required: bool = True + default: Any = None + + @staticmethod + def is_pixit(arg_name: str) -> bool: + """Only validate args starting with PIXIT.""" + return arg_name.startswith("PIXIT.") + + def validate_value(self, value: Any) -> bool: + """Validate PIXIT value matches its declared type""" + if not self.is_pixit(self.name): + return True # Skip validation for non-PIXIT args + + if value is None: + return not self.required + + # Mapping of PIXITType to validation function + type_validators = { + PIXITType.INT: self.validate_int, + PIXITType.BOOL: self.validate_bool, + PIXITType.FLOAT: self.validate_float, + PIXITType.STRING: self.validate_string, + PIXITType.JSON: self.validate_json, + PIXITType.HEX: self.validate_hex, + } + + validator = type_validators.get(self.pixit_type) + if validator: + try: + return validator(value) + except (ValueError, TypeError, json.JSONDecodeError): + return False + else: + # Unknown type, consider validation failed + return False + + def validate_int(self, value: Any) -> bool: + int(value) + return True + + def validate_bool(self, value: Any) -> bool: + # Handle various boolean representations + if isinstance(value, str): + value_lower = value.lower() + if value_lower not in ('true', 'false', '1', '0', 'yes', 'no'): + return False + else: + bool(value) + return True + + def validate_float(self, value: Any) -> bool: + float(value) + return True + + def validate_string(self, value: Any) -> bool: + str(value) + return True + + def validate_json(self, value: Any) -> bool: + if isinstance(value, str): + json.loads(value) + elif not isinstance(value, (dict, list)): + return False + return True + + def validate_hex(self, value: Any) -> bool: + if not isinstance(value, str): + return False + hex_str = value.lower().replace("0x", "") + int(hex_str, 16) + if len(hex_str) % 2 != 0: + return False + return True + + class MatterBaseTest(base_test.BaseTestClass): def __init__(self, *args): super().__init__(*args) From 11d86ab9eb6beb8a4a20b060b0b63fa5286bcf2f Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Wed, 11 Dec 2024 15:18:43 +0100 Subject: [PATCH 02/11] Extend existing MatterTestConfig to handle PIXIT definitions --- .../chip/testing/matter_testing.py | 242 +++++++++++------- 1 file changed, 147 insertions(+), 95 deletions(-) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index 1bd01322eed2e5..a6993ef073d3aa 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -39,7 +39,7 @@ from enum import Enum, IntFlag from functools import partial from itertools import chain -from typing import Any, Iterable, List, Optional, Tuple +from typing import Any, Iterable, List, Optional, Tuple, Dict from chip.tlv import float32, uint @@ -612,6 +612,100 @@ def test_skipped(self, filename: str, name: str): logging.info(f"Skipping test from {filename}: {name}") +class PIXITType(Enum): + INT = "int" + BOOL = "bool" + FLOAT = "float" + STRING = "string" + JSON = "json" + HEX = "hex" + + @property + def arg_flag(self) -> str: + """Maps PIXIT type to command line flag""" + return f"--{self.value}-arg" + + +@dataclass +class PIXITDefinition: + name: str + pixit_type: PIXITType + description: str + required: bool = True + default: Any = None + + @staticmethod + def is_pixit(arg_name: str) -> bool: + """Only validate args starting with PIXIT.""" + return arg_name.startswith("PIXIT.") + + def validate_value(self, value: Any) -> bool: + """Validate PIXIT value matches its declared type""" + if not self.is_pixit(self.name): + return True # Skip validation for non-PIXIT args + + if value is None: + return not self.required + + # Mapping of PIXITType to validation function + type_validators = { + PIXITType.INT: self.validate_int, + PIXITType.BOOL: self.validate_bool, + PIXITType.FLOAT: self.validate_float, + PIXITType.STRING: self.validate_string, + PIXITType.JSON: self.validate_json, + PIXITType.HEX: self.validate_hex, + } + + validator = type_validators.get(self.pixit_type) + if validator: + try: + return validator(value) + except (ValueError, TypeError, json.JSONDecodeError): + return False + else: + # Unknown type, consider validation failed + return False + + def validate_int(self, value: Any) -> bool: + int(value) + return True + + def validate_bool(self, value: Any) -> bool: + # Handle various boolean representations + if isinstance(value, str): + value_lower = value.lower() + if value_lower not in ('true', 'false', '1', '0', 'yes', 'no'): + return False + else: + bool(value) + return True + + def validate_float(self, value: Any) -> bool: + float(value) + return True + + def validate_string(self, value: Any) -> bool: + str(value) + return True + + def validate_json(self, value: Any) -> bool: + if isinstance(value, str): + json.loads(value) + elif not isinstance(value, (dict, list)): + return False + return True + + def validate_hex(self, value: Any) -> bool: + if not isinstance(value, str): + return False + hex_str = value.lower().replace("0x", "") + int(hex_str, 16) + if len(hex_str) % 2 != 0: + return False + return True + + @dataclass class MatterTestConfig: storage_path: pathlib.Path = pathlib.Path(".") @@ -670,6 +764,58 @@ class MatterTestConfig: trace_to: List[str] = field(default_factory=list) + def __post_init__(self): + # Initialize an empty dictionary to store PIXIT definitions + self.pixit_definitions: Dict[str, PIXITDefinition] = {} + + def require_pixit(self, name: str, pixit_type: PIXITType, description: str): + """Register a required PIXIT""" + # Create a PIXITDefinition object for the required PIXIT and store it in pixit_definitions + self.pixit_definitions[name] = PIXITDefinition( + name=name, + pixit_type=pixit_type, + description=description, + required=True + ) + + def optional_pixit(self, name: str, pixit_type: PIXITType, description: str, default: Any = None): + """Register an optional PIXIT""" + # Create a PIXITDefinition object for the optional PIXIT and store it in pixit_definitions + self.pixit_definitions[name] = PIXITDefinition( + name=name, + pixit_type=pixit_type, + description=description, + required=False, + default=default + ) + + def validate_pixits(self) -> Tuple[bool, str]: + """Ensure all required PIXITs are provided with valid values""" + missing = [] + invalid = [] + # Iterate through all PIXIT definitions to check for missing or invalid values + for name, pixit in self.pixit_definitions.items(): + if name not in self.global_test_params: + if pixit.required: + # If a required PIXIT is missing, add it to the missing list + missing.append(f"{name} ({pixit.description})") + else: + value = self.global_test_params[name] + if not pixit.validate_value(value): + # If a PIXIT value is invalid, add it to the invalid list + invalid.append(f"{name}: expected {pixit.pixit_type.value}") + + if missing or invalid: + error = "" + if missing: + # Construct error message for missing PIXITs + error += "\nMissing required PIXITs:\n" + "\n".join(missing) + if invalid: + # Construct error message for invalid PIXIT values + error += "\nInvalid PIXIT values:\n" + "\n".join(invalid) + return False, error + return True, "" + class ClusterMapper: """Describe clusters/attributes using schema names.""" @@ -956,100 +1102,6 @@ class TestInfo: pics: list[str] -class PIXITType(Enum): - INT = "int" # Maps to the command line flag --int-arg, below fields will be mapped to the corresponding flag - BOOL = "bool" - FLOAT = "float" - STRING = "string" - JSON = "json" - HEX = "hex" - - @property - def arg_flag(self) -> str: - """Maps PIXIT type to command line flag""" - return f"--{self.value}-arg" - - -@dataclass -class PIXITDefinition: - name: str - pixit_type: PIXITType - description: str - required: bool = True - default: Any = None - - @staticmethod - def is_pixit(arg_name: str) -> bool: - """Only validate args starting with PIXIT.""" - return arg_name.startswith("PIXIT.") - - def validate_value(self, value: Any) -> bool: - """Validate PIXIT value matches its declared type""" - if not self.is_pixit(self.name): - return True # Skip validation for non-PIXIT args - - if value is None: - return not self.required - - # Mapping of PIXITType to validation function - type_validators = { - PIXITType.INT: self.validate_int, - PIXITType.BOOL: self.validate_bool, - PIXITType.FLOAT: self.validate_float, - PIXITType.STRING: self.validate_string, - PIXITType.JSON: self.validate_json, - PIXITType.HEX: self.validate_hex, - } - - validator = type_validators.get(self.pixit_type) - if validator: - try: - return validator(value) - except (ValueError, TypeError, json.JSONDecodeError): - return False - else: - # Unknown type, consider validation failed - return False - - def validate_int(self, value: Any) -> bool: - int(value) - return True - - def validate_bool(self, value: Any) -> bool: - # Handle various boolean representations - if isinstance(value, str): - value_lower = value.lower() - if value_lower not in ('true', 'false', '1', '0', 'yes', 'no'): - return False - else: - bool(value) - return True - - def validate_float(self, value: Any) -> bool: - float(value) - return True - - def validate_string(self, value: Any) -> bool: - str(value) - return True - - def validate_json(self, value: Any) -> bool: - if isinstance(value, str): - json.loads(value) - elif not isinstance(value, (dict, list)): - return False - return True - - def validate_hex(self, value: Any) -> bool: - if not isinstance(value, str): - return False - hex_str = value.lower().replace("0x", "") - int(hex_str, 16) - if len(hex_str) % 2 != 0: - return False - return True - - class MatterBaseTest(base_test.BaseTestClass): def __init__(self, *args): super().__init__(*args) From e96ec4ba849f3eed75704cf9c0fd0a75347951de Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Thu, 12 Dec 2024 14:53:47 +0100 Subject: [PATCH 03/11] move validation logic to a separate class --- .../chip/testing/matter_testing.py | 183 +++++++++--------- 1 file changed, 96 insertions(+), 87 deletions(-) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index a6993ef073d3aa..13f5344d6926a0 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -628,6 +628,7 @@ def arg_flag(self) -> str: @dataclass class PIXITDefinition: + """Describes a PIXIT requirement for a test""" name: str pixit_type: PIXITType description: str @@ -639,40 +640,17 @@ def is_pixit(arg_name: str) -> bool: """Only validate args starting with PIXIT.""" return arg_name.startswith("PIXIT.") - def validate_value(self, value: Any) -> bool: - """Validate PIXIT value matches its declared type""" - if not self.is_pixit(self.name): - return True # Skip validation for non-PIXIT args - if value is None: - return not self.required +class PIXITValidator: + """Handles validation of PIXIT values against their definitions""" - # Mapping of PIXITType to validation function - type_validators = { - PIXITType.INT: self.validate_int, - PIXITType.BOOL: self.validate_bool, - PIXITType.FLOAT: self.validate_float, - PIXITType.STRING: self.validate_string, - PIXITType.JSON: self.validate_json, - PIXITType.HEX: self.validate_hex, - } - - validator = type_validators.get(self.pixit_type) - if validator: - try: - return validator(value) - except (ValueError, TypeError, json.JSONDecodeError): - return False - else: - # Unknown type, consider validation failed - return False - - def validate_int(self, value: Any) -> bool: + @staticmethod + def validate_int(value: Any) -> bool: int(value) return True - def validate_bool(self, value: Any) -> bool: - # Handle various boolean representations + @staticmethod + def validate_bool(value: Any) -> bool: if isinstance(value, str): value_lower = value.lower() if value_lower not in ('true', 'false', '1', '0', 'yes', 'no'): @@ -681,22 +659,26 @@ def validate_bool(self, value: Any) -> bool: bool(value) return True - def validate_float(self, value: Any) -> bool: + @staticmethod + def validate_float(value: Any) -> bool: float(value) return True - def validate_string(self, value: Any) -> bool: + @staticmethod + def validate_string(value: Any) -> bool: str(value) return True - def validate_json(self, value: Any) -> bool: + @staticmethod + def validate_json(value: Any) -> bool: if isinstance(value, str): json.loads(value) elif not isinstance(value, (dict, list)): return False return True - def validate_hex(self, value: Any) -> bool: + @staticmethod + def validate_hex(value: Any) -> bool: if not isinstance(value, str): return False hex_str = value.lower().replace("0x", "") @@ -705,6 +687,66 @@ def validate_hex(self, value: Any) -> bool: return False return True + @classmethod + def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> tuple[bool, Optional[str]]: + """Validate PIXIT value matches its declared type""" + if not PIXITDefinition.is_pixit(pixit_def.name): + return True, None # Skip validation for non-PIXIT args + + if value is None: + if pixit_def.required: + return False, f"Required PIXIT {pixit_def.name} ({pixit_def.description}) is missing" + return True, None + + # Mapping of PIXITType to validation function + type_validators = { + PIXITType.INT: cls.validate_int, + PIXITType.BOOL: cls.validate_bool, + PIXITType.FLOAT: cls.validate_float, + PIXITType.STRING: cls.validate_string, + PIXITType.JSON: cls.validate_json, + PIXITType.HEX: cls.validate_hex, + } + + validator = type_validators.get(pixit_def.pixit_type) + if not validator: + return False, f"Unknown PIXIT type: {pixit_def.pixit_type}" + + try: + if validator(value): + return True, None + return False, f"Invalid value for {pixit_def.name}: expected {pixit_def.pixit_type.value}" + except (ValueError, TypeError, json.JSONDecodeError): + return False, f"Invalid value for {pixit_def.name}: {value} (expected {pixit_def.pixit_type.value})" + + @classmethod + def validate_pixits(cls, pixits: list[PIXITDefinition], + provided_values: dict[str, Any]) -> tuple[bool, str]: + """Validate all PIXITs against provided values""" + missing = [] + invalid = [] + + for pixit in pixits: + value = provided_values.get(pixit.name) + if value is None: + if pixit.required: + missing.append(f"{pixit.name} ({pixit.description})") + continue + + valid, error = cls.validate_value(value, pixit) + if not valid: + invalid.append(error) + + if missing or invalid: + error = "" + if missing: + error += "\nMissing required PIXITs:\n" + "\n".join(missing) + if invalid: + error += "\nInvalid PIXIT values:\n" + "\n".join(invalid) + return False, error + + return True, "" + @dataclass class MatterTestConfig: @@ -764,58 +806,6 @@ class MatterTestConfig: trace_to: List[str] = field(default_factory=list) - def __post_init__(self): - # Initialize an empty dictionary to store PIXIT definitions - self.pixit_definitions: Dict[str, PIXITDefinition] = {} - - def require_pixit(self, name: str, pixit_type: PIXITType, description: str): - """Register a required PIXIT""" - # Create a PIXITDefinition object for the required PIXIT and store it in pixit_definitions - self.pixit_definitions[name] = PIXITDefinition( - name=name, - pixit_type=pixit_type, - description=description, - required=True - ) - - def optional_pixit(self, name: str, pixit_type: PIXITType, description: str, default: Any = None): - """Register an optional PIXIT""" - # Create a PIXITDefinition object for the optional PIXIT and store it in pixit_definitions - self.pixit_definitions[name] = PIXITDefinition( - name=name, - pixit_type=pixit_type, - description=description, - required=False, - default=default - ) - - def validate_pixits(self) -> Tuple[bool, str]: - """Ensure all required PIXITs are provided with valid values""" - missing = [] - invalid = [] - # Iterate through all PIXIT definitions to check for missing or invalid values - for name, pixit in self.pixit_definitions.items(): - if name not in self.global_test_params: - if pixit.required: - # If a required PIXIT is missing, add it to the missing list - missing.append(f"{name} ({pixit.description})") - else: - value = self.global_test_params[name] - if not pixit.validate_value(value): - # If a PIXIT value is invalid, add it to the invalid list - invalid.append(f"{name}: expected {pixit.pixit_type.value}") - - if missing or invalid: - error = "" - if missing: - # Construct error message for missing PIXITs - error += "\nMissing required PIXITs:\n" + "\n".join(missing) - if invalid: - # Construct error message for invalid PIXIT values - error += "\nInvalid PIXIT values:\n" + "\n".join(invalid) - return False, error - return True, "" - class ClusterMapper: """Describe clusters/attributes using schema names.""" @@ -1100,6 +1090,7 @@ class TestInfo: desc: str steps: list[TestStep] pics: list[str] + pixits: list[PIXITDefinition] class MatterBaseTest(base_test.BaseTestClass): @@ -1112,6 +1103,15 @@ def __init__(self, *args): # The named pipe name must be set in the derived classes self.app_pipe = None + def get_test_pixits(self, test: str) -> list[PIXITDefinition]: + """Get PIXIT definitions for a specific test""" + pixits_name = f'pixits_{test.removeprefix("test_")}' + try: + fn = getattr(self, pixits_name) + return fn() + except AttributeError: + return [] + def get_test_steps(self, test: str) -> list[TestStep]: ''' Retrieves the test step list for the given test @@ -2486,14 +2486,23 @@ def get_test_info(test_class: MatterBaseTest, matter_test_config: MatterTestConf info = [] for t in tests: - info.append(TestInfo(t, steps=base.get_test_steps(t), desc=base.get_test_desc(t), pics=base.get_test_pics(t))) + info.append(TestInfo(t, steps=base.get_test_steps(t), desc=base.get_test_desc(t), + pics=base.get_test_pics(t), pixits=base.get_test_pixits(t))) return info def run_tests_no_exit(test_class: MatterBaseTest, matter_test_config: MatterTestConfig, hooks: TestRunnerHooks, default_controller=None, external_stack=None) -> bool: - get_test_info(test_class, matter_test_config) + # Get test info which now includes PIXITs + test_info = get_test_info(test_class, matter_test_config) + + # Validate PIXITs before proceeding + validator = PIXITValidator() + valid, error = validator.validate_pixits(test_info[0].pixits, matter_test_config.global_test_params) + if not valid: + logging.error(f"PIXIT validation failed: {error}") + return False # Load test config file. test_config = generate_mobly_test_config(matter_test_config) From 93c68378089bb93d78fb56d61b134cf166712f18 Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Fri, 13 Dec 2024 15:14:12 +0100 Subject: [PATCH 04/11] rename validators and adjust value type --- .../chip/testing/matter_testing.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index 13f5344d6926a0..ef51cbf8c87468 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -645,32 +645,32 @@ class PIXITValidator: """Handles validation of PIXIT values against their definitions""" @staticmethod - def validate_int(value: Any) -> bool: + def is_int_pixit_value_valid(value: str) -> bool: int(value) return True @staticmethod - def validate_bool(value: Any) -> bool: + def is_bool_pixit_value_valid(value: str) -> bool: if isinstance(value, str): value_lower = value.lower() - if value_lower not in ('true', 'false', '1', '0', 'yes', 'no'): + if value_lower not in ('true', 'false'): return False else: bool(value) return True @staticmethod - def validate_float(value: Any) -> bool: + def is_float_pixit_value_valid(value: str) -> bool: float(value) return True @staticmethod - def validate_string(value: Any) -> bool: + def is_string_pixit_value_valid(value: str) -> bool: str(value) return True @staticmethod - def validate_json(value: Any) -> bool: + def is_json_pixit_value_valid(value: str) -> bool: if isinstance(value, str): json.loads(value) elif not isinstance(value, (dict, list)): @@ -678,7 +678,7 @@ def validate_json(value: Any) -> bool: return True @staticmethod - def validate_hex(value: Any) -> bool: + def is_hex_pixit_value_valid(value: str) -> bool: if not isinstance(value, str): return False hex_str = value.lower().replace("0x", "") @@ -700,12 +700,12 @@ def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> tuple[bool, O # Mapping of PIXITType to validation function type_validators = { - PIXITType.INT: cls.validate_int, - PIXITType.BOOL: cls.validate_bool, - PIXITType.FLOAT: cls.validate_float, - PIXITType.STRING: cls.validate_string, - PIXITType.JSON: cls.validate_json, - PIXITType.HEX: cls.validate_hex, + PIXITType.INT: cls.is_int_pixit_value_valid, + PIXITType.BOOL: cls.is_bool_pixit_value_valid, + PIXITType.FLOAT: cls.is_float_pixit_value_valid, + PIXITType.STRING: cls.is_string_pixit_value_valid, + PIXITType.JSON: cls.is_json_pixit_value_valid, + PIXITType.HEX: cls.is_hex_pixit_value_valid, } validator = type_validators.get(pixit_def.pixit_type) From d3976d0c4e6fca829199e5b0b28aec7b8ed4c180 Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Mon, 16 Dec 2024 12:03:52 +0100 Subject: [PATCH 05/11] change from boolean checks to handling of exceptions, fix issue where validation would run only for first test --- .../chip/testing/matter_testing.py | 228 ++++++++++++------ 1 file changed, 154 insertions(+), 74 deletions(-) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index ef51cbf8c87468..d284f279408786 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -641,111 +641,192 @@ def is_pixit(arg_name: str) -> bool: return arg_name.startswith("PIXIT.") +class PIXITValidationError(Exception): + """Raised when PIXIT validation fails""" + pass + + class PIXITValidator: """Handles validation of PIXIT values against their definitions""" @staticmethod - def is_int_pixit_value_valid(value: str) -> bool: - int(value) - return True + def validate_int_pixit_value(value: str) -> None: + """Validates that a value can be converted to int. + + Args: + value: Value to validate + + Raises: + ValueError: If value cannot be converted to int + """ + try: + int(value) + except ValueError as e: + raise ValueError(f"Invalid integer value: {e}") @staticmethod - def is_bool_pixit_value_valid(value: str) -> bool: - if isinstance(value, str): - value_lower = value.lower() - if value_lower not in ('true', 'false'): - return False - else: - bool(value) - return True + def validate_bool_pixit_value(value: str) -> None: + """Validates that a value represents a valid boolean. + + Args: + value: Value to validate + + Raises: + ValueError: If value is not a valid boolean representation + """ + try: + if isinstance(value, str): + value_lower = value.lower() + if value_lower not in ('true', 'false'): + raise ValueError(f"String value must be 'true' or 'false', got '{value}'") + else: + bool(value) + except ValueError as e: + raise ValueError(f"Invalid boolean value: {e}") @staticmethod - def is_float_pixit_value_valid(value: str) -> bool: - float(value) - return True + def validate_float_pixit_value(value: str) -> None: + """Validates that a value can be converted to float. + + Args: + value: Value to validate + + Raises: + ValueError: If value cannot be converted to float + """ + try: + float(value) + except ValueError as e: + raise ValueError(f"Invalid float value: {e}") @staticmethod - def is_string_pixit_value_valid(value: str) -> bool: - str(value) - return True + def validate_string_pixit_value(value: str) -> None: + """Validates that a value can be converted to string. + + Args: + value: Value to validate + + Raises: + ValueError: If value cannot be converted to string + """ + try: + str(value) + except ValueError as e: + raise ValueError(f"Invalid string value: {e}") @staticmethod - def is_json_pixit_value_valid(value: str) -> bool: - if isinstance(value, str): + def validate_json_pixit_value(value: str) -> None: + """Validates that a value can be parsed as valid JSON. + + Args: + value: Value to validate + + Raises: + ValueError: If value cannot be parsed as valid JSON + """ + try: json.loads(value) - elif not isinstance(value, (dict, list)): - return False - return True + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON value: {e}") @staticmethod - def is_hex_pixit_value_valid(value: str) -> bool: - if not isinstance(value, str): - return False - hex_str = value.lower().replace("0x", "") - int(hex_str, 16) - if len(hex_str) % 2 != 0: - return False - return True + def validate_hex_pixit_value(value: str) -> None: + """Validates that a value represents valid hexadecimal data. + + Args: + value: Value to validate. Can include optional "0x" prefix. + + Raises: + ValueError: If value is not valid hexadecimal or has odd number of digits + """ + try: + hex_str = value.lower().replace("0x", "") + int(hex_str, 16) # Validate hex format + + if len(hex_str) % 2 != 0: + raise ValueError(f"Hex string must have even number of digits, got {len(hex_str)} digits") + + except (ValueError, TypeError) as e: + raise ValueError(f"Invalid hex value: {e}") @classmethod - def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> tuple[bool, Optional[str]]: - """Validate PIXIT value matches its declared type""" + def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> None]: + """Validate PIXIT value matches its declared type. + + Args: + value: The value to validate + pixit_def: The PIXIT definition containing type and requirements + + Raises: + PIXITValidationError: If validation fails + """ if not PIXITDefinition.is_pixit(pixit_def.name): - return True, None # Skip validation for non-PIXIT args + return # Skip validation for non-PIXIT args if value is None: if pixit_def.required: - return False, f"Required PIXIT {pixit_def.name} ({pixit_def.description}) is missing" - return True, None + raise PIXITValidationError( + f"Required PIXIT {pixit_def.name} ({pixit_def.description}) is missing" + ) + return # Mapping of PIXITType to validation function - type_validators = { - PIXITType.INT: cls.is_int_pixit_value_valid, - PIXITType.BOOL: cls.is_bool_pixit_value_valid, - PIXITType.FLOAT: cls.is_float_pixit_value_valid, - PIXITType.STRING: cls.is_string_pixit_value_valid, - PIXITType.JSON: cls.is_json_pixit_value_valid, - PIXITType.HEX: cls.is_hex_pixit_value_valid, + type_validators= { + PIXITType.INT: cls.validate_int_pixit_value, + PIXITType.BOOL: cls.validate_bool_pixit_value, + PIXITType.FLOAT: cls.validate_float_pixit_value, + PIXITType.STRING: cls.validate_string_pixit_value, + PIXITType.JSON: cls.validate_json_pixit_value, + PIXITType.HEX: cls.validate_hex_pixit_value, } validator = type_validators.get(pixit_def.pixit_type) if not validator: - return False, f"Unknown PIXIT type: {pixit_def.pixit_type}" + raise PIXITValidationError(f"Unknown PIXIT type: {pixit_def.pixit_type}") try: - if validator(value): - return True, None - return False, f"Invalid value for {pixit_def.name}: expected {pixit_def.pixit_type.value}" - except (ValueError, TypeError, json.JSONDecodeError): - return False, f"Invalid value for {pixit_def.name}: {value} (expected {pixit_def.pixit_type.value})" + validator(value) + except (ValueError, TypeError, json.JSONDecodeError) as e: + raise PIXITValidationError( + f"Invalid value for {pixit_def.name}: {value} (expected {pixit_def.pixit_type.value})" + ) from e @classmethod def validate_pixits(cls, pixits: list[PIXITDefinition], - provided_values: dict[str, Any]) -> tuple[bool, str]: - """Validate all PIXITs against provided values""" + provided_values: dict[str, Any]) -> None: + """Validate all PIXITs against provided values. + + Args: + pixits: List of PIXIT definitions to validate + provided_values: Dictionary of provided PIXIT values + + Raises: + PIXITValidationError: If any PIXIT validation fails + """ missing = [] invalid = [] for pixit in pixits: value = provided_values.get(pixit.name) - if value is None: - if pixit.required: - missing.append(f"{pixit.name} ({pixit.description})") + if value is None and pixit.required: + missing.append(f"{pixit.name} ({pixit.description})") continue - - valid, error = cls.validate_value(value, pixit) - if not valid: - invalid.append(error) - + + # Validate non-missing values + if value is not None: + try: + cls.validate_value(value, pixit) + except PIXITValidationError as e: + invalid.append(str(e)) + + # Collect all validation errors if missing or invalid: - error = "" + error_msg = "" if missing: - error += "\nMissing required PIXITs:\n" + "\n".join(missing) + error_msg += "Missing required PIXITs: " + ", ".join(missing) if invalid: - error += "\nInvalid PIXIT values:\n" + "\n".join(invalid) - return False, error - - return True, "" + error_msg += "Invalid PIXIT values: " + ", ".join(invalid) + raise PIXITValidationError(error_msg) @dataclass @@ -1277,8 +1358,15 @@ def setup_test(self): self.step_start_time = datetime.now(timezone.utc) self.step_skipped = False self.failed = False + test_name = self.current_test_info.name + + pixits = self.get_test_pixits(test_name) + validator = PIXITValidator() + valid, error = validator.validate_pixits(pixits, self.matter_test_config.global_test_params) + if not valid: + raise signals.TestFailure(f"PIXIT validation failed for test {test_name}: {error}") + if self.runner_hook and not self.is_commissioning: - test_name = self.current_test_info.name steps = self.get_defined_test_steps(test_name) num_steps = 1 if steps is None else len(steps) filename = inspect.getfile(self.__class__) @@ -2494,15 +2582,7 @@ def get_test_info(test_class: MatterBaseTest, matter_test_config: MatterTestConf def run_tests_no_exit(test_class: MatterBaseTest, matter_test_config: MatterTestConfig, hooks: TestRunnerHooks, default_controller=None, external_stack=None) -> bool: - # Get test info which now includes PIXITs - test_info = get_test_info(test_class, matter_test_config) - - # Validate PIXITs before proceeding - validator = PIXITValidator() - valid, error = validator.validate_pixits(test_info[0].pixits, matter_test_config.global_test_params) - if not valid: - logging.error(f"PIXIT validation failed: {error}") - return False + get_test_info(test_class, matter_test_config) # Load test config file. test_config = generate_mobly_test_config(matter_test_config) From 17c05f9418b397e4e357f330f20919d5b4e9b708 Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Mon, 16 Dec 2024 17:05:55 +0100 Subject: [PATCH 06/11] don't validate pixits for commissioning test, fix typo, reformat --- .../chip/testing/matter_testing.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index d284f279408786..22859137c1cd71 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -750,7 +750,7 @@ def validate_hex_pixit_value(value: str) -> None: raise ValueError(f"Invalid hex value: {e}") @classmethod - def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> None]: + def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> None: """Validate PIXIT value matches its declared type. Args: @@ -771,7 +771,7 @@ def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> None]: return # Mapping of PIXITType to validation function - type_validators= { + type_validators = { PIXITType.INT: cls.validate_int_pixit_value, PIXITType.BOOL: cls.validate_bool_pixit_value, PIXITType.FLOAT: cls.validate_float_pixit_value, @@ -795,11 +795,11 @@ def validate_value(cls, value: Any, pixit_def: PIXITDefinition) -> None]: def validate_pixits(cls, pixits: list[PIXITDefinition], provided_values: dict[str, Any]) -> None: """Validate all PIXITs against provided values. - + Args: pixits: List of PIXIT definitions to validate provided_values: Dictionary of provided PIXIT values - + Raises: PIXITValidationError: If any PIXIT validation fails """ @@ -811,7 +811,7 @@ def validate_pixits(cls, pixits: list[PIXITDefinition], if value is None and pixit.required: missing.append(f"{pixit.name} ({pixit.description})") continue - + # Validate non-missing values if value is not None: try: @@ -1190,7 +1190,7 @@ def get_test_pixits(self, test: str) -> list[PIXITDefinition]: try: fn = getattr(self, pixits_name) return fn() - except AttributeError: + except AttributeError as e: return [] def get_test_steps(self, test: str) -> list[TestStep]: @@ -1360,11 +1360,13 @@ def setup_test(self): self.failed = False test_name = self.current_test_info.name - pixits = self.get_test_pixits(test_name) - validator = PIXITValidator() - valid, error = validator.validate_pixits(pixits, self.matter_test_config.global_test_params) - if not valid: - raise signals.TestFailure(f"PIXIT validation failed for test {test_name}: {error}") + if not self.is_commissioning: + pixits = self.get_test_pixits(test_name) + validator = PIXITValidator() + try: + validator.validate_pixits(pixits, self.matter_test_config.global_test_params) + except PIXITValidationError as e: + raise signals.TestFailure(f"PIXIT validation failed for test {test_name}: {str(e)}") if self.runner_hook and not self.is_commissioning: steps = self.get_defined_test_steps(test_name) From 13021d6f942a6ebf409a5ed4234f00d3cbe46d01 Mon Sep 17 00:00:00 2001 From: Alex Sirko <10052495+asirko-soft@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:26:28 +0100 Subject: [PATCH 07/11] commit review suggestion Co-authored-by: C Freeman --- .../chip/testing/matter_testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index 22859137c1cd71..e9b347fd897e08 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -633,7 +633,7 @@ class PIXITDefinition: pixit_type: PIXITType description: str required: bool = True - default: Any = None + default: Optional[Any] = None @staticmethod def is_pixit(arg_name: str) -> bool: From ef7d4bc39ec6fb26d500cdb02e35f6fd8348c465 Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Mon, 16 Dec 2024 17:40:40 +0100 Subject: [PATCH 08/11] add tests for pixit validator --- .../TestMatterTestingSupport.py | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/src/python_testing/TestMatterTestingSupport.py b/src/python_testing/TestMatterTestingSupport.py index d39c869456e8f9..dc4122035ed663 100644 --- a/src/python_testing/TestMatterTestingSupport.py +++ b/src/python_testing/TestMatterTestingSupport.py @@ -22,7 +22,7 @@ import chip.clusters as Clusters from chip.clusters.Types import Nullable, NullValue -from chip.testing.matter_testing import (MatterBaseTest, async_test_body, compare_time, default_matter_test_main, +from chip.testing.matter_testing import (MatterBaseTest, PIXITDefinition, PIXITType, PIXITValidationError, PIXITValidator, async_test_body, compare_time, default_matter_test_main, get_wait_seconds_from_set_time, parse_matter_test_args, type_matches, utc_time_in_matter_epoch) from chip.testing.pics import parse_pics, parse_pics_xml @@ -656,6 +656,130 @@ def test_parse_matter_test_args(self): asserts.assert_equal(parsed.global_test_params.get("PIXIT.TEST.STR.MULTI.2"), "bar") asserts.assert_equal(parsed.global_test_params.get("PIXIT.TEST.JSON"), {"key": "value"}) + def test_pixit_validation(self): + # Test integer validation + PIXITValidator.validate_int_pixit_value("123") # Should pass + PIXITValidator.validate_int_pixit_value("-123") # Should pass + PIXITValidator.validate_int_pixit_value("0") # Should pass + + try: + PIXITValidator.validate_int_pixit_value("abc") + asserts.fail("Expected ValueError for invalid integer") + except ValueError: + pass + + try: + PIXITValidator.validate_int_pixit_value("12.34") + asserts.fail("Expected ValueError for float value") + except ValueError: + pass + + # Test boolean validation + PIXITValidator.validate_bool_pixit_value("true") # Should pass + PIXITValidator.validate_bool_pixit_value("false") # Should pass + PIXITValidator.validate_bool_pixit_value("True") # Should pass + PIXITValidator.validate_bool_pixit_value("False") # Should pass + + try: + PIXITValidator.validate_bool_pixit_value("yes") + asserts.fail("Expected ValueError for invalid boolean") + except ValueError: + pass + + try: + PIXITValidator.validate_bool_pixit_value("1") + asserts.fail("Expected ValueError for numeric boolean") + except ValueError: + pass + + # Test float validation + PIXITValidator.validate_float_pixit_value("123.45") # Should pass + PIXITValidator.validate_float_pixit_value("-123.45") # Should pass + PIXITValidator.validate_float_pixit_value("0.0") # Should pass + + try: + PIXITValidator.validate_float_pixit_value("abc") + asserts.fail("Expected ValueError for invalid float") + except ValueError: + pass + + # Test string validation - should accept most inputs + PIXITValidator.validate_string_pixit_value("abc") # Should pass + PIXITValidator.validate_string_pixit_value("123") # Should pass + + # Test JSON validation + PIXITValidator.validate_json_pixit_value('{"key": "value"}') # Should pass + PIXITValidator.validate_json_pixit_value('[]') # Should pass + PIXITValidator.validate_json_pixit_value('null') # Should pass + + try: + PIXITValidator.validate_json_pixit_value('{invalid json}') + asserts.fail("Expected ValueError for invalid JSON") + except ValueError: + pass + + try: + PIXITValidator.validate_json_pixit_value('{"unclosed": "object"') + asserts.fail("Expected ValueError for malformed JSON") + except ValueError: + pass + + # Test hex validation + PIXITValidator.validate_hex_pixit_value("0x1234") # Should pass + PIXITValidator.validate_hex_pixit_value("1234") # Should pass + PIXITValidator.validate_hex_pixit_value("ABCD") # Should pass + PIXITValidator.validate_hex_pixit_value("abcd") # Should pass + + try: + PIXITValidator.validate_hex_pixit_value("0x123") # Odd number of digits + asserts.fail("Expected ValueError for odd number of hex digits") + except ValueError: + pass + + try: + PIXITValidator.validate_hex_pixit_value("123") # Odd number of digits + asserts.fail("Expected ValueError for odd number of hex digits") + except ValueError: + pass + + try: + PIXITValidator.validate_hex_pixit_value("WXYZ") # Invalid hex chars + asserts.fail("Expected ValueError for invalid hex characters") + except ValueError: + pass + + # Test full PIXIT validation + pixit_def = PIXITDefinition( + name="PIXIT.TEST.INT", + pixit_type=PIXITType.INT, + description="Test integer PIXIT", + required=True + ) + + PIXITValidator.validate_value("123", pixit_def) # Should pass + + try: + PIXITValidator.validate_value("abc", pixit_def) + asserts.fail("Expected PIXITValidationError for invalid type") + except PIXITValidationError: + pass + + try: + PIXITValidator.validate_value(None, pixit_def) # Required but None + asserts.fail("Expected PIXITValidationError for missing required value") + except PIXITValidationError: + pass + + # Test optional PIXIT + optional_pixit = PIXITDefinition( + name="PIXIT.TEST.INT", + pixit_type=PIXITType.INT, + description="Test integer PIXIT", + required=False + ) + + PIXITValidator.validate_value(None, optional_pixit) # Should pass as it's optional + if __name__ == "__main__": default_matter_test_main() From 663add6ef1d1b27f6b0c5217ddad9985dc77a521 Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Mon, 16 Dec 2024 18:08:28 +0100 Subject: [PATCH 09/11] apply linter suggestions --- src/python_testing/TestMatterTestingSupport.py | 6 +++--- .../chip/testing/matter_testing.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python_testing/TestMatterTestingSupport.py b/src/python_testing/TestMatterTestingSupport.py index dc4122035ed663..4029f502eab03b 100644 --- a/src/python_testing/TestMatterTestingSupport.py +++ b/src/python_testing/TestMatterTestingSupport.py @@ -22,9 +22,9 @@ import chip.clusters as Clusters from chip.clusters.Types import Nullable, NullValue -from chip.testing.matter_testing import (MatterBaseTest, PIXITDefinition, PIXITType, PIXITValidationError, PIXITValidator, async_test_body, compare_time, default_matter_test_main, - get_wait_seconds_from_set_time, parse_matter_test_args, type_matches, - utc_time_in_matter_epoch) +from chip.testing.matter_testing import (MatterBaseTest, PIXITDefinition, PIXITType, PIXITValidationError, PIXITValidator, + async_test_body, compare_time, default_matter_test_main, get_wait_seconds_from_set_time, + parse_matter_test_args, type_matches, utc_time_in_matter_epoch) from chip.testing.pics import parse_pics, parse_pics_xml from chip.testing.taglist_and_topology_test import (TagProblem, create_device_type_list_for_root, create_device_type_lists, find_tag_list_problems, find_tree_roots, flat_list_ok, get_all_children, diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index e9b347fd897e08..d193f09f232db4 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -39,7 +39,7 @@ from enum import Enum, IntFlag from functools import partial from itertools import chain -from typing import Any, Iterable, List, Optional, Tuple, Dict +from typing import Any, Dict, Iterable, List, Optional, Tuple from chip.tlv import float32, uint From 7d14dc953742b775d9127f629210031e261b049f Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Tue, 17 Dec 2024 15:52:37 +0100 Subject: [PATCH 10/11] apply Ruff suggestions --- .../chip/testing/matter_testing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index d193f09f232db4..abd3dd17348c59 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -39,7 +39,7 @@ from enum import Enum, IntFlag from functools import partial from itertools import chain -from typing import Any, Dict, Iterable, List, Optional, Tuple +from typing import Any, Iterable, List, Optional, Tuple from chip.tlv import float32, uint @@ -1190,7 +1190,7 @@ def get_test_pixits(self, test: str) -> list[PIXITDefinition]: try: fn = getattr(self, pixits_name) return fn() - except AttributeError as e: + except AttributeError: return [] def get_test_steps(self, test: str) -> list[TestStep]: From ce2938e389b3d255ab50a39cd1a8bcd557583618 Mon Sep 17 00:00:00 2001 From: Oleksandr Sirko Date: Wed, 8 Jan 2025 18:24:19 +0100 Subject: [PATCH 11/11] add one more possible hex format to validator, add test for testing validate_pixits function --- .../TestMatterTestingSupport.py | 64 +++++++++++++++++++ .../chip/testing/matter_testing.py | 19 ++++-- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/python_testing/TestMatterTestingSupport.py b/src/python_testing/TestMatterTestingSupport.py index 86c768bf374948..45f7efb9926e95 100644 --- a/src/python_testing/TestMatterTestingSupport.py +++ b/src/python_testing/TestMatterTestingSupport.py @@ -732,6 +732,7 @@ def test_pixit_validation(self): PIXITValidator.validate_hex_pixit_value("1234") # Should pass PIXITValidator.validate_hex_pixit_value("ABCD") # Should pass PIXITValidator.validate_hex_pixit_value("abcd") # Should pass + PIXITValidator.validate_hex_pixit_value("hex:12ab") # Should pass try: PIXITValidator.validate_hex_pixit_value("0x123") # Odd number of digits @@ -783,6 +784,69 @@ def test_pixit_validation(self): PIXITValidator.validate_value(None, optional_pixit) # Should pass as it's optional + def test_pixits_validation(self): + """Test bulk PIXIT validation functionality""" + pixits = [ + PIXITDefinition( + name="PIXIT.TEST.INT", + pixit_type=PIXITType.INT, + description="Test integer PIXIT" + ), + PIXITDefinition( + name="PIXIT.TEST.STRING", + pixit_type=PIXITType.STRING, + description="Test string PIXIT", + required=False + ), + PIXITDefinition( + name="PIXIT.TEST.HEX", + pixit_type=PIXITType.HEX, + description="Test hex PIXIT" + ) + ] + + # Test valid values + valid_values = { + "PIXIT.TEST.INT": "42", + "PIXIT.TEST.STRING": "test string", + "PIXIT.TEST.HEX": "0x1234" + } + PIXITValidator.validate_pixits(pixits, valid_values) # Should pass + + # Test missing required PIXIT + missing_required = { + "PIXIT.TEST.STRING": "test string", + "PIXIT.TEST.HEX": "0x1234" + } + try: + PIXITValidator.validate_pixits(pixits, missing_required) + asserts.fail("Expected PIXITValidationError for missing required PIXIT") + except PIXITValidationError as e: + asserts.assert_true("PIXIT.TEST.INT" in str(e), + "Error message should indicate missing PIXIT") + + # Test missing optional PIXIT (should pass) + missing_optional = { + "PIXIT.TEST.INT": "42", + "PIXIT.TEST.HEX": "0x1234" + } + PIXITValidator.validate_pixits(pixits, missing_optional) # Should pass + + # Test multiple validation errors + multiple_errors = { + "PIXIT.TEST.INT": "not a number", + "PIXIT.TEST.HEX": "invalid hex" + } + try: + PIXITValidator.validate_pixits(pixits, multiple_errors) + asserts.fail("Expected PIXITValidationError for multiple validation errors") + except PIXITValidationError as e: + error_msg = str(e) + asserts.assert_true("Invalid value for PIXIT.TEST.INT" in error_msg, + "Error message should indicate invalid INT PIXIT value") + asserts.assert_true("Invalid value for PIXIT.TEST.HEX" in error_msg, + "Error message should indicate invalid HEX PIXIT value") + if __name__ == "__main__": default_matter_test_main() diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py index 80aa296b1f18b8..5d2d64a12bdf71 100644 --- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py +++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py @@ -734,18 +734,23 @@ def validate_hex_pixit_value(value: str) -> None: """Validates that a value represents valid hexadecimal data. Args: - value: Value to validate. Can include optional "0x" prefix. + value: Value to validate. Can include optional "0x" prefix or "hex:" prefix Raises: ValueError: If value is not valid hexadecimal or has odd number of digits """ - try: - hex_str = value.lower().replace("0x", "") - int(hex_str, 16) # Validate hex format - - if len(hex_str) % 2 != 0: - raise ValueError(f"Hex string must have even number of digits, got {len(hex_str)} digits") + # Remove optional "0x" or "hex:" prefix + if value.startswith("0x"): + hex_value = value[2:] + elif value.startswith("hex:"): + hex_value = value[4:] + else: + hex_value = value + try: + int(hex_value, 16) # Validate hex format + if len(hex_value) % 2 != 0: + raise ValueError("Hex string must have even number of digits") except (ValueError, TypeError) as e: raise ValueError(f"Invalid hex value: {e}")