Skip to content

Commit

Permalink
Couple of quick fixes to yamltests parser (project-chip#24331)
Browse files Browse the repository at this point in the history
These fixes are needed for multi admin support. They were missed when
doing some refactoring in the yamltests parser.
  • Loading branch information
tehampson authored and cletnick committed Jan 10, 2023
1 parent 8cc664f commit 7de4dc1
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 36 deletions.
9 changes: 5 additions & 4 deletions scripts/py_matter_yamltests/matter_yamltests/fixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ def try_add_yaml_support_for_scientific_notation_without_dot(loader):
# accessory. But this state may not exist in the runner (as in it prevent to have multiple node ids
# associated to a fabric...) so the 'nodeId' needs to be added back manually.
def try_update_yaml_node_id_test_runner_state(tests, config):
identities = {'alpha': None if 'nodeId' not in config else config['nodeId']}
identities = {
'alpha': None if 'nodeId' not in config else config['nodeId']}

for test in tests:
if not test.is_enabled:
Expand All @@ -106,9 +107,9 @@ def try_update_yaml_node_id_test_runner_state(tests, config):

if test.cluster == 'CommissionerCommands':
if test.command == 'PairWithCode':
for item in test.arguments['values']:
for item in test.arguments_with_placeholders['values']:
if item['name'] == 'nodeId':
identities[identity] = item['value']
elif identity is not None and identity in identities:
nodeId = identities[identity]
test.nodeId = nodeId
node_id = identities[identity]
test.node_id = node_id
95 changes: 63 additions & 32 deletions scripts/py_matter_yamltests/matter_yamltests/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ def __init__(self, test: dict, config: dict, definitions):
self.fabric_filtered = _value_or_none(test, 'fabricFiltered')
self.min_interval = _value_or_none(test, 'minInterval')
self.max_interval = _value_or_none(test, 'maxInterval')
self.timed_interaction_timeout_ms = _value_or_none(test, 'timedInteractionTimeoutMs')
self.timed_interaction_timeout_ms = _value_or_none(
test, 'timedInteractionTimeoutMs')
self.busy_wait_ms = _value_or_none(test, 'busyWaitMs')

self.is_attribute = self.command in _ATTRIBUTE_COMMANDS
Expand All @@ -215,8 +216,10 @@ def __init__(self, test: dict, config: dict, definitions):
self.arguments_with_placeholders = _value_or_none(test, 'arguments')
self.response_with_placeholders = _value_or_none(test, 'response')

_check_valid_keys(self.arguments_with_placeholders, _TEST_ARGUMENTS_SECTION)
_check_valid_keys(self.response_with_placeholders, _TEST_RESPONSE_SECTION)
_check_valid_keys(self.arguments_with_placeholders,
_TEST_ARGUMENTS_SECTION)
_check_valid_keys(self.response_with_placeholders,
_TEST_RESPONSE_SECTION)

self._convert_single_value_to_values(self.arguments_with_placeholders)
self._convert_single_value_to_values(self.response_with_placeholders)
Expand All @@ -225,20 +228,26 @@ def __init__(self, test: dict, config: dict, definitions):
response_mapping = None

if self.is_attribute:
attribute = definitions.get_attribute_by_name(self.cluster, self.attribute)
attribute = definitions.get_attribute_by_name(
self.cluster, self.attribute)
if attribute:
attribute_mapping = self._as_mapping(definitions, self.cluster,
attribute.definition.data_type.name)
argument_mapping = attribute_mapping
response_mapping = attribute_mapping
else:
command = definitions.get_command_by_name(self.cluster, self.command)
command = definitions.get_command_by_name(
self.cluster, self.command)
if command:
argument_mapping = self._as_mapping(definitions, self.cluster, command.input_param)
response_mapping = self._as_mapping(definitions, self.cluster, command.output_param)
argument_mapping = self._as_mapping(
definitions, self.cluster, command.input_param)
response_mapping = self._as_mapping(
definitions, self.cluster, command.output_param)

self._update_with_definition(self.arguments_with_placeholders, argument_mapping)
self._update_with_definition(self.response_with_placeholders, response_mapping)
self._update_with_definition(
self.arguments_with_placeholders, argument_mapping)
self._update_with_definition(
self.response_with_placeholders, response_mapping)

# This performs a very basic sanity parse time check of constrains. This parsing happens
# again inside post processing response since at that time we will have required variables
Expand Down Expand Up @@ -281,7 +290,8 @@ def _as_mapping(self, definitions, cluster_name, target_name):
if hasattr(element, 'base_type'):
target_name = element.base_type.lower()
elif hasattr(element, 'fields'):
target_name = {f.name: self._as_mapping(definitions, cluster_name, f.data_type.name) for f in element.fields}
target_name = {f.name: self._as_mapping(
definitions, cluster_name, f.data_type.name) for f in element.fields}
elif target_name:
target_name = target_name.lower()

Expand All @@ -296,7 +306,8 @@ def _update_with_definition(self, container: dict, mapping_type):
mapping = mapping_type if self.is_attribute else mapping_type[value['name']]

if key == 'value':
value[key] = self._update_value_with_definition(item_value, mapping)
value[key] = self._update_value_with_definition(
item_value, mapping)
elif key == 'saveAs' and type(item_value) is str and item_value not in self._parsing_config_variable_storage:
self._parsing_config_variable_storage[item_value] = None
elif key == 'constraints':
Expand All @@ -321,7 +332,8 @@ def _update_value_with_definition(self, value, mapping_type):
rv[key] = value[key] # int64u
else:
mapping = mapping_type[key]
rv[key] = self._update_value_with_definition(value[key], mapping)
rv[key] = self._update_value_with_definition(
value[key], mapping)
return rv
if type(value) is list:
return [self._update_value_with_definition(entry, mapping_type) for entry in value]
Expand All @@ -331,7 +343,8 @@ def _update_value_with_definition(self, value, mapping_type):
if value is not None and value not in self._parsing_config_variable_storage:
if mapping_type == 'int64u' or mapping_type == 'int64s' or mapping_type == 'bitmap64' or mapping_type == 'epoch_us':
value = fixes.try_apply_yaml_cpp_longlong_limitation_fix(value)
value = fixes.try_apply_yaml_unrepresentable_integer_for_javascript_fixes(value)
value = fixes.try_apply_yaml_unrepresentable_integer_for_javascript_fixes(
value)
elif mapping_type == 'single' or mapping_type == 'double':
value = fixes.try_apply_yaml_float_written_as_strings(value)
elif mapping_type == 'octet_string' or mapping_type == 'long_octet_string':
Expand Down Expand Up @@ -464,7 +477,7 @@ def _response_error_validation(self, response, result):
error_success = 'The test expects the "{error}" error which occured successfully.'
error_success_no_error = 'The test expects no error and no error occurred.'
error_wrong_error = 'The test expects the "{error}" error but the "{value}" error occured.'
error_unexpected_error = 'The test expects no error but the "{value}" error occured.'
error_unexpected_error = 'The test expects no error but the "{error}" error occured.'
error_unexpected_success = 'The test expects the "{error}" error but no error occured.'

expected_error = self.response.get('error') if self.response else None
Expand All @@ -478,14 +491,17 @@ def _response_error_validation(self, response, result):
received_error = response.get('error')

if expected_error and received_error and expected_error == received_error:
result.success(check_type, error_success.format(error=expected_error))
result.success(check_type, error_success.format(
error=expected_error))
elif expected_error and received_error:
result.error(check_type, error_wrong_error.format(
error=expected_error, value=received_error))
elif expected_error and not received_error:
result.error(check_type, error_unexpected_success.format(error=expected_error))
result.error(check_type, error_unexpected_success.format(
error=expected_error))
elif not expected_error and received_error:
result.error(check_type, error_unexpected_error.format(error=received_error))
result.error(check_type, error_unexpected_error.format(
error=received_error))
elif not expected_error and not received_error:
result.success(check_type, error_success_no_error)
else:
Expand All @@ -503,12 +519,14 @@ def _response_cluster_error_validation(self, response, result):

if expected_error:
if received_error and expected_error == received_error:
result.success(check_type, error_success.format(error=expected_error))
result.success(check_type, error_success.format(
error=expected_error))
elif received_error:
result.error(check_type, error_wrong_error.format(
error=expected_error, value=received_error))
else:
result.error(check_type, error_unexpected_success.format(error=expected_error))
result.error(check_type, error_unexpected_success.format(
error=expected_error))
else:
# Nothing is logged here to not be redundant with the generic error checking code.
pass
Expand All @@ -528,10 +546,12 @@ def _response_values_validation(self, response, result):
if not self.is_attribute:
expected_name = value.get('name')
if expected_name not in received_value:
result.error(check_type, error_name_does_not_exist.format(name=expected_name))
result.error(check_type, error_name_does_not_exist.format(
name=expected_name))
continue

received_value = received_value.get(expected_name) if received_value else None
received_value = received_value.get(
expected_name) if received_value else None

# TODO Supports Array/List. See an exemple of failure in TestArmFailSafe.yaml
expected_value = value.get('value')
Expand All @@ -557,10 +577,12 @@ def _response_constraints_validation(self, response, result):
if not self.is_attribute:
expected_name = value.get('name')
if expected_name not in received_value:
result.error(check_type, error_name_does_not_exist.format(name=expected_name))
result.error(check_type, error_name_does_not_exist.format(
name=expected_name))
continue

received_value = received_value.get(expected_name) if received_value else None
received_value = received_value.get(
expected_name) if received_value else None

constraints = get_constraints(value['constraints'])
if all([constraint.is_met(received_value) for constraint in constraints]):
Expand All @@ -583,14 +605,17 @@ def _maybe_save_as(self, response, result):
if not self.is_attribute:
expected_name = value.get('name')
if expected_name not in received_value:
result.error(check_type, error_name_does_not_exist.format(name=expected_name))
result.error(check_type, error_name_does_not_exist.format(
name=expected_name))
continue

received_value = received_value.get(expected_name) if received_value else None
received_value = received_value.get(
expected_name) if received_value else None

save_as = value.get('saveAs')
self._runtime_config_variable_storage[save_as] = received_value
result.success(check_type, error_success.format(value=received_value, name=save_as))
result.success(check_type, error_success.format(
value=received_value, name=save_as))

def _update_placeholder_values(self, container):
if not container:
Expand All @@ -600,7 +625,8 @@ def _update_placeholder_values(self, container):

for idx, item in enumerate(values):
if 'value' in item:
values[idx]['value'] = self._config_variable_substitution(item['value'])
values[idx]['value'] = self._config_variable_substitution(
item['value'])

if 'constraints' in item:
for constraint, constraint_value in item['constraints'].items():
Expand All @@ -615,7 +641,8 @@ def _config_variable_substitution(self, value):
elif type(value) is dict:
mapped_value = {}
for key in value:
mapped_value[key] = self._config_variable_substitution(value[key])
mapped_value[key] = self._config_variable_substitution(
value[key])
return mapped_value
elif type(value) is str:
# For most tests, a single config variable is used and it can be replaced as in.
Expand Down Expand Up @@ -669,7 +696,8 @@ def __init__(self, parsing_config_variable_storage: dict, definitions, tests: di
fixes.try_update_yaml_node_id_test_runner_state(
enabled_tests, self._parsing_config_variable_storage)

self._runtime_config_variable_storage = copy.deepcopy(parsing_config_variable_storage)
self._runtime_config_variable_storage = copy.deepcopy(
parsing_config_variable_storage)
self._tests = enabled_tests
self._index = 0
self.count = len(self._tests)
Expand All @@ -692,18 +720,21 @@ def __init__(self, test_file, pics_file, definitions):
# TODO Needs supports for PICS file
with open(test_file) as f:
loader = yaml.FullLoader
loader = fixes.try_add_yaml_support_for_scientific_notation_without_dot(loader)
loader = fixes.try_add_yaml_support_for_scientific_notation_without_dot(
loader)

data = yaml.load(f, Loader=loader)
_check_valid_keys(data, _TESTS_SECTION)

self.name = _value_or_none(data, 'name')
self.PICS = _value_or_none(data, 'PICS')

self._parsing_config_variable_storage = _value_or_none(data, 'config')
self._parsing_config_variable_storage = _value_or_none(
data, 'config')

tests = _value_or_none(data, 'tests')
self.tests = YamlTests(self._parsing_config_variable_storage, definitions, tests)
self.tests = YamlTests(
self._parsing_config_variable_storage, definitions, tests)

def update_config(self, key, value):
self._parsing_config_variable_storage[key] = value

0 comments on commit 7de4dc1

Please sign in to comment.