| #!/usr/bin/env python3 |
| |
| # Copyright 2021 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Ignore indention messages, since legacy scripts use 2 spaces instead of 4. |
| # pylint: disable=bad-indentation,docstring-section-indent |
| # pylint: disable=docstring-trailing-quotes |
| |
| """Generate gtest metadata for testexecserv from provided yaml""" |
| |
| import argparse |
| import pathlib |
| import sys |
| import yaml |
| import six |
| import jsonschema |
| |
| from google.protobuf import text_format |
| from chromiumos.test.api import test_case_metadata_pb2 as tc_metadata_pb |
| from chromiumos.test.api import test_harness_pb2 as th_pb |
| from chromiumos.test.api import test_case_pb2 as tc_pb |
| |
| |
| def _test_case_exec_factory() -> th_pb.TestHarness: |
| """Factory method to build TestCaseExec proto objects""" |
| return tc_metadata_pb.TestCaseExec(test_harness=th_pb.TestHarness( |
| gtest=th_pb.TestHarness.Gtest())) |
| |
| |
| def _test_case_info_factory(data: dict) -> list: |
| """Factory method to build TestCaseInfo proto objects |
| |
| Args: |
| data: dict representing the 'owners' yaml data |
| """ |
| return tc_metadata_pb.TestCaseInfo( |
| owners=[tc_metadata_pb.Contact(email=x['email']) for x in data]) |
| |
| |
| def _test_case_factory(case_data: dict, suite_name: str) -> tc_pb.TestCase: |
| """Factory method for build TestCase proto objects |
| |
| Args: |
| case_data: dict representing the 'cases' yaml data |
| suite_name: string representing the 'name' yaml field |
| """ |
| tags = [tc_pb.TestCase.Tag(value=t) for t in case_data['tags']] |
| tc_id = tc_pb.TestCase.Id(value=f'gtest.{suite_name}.{case_data["id"]}') |
| name = f'{suite_name}.{case_data["id"]}' |
| return tc_pb.TestCase(id=tc_id, name=name, tags=tags) |
| |
| |
| def _test_case_metadata_factory( |
| case: tc_pb.TestCase, case_exec: tc_metadata_pb.TestCaseExec, |
| case_info: tc_metadata_pb.TestCaseInfo |
| ) -> tc_metadata_pb.TestCaseMetadata: |
| """Factory method for building TestCaseMetadata proto objects""" |
| return tc_metadata_pb.TestCaseMetadata(test_case=case, |
| test_case_exec=case_exec, |
| test_case_info=case_info) |
| |
| |
| def _test_case_list_factory(input_data: dict) -> list: |
| """Factory method for batch building TestCaseMetadata objects""" |
| suite_name = input_data['name'] |
| test_case_exec = _test_case_exec_factory() |
| test_case_info = _test_case_info_factory(input_data['owners']) |
| |
| test_cases = [ |
| _test_case_factory(tc, suite_name) for tc in input_data['cases'] |
| ] |
| |
| test_case_metadata = [ |
| _test_case_metadata_factory(case, test_case_exec, test_case_info) |
| for case in test_cases |
| ] |
| |
| return test_case_metadata |
| |
| |
| def _validate_yaml_file(parsed_yaml: dict, schema: dict): |
| """Validate the gtest yaml file against the schema |
| |
| The validate() method will throw an exception if the yaml is not valid. |
| |
| Args: |
| parsed_yaml: dict representation of the yaml file |
| schema: dict representation of the yaml schema |
| """ |
| jsonschema.validate(instance=parsed_yaml, schema=schema) |
| |
| |
| def main(input_files: list, output_file: pathlib.Path, |
| yaml_schema_file: pathlib.Path): |
| """Main entry point |
| |
| Args: |
| input_files: list of pathlib.Path objects for yaml metadata files |
| output_file: pathlib.Path object for storing generated proto metadata |
| yaml_schema_file: pathlib.Path object representing the YAML schema |
| """ |
| test_case_metadata = [] |
| |
| schema = yaml.safe_load(yaml_schema_file.read_text()) |
| |
| for f in input_files: |
| yaml_data = yaml.safe_load(f.read_text()) |
| _validate_yaml_file(yaml_data, schema) |
| test_case_metadata.extend(_test_case_list_factory(yaml_data)) |
| |
| test_case_metadata_list = tc_metadata_pb.TestCaseMetadataList( |
| values=test_case_metadata) |
| |
| # Make sure the appropriate directory structure is created. |
| # Mimic "mkdir -p" here. |
| output_file.parent.mkdir(parents=True, exist_ok=True) |
| output_file.write_bytes(test_case_metadata_list.SerializeToString()) |
| |
| |
| def _argparse_existing_file_factory(path: str) -> pathlib.Path: |
| """Create path objects |
| |
| Verify the files specified on the command line against: |
| 1) The file exists |
| 2) The file is a file, not a directory or other type |
| |
| Args: |
| path: path to file specified (str) |
| """ |
| p = pathlib.Path(path) |
| if not p.exists(): |
| raise argparse.ArgumentTypeError( |
| f"The specified file '{path}' does not exist!") |
| |
| if not p.is_file(): |
| raise argparse.ArgumentTypeError( |
| f"The specified file '{path}' is not a file!") |
| |
| return p |
| |
| |
| def _argparse_file_factory(path: str) -> pathlib.Path: |
| """Factory method that builds a pathlib.Path object""" |
| return pathlib.Path(path) |
| |
| |
| if __name__ == '__main__': |
| if six.PY2: |
| print('ERROR: Python2 detected, this script only runs with python3!') |
| sys.exit(1) |
| |
| parser = argparse.ArgumentParser( |
| description='Generate Gtest harness test case metadata') |
| parser.add_argument('--output_file', |
| help='Output file to write proto metadata', |
| type=_argparse_file_factory, |
| required=True) |
| parser.add_argument('--yaml_schema', |
| help='YAML schema file to validate test metadata', |
| type=_argparse_existing_file_factory, |
| required=True) |
| parser.add_argument('files', |
| help='Gtest YAML metadata files', |
| metavar='INPUT_YAML', |
| type=_argparse_existing_file_factory, |
| nargs='+') |
| parser.add_argument( |
| '--dump', |
| help='Dump pretty printed protobuf to stdout. For debugging purposes', |
| action='store_true', |
| default=False, |
| required=False) |
| args = parser.parse_args() |
| main(args.files, args.output_file, args.yaml_schema) |
| |
| if args.dump: |
| metadata_list = tc_metadata_pb.TestCaseMetadataList() |
| metadata_list.ParseFromString(args.output_file.read_bytes()) |
| print(text_format.MessageToString(metadata_list)) |