1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17from builtins import str
18
19import copy
20import signal
21import sys
22import traceback
23
24from vts.runners.host import keys
25from vts.runners.host import errors
26from vts.runners.host import signals
27from vts.runners.host import utils
28
29_DEFAULT_CONFIG_TEMPLATE = {
30    "test_bed": {
31        "AndroidDevice": "*",
32    },
33    "log_path": "/tmp/logs",
34    "test_paths": ["./"],
35    "enable_web": False,
36}
37
38
39def GetDefaultConfig(test_name):
40    """Returns a default config data structure (when no config file is given)."""
41    result = copy.deepcopy(_DEFAULT_CONFIG_TEMPLATE)
42    result[keys.ConfigKeys.KEY_TESTBED][
43        keys.ConfigKeys.KEY_TESTBED_NAME] = test_name
44    return result
45
46
47def load_test_config_file(test_config_path,
48                          tb_filters=None,
49                          baseline_config=None):
50    """Processes the test configuration file provided by user.
51
52    Loads the configuration file into a json object, unpacks each testbed
53    config into its own json object, and validate the configuration in the
54    process.
55
56    Args:
57        test_config_path: Path to the test configuration file.
58        tb_filters: A list of strings, each is a test bed name. If None, all
59                    test beds are picked up. Otherwise only test bed names
60                    specified will be picked up.
61        baseline_config: dict, the baseline config to use (used iff
62                         test_config_path does not have device info).
63
64    Returns:
65        A list of test configuration json objects to be passed to TestRunner.
66    """
67    try:
68        configs = utils.load_config(test_config_path)
69        if keys.ConfigKeys.KEY_TESTBED not in configs and baseline_config:
70            configs.update(baseline_config)
71
72        if tb_filters:
73            tbs = []
74            for tb in configs[keys.ConfigKeys.KEY_TESTBED]:
75                if tb[keys.ConfigKeys.KEY_TESTBED_NAME] in tb_filters:
76                    tbs.append(tb)
77            if len(tbs) != len(tb_filters):
78                print("Expect to find %d test bed configs, found %d." %
79                      (len(tb_filters), len(tbs)))
80                print("Check if you have the correct test bed names.")
81                return None
82            configs[keys.ConfigKeys.KEY_TESTBED] = tbs
83        _validate_test_config(configs)
84        _validate_testbed_configs(configs[keys.ConfigKeys.KEY_TESTBED])
85        k_log_path = keys.ConfigKeys.KEY_LOG_PATH
86        configs[k_log_path] = utils.abs_path(configs[k_log_path])
87        tps = configs[keys.ConfigKeys.KEY_TEST_PATHS]
88    except errors.USERError as e:
89        print("Something is wrong in the test configurations.")
90        print(str(e))
91        return None
92    except Exception as e:
93        print("Error loading test config {}".format(test_config_path))
94        print(traceback.format_exc())
95        return None
96    # Unpack testbeds into separate json objects.
97    beds = configs.pop(keys.ConfigKeys.KEY_TESTBED)
98    config_jsons = []
99    for original_bed_config in beds:
100        new_test_config = dict(configs)
101        new_test_config[keys.ConfigKeys.KEY_TESTBED] = original_bed_config
102        # Keys in each test bed config will be copied to a level up to be
103        # picked up for user_params. If the key already exists in the upper
104        # level, the local one defined in test bed config overwrites the
105        # general one.
106        new_test_config.update(original_bed_config)
107        config_jsons.append(new_test_config)
108    return config_jsons
109
110
111def parse_test_list(test_list):
112    """Parse user provided test list into internal format for test_runner.
113
114    Args:
115        test_list: A list of test classes/cases.
116
117    Returns:
118        A list of tuples, each has a test class name and a list of test case
119        names.
120    """
121    result = []
122    for elem in test_list:
123        result.append(_parse_one_test_specifier(elem))
124    return result
125
126
127def _validate_test_config(test_config):
128    """Validates the raw configuration loaded from the config file.
129
130    Making sure all the required keys exist.
131
132    Args:
133        test_config: A dict that is the config to validate.
134
135    Raises:
136        errors.USERError is raised if any required key is missing from the
137        config.
138    """
139    for k in keys.ConfigKeys.RESERVED_KEYS:
140        if k not in test_config:
141            raise errors.USERError(("Required key {} missing in test "
142                                    "config.").format(k))
143
144
145def _parse_one_test_specifier(item):
146    """Parse one test specifier from command line input.
147
148    This also verifies that the test class name and test case names follow
149    ACTS's naming conventions. A test class name has to end with "Test"; a test
150    case name has to start with "test".
151
152    Args:
153        item: A string that specifies a test class or test cases in one test
154            class to run.
155
156    Returns:
157        A tuple of a string and a list of strings. The string is the test class
158        name, the list of strings is a list of test case names. The list can be
159        None.
160    """
161    tokens = item.split(':')
162    if len(tokens) > 2:
163        raise errors.USERError("Syntax error in test specifier %s" % item)
164    if len(tokens) == 1:
165        # This should be considered a test class name
166        test_cls_name = tokens[0]
167        _validate_test_class_name(test_cls_name)
168        return (test_cls_name, None)
169    elif len(tokens) == 2:
170        # This should be considered a test class name followed by
171        # a list of test case names.
172        test_cls_name, test_case_names = tokens
173        clean_names = []
174        _validate_test_class_name(test_cls_name)
175        for elem in test_case_names.split(','):
176            test_case_name = elem.strip()
177            if not test_case_name.startswith("test_"):
178                raise errors.USERError(
179                    ("Requested test case '%s' in test class "
180                     "'%s' does not follow the test case "
181                     "naming convention test_*.") % (test_case_name,
182                                                     test_cls_name))
183            clean_names.append(test_case_name)
184        return (test_cls_name, clean_names)
185
186
187def _parse_test_file(fpath):
188    """Parses a test file that contains test specifiers.
189
190    Args:
191        fpath: A string that is the path to the test file to parse.
192
193    Returns:
194        A list of strings, each is a test specifier.
195    """
196    try:
197        with open(fpath, 'r') as f:
198            tf = []
199            for line in f:
200                line = line.strip()
201                if not line:
202                    continue
203                if len(tf) and (tf[-1].endswith(':') or tf[-1].endswith(',')):
204                    tf[-1] += line
205                else:
206                    tf.append(line)
207            return tf
208    except:
209        print("Error loading test file.")
210        raise
211
212
213def _validate_test_class_name(test_cls_name):
214    """Checks if a string follows the test class name convention.
215
216    Args:
217        test_cls_name: A string that should be a test class name.
218
219    Raises:
220        errors.USERError is raised if the input does not follow test class
221        naming convention.
222    """
223    if not test_cls_name.endswith("Test"):
224        raise errors.USERError(
225            ("Requested test class '%s' does not follow the test "
226             "class naming convention *Test.") % test_cls_name)
227
228
229def _validate_testbed_configs(testbed_configs):
230    """Validates the testbed configurations.
231
232    Args:
233        testbed_configs: A list of testbed configuration json objects.
234
235    Raises:
236        If any part of the configuration is invalid, errors.USERError is raised.
237    """
238    seen_names = set()
239    # Cross checks testbed configs for resource conflicts.
240    for config in testbed_configs:
241        # Check for conflicts between multiple concurrent testbed configs.
242        # No need to call it if there's only one testbed config.
243        name = config[keys.ConfigKeys.KEY_TESTBED_NAME]
244        _validate_testbed_name(name)
245        # Test bed names should be unique.
246        if name in seen_names:
247            raise errors.USERError("Duplicate testbed name {} found.".format(
248                name))
249        seen_names.add(name)
250
251
252def _validate_testbed_name(name):
253    """Validates the name of a test bed.
254
255    Since test bed names are used as part of the test run id, it needs to meet
256    certain requirements.
257
258    Args:
259        name: The test bed's name specified in config file.
260
261    Raises:
262        If the name does not meet any criteria, errors.USERError is raised.
263    """
264    if not name:
265        raise errors.USERError("Test bed names can't be empty.")
266    if not isinstance(name, str) and not isinstance(name, basestring):
267        raise errors.USERError("Test bed names have to be string. Found: %s" %
268                               type(name))
269    for l in name:
270        if l not in utils.valid_filename_chars:
271            raise errors.USERError(
272                "Char '%s' is not allowed in test bed names." % l)
273