• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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