• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 gen_term_signal_handler(test_runners):
48    """Generates a termination signal handler function.
49
50    Args:
51        test_runners: A list of TestRunner objects.
52
53    Returns:
54        A function to be called when termination signals are received from
55        command line. This function stops all TestRunner objects.
56    """
57
58    def termination_sig_handler(signal_num, frame):
59        for t in test_runners:
60            t.stop()
61        sys.exit(1)
62
63    return termination_sig_handler
64
65
66def load_test_config_file(test_config_path,
67                          tb_filters=None,
68                          baseline_config=None):
69    """Processes the test configuration file provided by user.
70
71    Loads the configuration file into a json object, unpacks each testbed
72    config into its own json object, and validate the configuration in the
73    process.
74
75    Args:
76        test_config_path: Path to the test configuration file.
77        tb_filters: A list of strings, each is a test bed name. If None, all
78                    test beds are picked up. Otherwise only test bed names
79                    specified will be picked up.
80        baseline_config: dict, the baseline config to use (used iff
81                         test_config_path does not have device info).
82
83    Returns:
84        A list of test configuration json objects to be passed to TestRunner.
85    """
86    try:
87        configs = utils.load_config(test_config_path)
88        if keys.ConfigKeys.KEY_TESTBED not in configs and baseline_config:
89            configs.update(baseline_config)
90
91        if tb_filters:
92            tbs = []
93            for tb in configs[keys.ConfigKeys.KEY_TESTBED]:
94                if tb[keys.ConfigKeys.KEY_TESTBED_NAME] in tb_filters:
95                    tbs.append(tb)
96            if len(tbs) != len(tb_filters):
97                print("Expect to find %d test bed configs, found %d." %
98                      (len(tb_filters), len(tbs)))
99                print("Check if you have the correct test bed names.")
100                return None
101            configs[keys.ConfigKeys.KEY_TESTBED] = tbs
102        _validate_test_config(configs)
103        _validate_testbed_configs(configs[keys.ConfigKeys.KEY_TESTBED])
104        k_log_path = keys.ConfigKeys.KEY_LOG_PATH
105        configs[k_log_path] = utils.abs_path(configs[k_log_path])
106        tps = configs[keys.ConfigKeys.KEY_TEST_PATHS]
107    except errors.USERError as e:
108        print("Something is wrong in the test configurations.")
109        print(str(e))
110        return None
111    except Exception as e:
112        print("Error loading test config {}".format(test_config_path))
113        print(traceback.format_exc())
114        return None
115    # Unpack testbeds into separate json objects.
116    beds = configs.pop(keys.ConfigKeys.KEY_TESTBED)
117    config_jsons = []
118    for original_bed_config in beds:
119        new_test_config = dict(configs)
120        new_test_config[keys.ConfigKeys.KEY_TESTBED] = original_bed_config
121        # Keys in each test bed config will be copied to a level up to be
122        # picked up for user_params. If the key already exists in the upper
123        # level, the local one defined in test bed config overwrites the
124        # general one.
125        new_test_config.update(original_bed_config)
126        config_jsons.append(new_test_config)
127    return config_jsons
128
129
130def parse_test_list(test_list):
131    """Parse user provided test list into internal format for test_runner.
132
133    Args:
134        test_list: A list of test classes/cases.
135
136    Returns:
137        A list of tuples, each has a test class name and a list of test case
138        names.
139    """
140    result = []
141    for elem in test_list:
142        result.append(_parse_one_test_specifier(elem))
143    return result
144
145
146def _validate_test_config(test_config):
147    """Validates the raw configuration loaded from the config file.
148
149    Making sure all the required keys exist.
150
151    Args:
152        test_config: A dict that is the config to validate.
153
154    Raises:
155        errors.USERError is raised if any required key is missing from the
156        config.
157    """
158    for k in keys.ConfigKeys.RESERVED_KEYS:
159        if k not in test_config:
160            raise errors.USERError(("Required key {} missing in test "
161                                    "config.").format(k))
162
163
164def _parse_one_test_specifier(item):
165    """Parse one test specifier from command line input.
166
167    This also verifies that the test class name and test case names follow
168    ACTS's naming conventions. A test class name has to end with "Test"; a test
169    case name has to start with "test".
170
171    Args:
172        item: A string that specifies a test class or test cases in one test
173            class to run.
174
175    Returns:
176        A tuple of a string and a list of strings. The string is the test class
177        name, the list of strings is a list of test case names. The list can be
178        None.
179    """
180    tokens = item.split(':')
181    if len(tokens) > 2:
182        raise errors.USERError("Syntax error in test specifier %s" % item)
183    if len(tokens) == 1:
184        # This should be considered a test class name
185        test_cls_name = tokens[0]
186        _validate_test_class_name(test_cls_name)
187        return (test_cls_name, None)
188    elif len(tokens) == 2:
189        # This should be considered a test class name followed by
190        # a list of test case names.
191        test_cls_name, test_case_names = tokens
192        clean_names = []
193        _validate_test_class_name(test_cls_name)
194        for elem in test_case_names.split(','):
195            test_case_name = elem.strip()
196            if not test_case_name.startswith("test_"):
197                raise errors.USERError(
198                    ("Requested test case '%s' in test class "
199                     "'%s' does not follow the test case "
200                     "naming convention test_*.") % (test_case_name,
201                                                     test_cls_name))
202            clean_names.append(test_case_name)
203        return (test_cls_name, clean_names)
204
205
206def _parse_test_file(fpath):
207    """Parses a test file that contains test specifiers.
208
209    Args:
210        fpath: A string that is the path to the test file to parse.
211
212    Returns:
213        A list of strings, each is a test specifier.
214    """
215    try:
216        with open(fpath, 'r') as f:
217            tf = []
218            for line in f:
219                line = line.strip()
220                if not line:
221                    continue
222                if len(tf) and (tf[-1].endswith(':') or tf[-1].endswith(',')):
223                    tf[-1] += line
224                else:
225                    tf.append(line)
226            return tf
227    except:
228        print("Error loading test file.")
229        raise
230
231
232def _validate_test_class_name(test_cls_name):
233    """Checks if a string follows the test class name convention.
234
235    Args:
236        test_cls_name: A string that should be a test class name.
237
238    Raises:
239        errors.USERError is raised if the input does not follow test class
240        naming convention.
241    """
242    if not test_cls_name.endswith("Test"):
243        raise errors.USERError(
244            ("Requested test class '%s' does not follow the test "
245             "class naming convention *Test.") % test_cls_name)
246
247
248def _validate_testbed_configs(testbed_configs):
249    """Validates the testbed configurations.
250
251    Args:
252        testbed_configs: A list of testbed configuration json objects.
253
254    Raises:
255        If any part of the configuration is invalid, errors.USERError is raised.
256    """
257    seen_names = set()
258    # Cross checks testbed configs for resource conflicts.
259    for config in testbed_configs:
260        # Check for conflicts between multiple concurrent testbed configs.
261        # No need to call it if there's only one testbed config.
262        name = config[keys.ConfigKeys.KEY_TESTBED_NAME]
263        _validate_testbed_name(name)
264        # Test bed names should be unique.
265        if name in seen_names:
266            raise errors.USERError("Duplicate testbed name {} found.".format(
267                name))
268        seen_names.add(name)
269
270
271def _validate_testbed_name(name):
272    """Validates the name of a test bed.
273
274    Since test bed names are used as part of the test run id, it needs to meet
275    certain requirements.
276
277    Args:
278        name: The test bed's name specified in config file.
279
280    Raises:
281        If the name does not meet any criteria, errors.USERError is raised.
282    """
283    if not name:
284        raise errors.USERError("Test bed names can't be empty.")
285    if not isinstance(name, str) and not isinstance(name, basestring):
286        raise errors.USERError("Test bed names have to be string. Found: %s" %
287                               type(name))
288    for l in name:
289        if l not in utils.valid_filename_chars:
290            raise errors.USERError(
291                "Char '%s' is not allowed in test bed names." % l)
292