Home | History | Annotate | Download | only in host
      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 
     17 from builtins import str
     18 
     19 import copy
     20 import signal
     21 import sys
     22 import traceback
     23 
     24 from vts.runners.host import keys
     25 from vts.runners.host import errors
     26 from vts.runners.host import signals
     27 from 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 
     39 def 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 
     47 def 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 
    111 def 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 
    127 def _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 
    145 def _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 
    187 def _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 
    213 def _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 
    229 def _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 
    252 def _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