| # Copyright 2023 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Helper script to parse autotest control files for data gathering.""" |
| |
| import argparse |
| import pathlib |
| import re |
| |
| import actions |
| import cf_parse |
| import filters |
| |
| |
| # ChromeOS src/ dir relative to this file. |
| DEFAULT_SRC_DIR = pathlib.Path(__file__).joinpath('../../../../..').resolve() |
| |
| def modify_control_files(src_dir, actions_list, filters_list, dry_run=True, |
| public=True, private=True, client=True, server=True): |
| """Apply the given actions to control files if they pass the given filters. |
| |
| Args: |
| src_dir: Relative path to the chromium.org src/ directory. |
| actions_list: List of action functions applied to a ControlFile object. |
| filters_list: List of filter functions applied to a ControlFile object. |
| dry_run: True if no files should be actually modified, just printed. |
| public: True if public tests should be searched. |
| private: True if private tests should be searched. |
| client: True if client tests should be searched. |
| server: True if server tests should be searched. |
| """ |
| # Places to look for control files, relative to src_dir. |
| PUBLIC_CLIENT_DIR = 'third_party/autotest/files/client/site_tests/' |
| PUBLIC_SERVER_DIR = 'third_party/autotest/files/server/site_tests/' |
| PRIVATE_CLIENT_DIR = 'third_party/autotest-private/client/site_tests/' |
| |
| autotest_dirs = [] |
| if public: |
| if client: |
| autotest_dirs.append(PUBLIC_CLIENT_DIR) |
| if server: |
| autotest_dirs.append(PUBLIC_SERVER_DIR) |
| if private and client: |
| autotest_dirs.append(PRIVATE_CLIENT_DIR) |
| |
| for tests_dir in [pathlib.Path(src_dir, d) for d in autotest_dirs]: |
| for cf_path in tests_dir.glob('*/control*'): |
| cf = cf_parse.ControlFile(cf_path) |
| if not cf.is_valid: |
| continue |
| |
| # Skip this control file if it doesn't match all the given filters. |
| if not all(filter_func(cf) for filter_func in filters_list): |
| continue |
| |
| # Apply the given actions to this control file. |
| modified = False |
| for action_func in actions_list: |
| modified = action_func(cf) or modified |
| cf.update_contents() |
| |
| if dry_run: |
| print(f'Will modify {cf_path}:') |
| print(cf.contents) |
| else: |
| print(f'Editing {cf_path}') |
| with open(cf.path, 'w', encoding='utf-8') as f: |
| f.write(cf.contents) |
| |
| |
| def _set_up_args(): |
| """Define CLI arguments.""" |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--src_dir', default=DEFAULT_SRC_DIR, |
| help=('Path to the top-level chromiumos src/ directory ' |
| 'in your repo. Defaults to the path relative to ' |
| 'this file\'s location.')) |
| parser.add_argument('--write_out', action='store_true', |
| help=('Write out changes to your local repo. Please ' |
| 'do a dry run first!')) |
| public_private_group = parser.add_mutually_exclusive_group() |
| public_private_group.add_argument( |
| '--public_only', action='store_true', |
| help='Limit filtering to only public autotest tests.') |
| public_private_group.add_argument( |
| '--private_only', action='store_true', |
| help='Limit filtering to only autotest-private tests.') |
| client_server_group = parser.add_mutually_exclusive_group() |
| client_server_group.add_argument( |
| '--client_only', action='store_true', |
| help='Limit filtering to only client tests.') |
| client_server_group.add_argument( |
| '--server_only', action='store_true', |
| help='Limit filtering to only server tests.') |
| |
| action_group = parser.add_argument_group( |
| 'actions', 'Actions which are performed on a control file.') |
| action_group.add_argument( |
| '--remove_contacts', required=False, action='append', |
| metavar='EMAIL,EMAIL,...', |
| help=('Remove the given (comma or newline-separated) ' |
| 'email addresses from Contacts.')) |
| action_group.add_argument( |
| '--append_contacts', required=False, action='append', |
| metavar='EMAIL', |
| help=('Add (or move) the given email addresses to the END of the ' |
| 'contacts list.')) |
| action_group.add_argument( |
| '--prepend_contacts', required=False, action='append', |
| metavar='EMAIL', |
| help=('Add (or move) the given email addresses to the START of the ' |
| 'contacts list.')) |
| |
| filter_group = parser.add_argument_group( |
| 'filters', 'Filters to specify which control files are touched.') |
| filter_group.add_argument( |
| '--test_names', required=False, action='append', |
| help=('Action: Modify only the given (comma or newline-separated) ' |
| 'test ids (i.e. inlcuding \'tauto.\' prefix).')) |
| |
| return parser.parse_args() |
| |
| def _split_list_input(string_input): |
| """Split the given string into comma or newline-separated values.""" |
| return [elt.strip() for elt in re.split(r',\s*|\n\s*', string_input)] |
| |
| def _get_actions(args): |
| """Given the arguments to the CLI, return a list of actions requested.""" |
| output = [] |
| if args.remove_contacts: |
| for elt in args.remove_contacts: |
| output.append(actions.remove_contacts(_split_list_input(elt))) |
| if args.append_contacts: |
| for elt in args.append_contact: |
| output.append(actions.append_contacts(_split_list_input(elt))) |
| if args.prepend_contacts: |
| for elt in args.prepend_contact: |
| output.append(actions.prepend_contacts(_split_list_input(elt))) |
| return output |
| |
| |
| def _get_filters(args): |
| """Given the arguments to the CLI, return a list of filters requested.""" |
| output = [] |
| if args.test_names: |
| for elt in args.test_names: |
| tests = [t.strip() for t in re.split(r',\s*|\n\s*', elt)] |
| output.append(filters.test_list(tests)) |
| return output |
| |
| |
| def main(): |
| args = _set_up_args() |
| print(args) |
| |
| modify_control_files( |
| args.src_dir, _get_actions(args), _get_filters(args), |
| dry_run=not args.write_out, |
| public=not args.private_only, private=not args.public_only, |
| client=not args.server_only, server=not args.client_only) |
| |
| if __name__ == '__main__': |
| main() |