blob: 0445b3a31b4a6e4a33123aff34a50ddb00a04ca5 [file] [log] [blame]
#!/usr/bin/python3
# Copyright (c) 2024 Project CHIP Authors
#
# 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.
import re
from dataclasses import dataclass
from typing import Dict, List, Optional
import yaml
@dataclass
class Metadata:
py_script_path: Optional[str] = None
run: Optional[str] = None
app: Optional[str] = None
discriminator: Optional[str] = None
passcode: Optional[str] = None
def copy_from_dict(self, attr_dict: Dict[str, str]) -> None:
"""
Sets the value of the attributes from a dictionary.
Attributes:
attr_dict:
Dictionary that stores attributes value that should
be transferred to this class.
"""
if "app" in attr_dict:
self.app = attr_dict["app"]
if "run" in attr_dict:
self.run = attr_dict["run"]
if "discriminator" in attr_dict:
self.discriminator = attr_dict["discriminator"]
if "passcode" in attr_dict:
self.passcode = attr_dict["passcode"]
if "py_script_path" in attr_dict:
self.py_script_path = attr_dict["py_script_path"]
# TODO - set other attributes as well
class MetadataReader:
"""
A class to parse run arguments from the test scripts and
resolve them to environment specific values.
"""
def __init__(self, env_yaml_file_path: str):
"""
Reads the YAML file and Constructs the environment object
Parameters:
env_yaml_file_path:
Path to the environment file that contains the YAML configuration.
"""
with open(env_yaml_file_path) as stream:
self.env = yaml.safe_load(stream)
def __resolve_env_vals__(self, metadata_dict: Dict[str, str]) -> None:
"""
Resolves the argument defined in the test script to environment values.
For example, if a test script defines "all_clusters" as the value for app
name, we will check the environment configuration to see what raw value is
assocaited with the "all_cluster" variable and set the value for "app" option
to this raw value.
Parameter:
metadata_dict:
Dictionary where each key represent a particular argument and its value represent
the value for that argument defined in the test script.
"""
for run_arg, run_arg_val in metadata_dict.items():
if not type(run_arg_val) == str or run_arg == "run":
metadata_dict[run_arg] = run_arg_val
continue
if run_arg_val is None:
continue
sub_args = run_arg_val.split('/')
if len(sub_args) not in [1, 2]:
err = """The argument is not in the correct format.
The argument must follow the format of arg1 or arg1/arg2.
For example, arg1 represents the argument type and optionally arg2
represents a specific variable defined in the environment file whose
value should be used as the argument value. If arg2 is not specified,
we will just use the first value associated with arg1 in the environment file."""
raise Exception(err)
if len(sub_args) == 1:
run_arg_val = self.env.get(sub_args[0])
elif len(sub_args) == 2:
run_arg_val = self.env.get(sub_args[0]).get(sub_args[1])
# if a argument has been specified in the comment header
# but can't be found in the env file, consider it to be
# boolean value.
if run_arg_val is None:
run_arg_val = True
metadata_dict[run_arg] = run_arg_val
def __read_args__(self, run_args_lines: List[str]) -> Dict[str, str]:
"""
Parses a list of lines and extracts argument
values from it.
Parameters:
run_args_lines:
Line in test script header that contains run argument definition.
Each line will contain a list of run arguments separated by a space.
Line below is one example of what the run argument line will look like:
"app/all-clusters discriminator KVS storage-path"
In this case the line defines that app, discriminator, KVS, and storage-path
are the arguments that should be used with this run.
An argument can be defined multiple times in the same line or in different lines.
The last definition will override any previous definition. For example,
"KVS/kvs1 KVS/kvs2 KVS/kvs3" line will lead to KVS value of kvs3.
"""
metadata_dict = {}
for run_line in run_args_lines:
for run_arg_word in run_line.strip().split():
'''
We expect the run arg to be defined in one of the
following two formats:
1. run_arg
2. run_arg/run_arg_val
Examples: "discriminator" and "app/all_clusters"
'''
run_arg = run_arg_word.split('/', 1)[0]
metadata_dict[run_arg] = run_arg_word
return metadata_dict
def parse_script(self, py_script_path: str) -> List[Metadata]:
"""
Parses a script and returns a list of metadata object where
each element of that list representing run arguments associated
with a particular run.
Parameter:
py_script_path:
path to the python test script
Return:
List[Metadata]
List of Metadata object where each Metadata element represents
the run arguments associated with a particular run defined in
the script file.
"""
runs_def_ptrn = re.compile(r'^\s*#\s*test-runner-runs:\s*(.*)$')
args_def_ptrn = re.compile(r'^\s*#\s*test-runner-run/([a-zA-Z0-9_]+):\s*(.*)$')
runs_arg_lines: Dict[str, List[str]] = {}
runs_metadata = []
with open(py_script_path, 'r', encoding='utf8') as py_script:
for line in py_script.readlines():
runs_match = runs_def_ptrn.match(line.strip())
args_match = args_def_ptrn.match(line.strip())
if runs_match:
for run in runs_match.group(1).strip().split():
runs_arg_lines[run] = []
elif args_match:
runs_arg_lines[args_match.group(1)].append(args_match.group(2))
for run, lines in runs_arg_lines.items():
metadata_dict = self.__read_args__(lines)
self.__resolve_env_vals__(metadata_dict)
# store the run value and script location in the
# metadata object
metadata_dict['py_script_path'] = py_script_path
metadata_dict['run'] = run
metadata = Metadata()
metadata.copy_from_dict(metadata_dict)
runs_metadata.append(metadata)
return runs_metadata