1# Lint as: python2, python3 2# Copyright 2021 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Script to generate Tauto test wrappers based on JSON configuration. 6 7USAGE: python generate_tests.py <config_file.json> [suite] 8 9This script generates control files for wrapping all Tast test files provided 10in the configuration JSON file with a Tauto test cases. No Tauto suite files are 11generated, these assumed to be added manually. 12 13Configuration file may contain multiple suites, in which case, all tests of all 14suites will be generated, unless [suite] argument is present, in which case 15only that suite will be re/generated. 16 17Configuration file is validated against the schema in config_schema.yaml file. 18Schema file must be located in the same folder with the current script. 19""" 20 21import copy 22import json 23import os 24import sys 25from jsonschema import validate 26import yaml 27 28SCHEMA_FILE = 'config_schema.yaml' 29TEST_TEMPLATE_FILE = 'template.control.performance_cuj' 30 31# The priority of the first test. Decremented by 1 for each subsequent test. 32INITIAL_PRIORITY = 5000 33 34# Max duration of a single test. 35HOUR_IN_SECS = 60 * 60 36DEFAULT_TEST_DURATION = 1 * HOUR_IN_SECS 37 38 39def _get_absolute_path(local_file): 40 return os.path.join(os.path.dirname(os.path.realpath(__file__)), 41 local_file) 42 43 44def _load_json_config(config_path): 45 with open(_get_absolute_path(config_path), 'r') as config_file: 46 return json.load(config_file) 47 48 49def _validate_config_schema(json_config): 50 # Loading the schema file 51 with open(SCHEMA_FILE, 'r') as schema_file: 52 schema = yaml.safe_load(schema_file) 53 54 validate(json_config, schema) 55 56 57def _parse_constants(json_config): 58 consts = dict() 59 if 'const' in json_config: 60 for c in json_config['const']: 61 consts[c['name']] = c['value'] 62 return consts 63 64 65def _substitute_constants(val, constants): 66 for const in constants: 67 val = val.replace('$' + const + '$', constants[const]) 68 return val 69 70 71def _parse_tests(json_config, constants): 72 tests = [] 73 for test in json_config['tests']: 74 new_test = copy.deepcopy(test) 75 # Substitute constants in all fields of the test. 76 new_test['name'] = _substitute_constants(new_test['name'], constants) 77 new_test['test_expr'] = _substitute_constants(new_test['test_expr'], 78 constants) 79 if 'args' in new_test: 80 new_args = [] 81 for arg in new_test['args']: 82 new_args.append(_substitute_constants(arg, constants)) 83 new_test['args'] = new_args 84 if 'attributes' in new_test: 85 new_attrs = [] 86 for attr in new_test['attributes']: 87 new_attrs.append(_substitute_constants(attr, constants)) 88 new_test['attributes'] = new_attrs 89 if 'deps' in new_test: 90 new_deps = [] 91 for dep in new_test['deps']: 92 new_deps.append(_substitute_constants(dep, constants)) 93 new_test['deps'] = new_deps 94 tests.append(new_test) 95 return tests 96 97 98def _find_test(test_name, tests): 99 for test in tests: 100 if test['name'] == test_name: 101 return test 102 return None 103 104 105def _parse_suites(json_config, tests, constants): 106 suites = [] 107 for suite in json_config['suites']: 108 new_suite = copy.deepcopy(suite) 109 new_suite['name'] = _substitute_constants(new_suite['name'], constants) 110 if 'args_file' in new_suite: 111 new_suite['args_file'] = _substitute_constants( 112 new_suite['args_file'], constants) 113 if 'args' in new_suite: 114 new_args = [] 115 for arg in new_suite['args']: 116 new_args.append(_substitute_constants(arg, constants)) 117 new_suite['args'] = new_args 118 for test in new_suite['tests']: 119 if not _find_test(test['test'], tests): 120 raise Exception( 121 'Test %s (requested by suite %s) is not defined.' % 122 (test['test'], new_suite['name'])) 123 test['test'] = _substitute_constants(test['test'], constants) 124 suites.append(new_suite) 125 return suites 126 127 128def _read_file(filename): 129 with open(filename, 'r') as content_file: 130 return content_file.read() 131 132 133def _write_file(filename, data): 134 with open(filename, 'w') as out_file: 135 out_file.write(data) 136 137 138def _normalize_test_name(test_name): 139 return test_name.replace('.', '_').replace('*', '_') 140 141 142def _calculate_suffix(current_index, repeats): 143 # No suffix for single tests. 144 if repeats == 1: 145 return '' 146 # Number of suffix digits depends on the total repeat count. 147 digits = len(str(repeats)) 148 format_string = ('_{{index:0{digits}n}}').format(digits=digits) 149 return format_string.format(index=current_index) 150 151 152def _generate_test_files(version, suites, tests, suite_name=None): 153 template = _read_file(_get_absolute_path(TEST_TEMPLATE_FILE)) 154 for suite in suites: 155 priority = INITIAL_PRIORITY 156 if suite_name and suite['name'] != suite_name: 157 continue 158 for test in suite['tests']: 159 test_data = _find_test(test['test'], tests) 160 repeats = test['repeats'] 161 deps = [] 162 if 'deps' in test_data: 163 deps = test_data['deps'] 164 for i in range(repeats): 165 test_name = _normalize_test_name( 166 test_data['test_expr'] + 167 _calculate_suffix(i + 1, repeats)) 168 control_file = template.format( 169 name=test_name, 170 priority=priority, 171 duration=DEFAULT_TEST_DURATION, 172 test_exprs=test_data['test_expr'], 173 length='long', 174 version=version, 175 attributes='suite:' + suite['name'], 176 dependencies=', '.join(deps), 177 iteration=i + 1, 178 ) 179 control_file_name = 'control.' + '_'.join( 180 [suite['name'], test_name]) 181 _write_file(control_file_name, control_file) 182 priority = priority - 1 183 184 185def main(argv): 186 """Main program that parses JSON configuration and generates test wrappers.""" 187 if not argv or len(argv) < 2 or len(argv) > 3: 188 raise Exception( 189 'Invalid command-line arguments. Usage: python generate_tests.py <config_file.json> [suite]' 190 ) 191 192 suite_name = None 193 if (len(argv) == 3): 194 suite_name = argv[2] 195 196 # Load and validate the config JSON file. 197 json_config = _load_json_config(argv[1]) 198 _validate_config_schema(json_config) 199 200 version = json_config['version'] 201 constants = _parse_constants(json_config) 202 tests = _parse_tests(json_config, constants) 203 suites = _parse_suites(json_config, tests, constants) 204 _generate_test_files(version, suites, tests, suite_name) 205 206 207if __name__ == '__main__': 208 sys.exit(main(sys.argv)) 209