Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Couple of quick fixes to yamltests parser #24331

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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