1#!/usr/bin/env python3 2# 3# Copyright 2016 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import os 19import re 20import signal 21import sys 22import traceback 23from builtins import str 24 25from mobly import config_parser as mobly_config_parser 26 27from acts import config_parser 28from acts import keys 29from acts import signals 30from acts import test_runner 31from acts import utils 32from acts.config_parser import ActsConfigError 33 34 35def _run_test(parsed_config, test_identifiers, repeat=1): 36 """Instantiate and runs test_runner.TestRunner. 37 38 This is the function to start separate processes with. 39 40 Args: 41 parsed_config: A mobly.config_parser.TestRunConfig that is a set of 42 configs for one test_runner.TestRunner. 43 test_identifiers: A list of tuples, each identifies what test case to 44 run on what test class. 45 repeat: Number of times to iterate the specified tests. 46 47 Returns: 48 True if all tests passed without any error, False otherwise. 49 """ 50 runner = _create_test_runner(parsed_config, test_identifiers) 51 try: 52 for i in range(repeat): 53 runner.run() 54 return runner.results.is_all_pass 55 except signals.TestAbortAll: 56 return True 57 except: 58 print("Exception when executing %s, iteration %s." % 59 (runner.testbed_name, i)) 60 print(traceback.format_exc()) 61 finally: 62 runner.stop() 63 64 65def _create_test_runner(parsed_config, test_identifiers): 66 """Instantiates one test_runner.TestRunner object and register termination 67 signal handlers that properly shut down the test_runner.TestRunner run. 68 69 Args: 70 parsed_config: A mobly.config_parser.TestRunConfig that is a set of 71 configs for one test_runner.TestRunner. 72 test_identifiers: A list of tuples, each identifies what test case to 73 run on what test class. 74 75 Returns: 76 A test_runner.TestRunner object. 77 """ 78 try: 79 t = test_runner.TestRunner(parsed_config, test_identifiers) 80 except: 81 print("Failed to instantiate test runner, abort.") 82 print(traceback.format_exc()) 83 sys.exit(1) 84 # Register handler for termination signals. 85 handler = config_parser.gen_term_signal_handler([t]) 86 signal.signal(signal.SIGTERM, handler) 87 signal.signal(signal.SIGINT, handler) 88 return t 89 90 91def _run_tests(parsed_configs, test_identifiers, repeat): 92 """Executes requested tests sequentially. 93 94 Requested test runs will commence one after another according to the order 95 of their corresponding configs. 96 97 Args: 98 parsed_configs: A list of mobly.config_parser.TestRunConfig, each is a 99 set of configs for one test_runner.TestRunner. 100 test_identifiers: A list of tuples, each identifies what test case to 101 run on what test class. 102 repeat: Number of times to iterate the specified tests. 103 104 Returns: 105 True if all test runs executed successfully, False otherwise. 106 """ 107 ok = True 108 for c in parsed_configs: 109 try: 110 ret = _run_test(c, test_identifiers, repeat) 111 ok = ok and ret 112 except Exception as e: 113 print("Exception occurred when executing test bed %s. %s" % 114 (c.testbed_name, e)) 115 return ok 116 117 118def main(): 119 """This is the default implementation of a cli entry point for ACTS test 120 execution. 121 122 Or you could implement your own cli entry point using acts.config_parser 123 functions and acts.test_runner.execute_one_test_class. 124 """ 125 parser = argparse.ArgumentParser( 126 description=("Specify tests to run. If nothing specified, " 127 "run all test cases found.")) 128 parser.add_argument('-c', 129 '--config', 130 type=str, 131 required=True, 132 metavar="<PATH>", 133 help="Path to the test configuration file.") 134 parser.add_argument( 135 '-ci', 136 '--campaign_iterations', 137 metavar="<CAMPAIGN_ITERATIONS>", 138 nargs='?', 139 type=int, 140 const=1, 141 default=1, 142 help="Number of times to run the campaign or a group of test cases.") 143 parser.add_argument('-tb', 144 '--testbed', 145 nargs='+', 146 type=str, 147 metavar="[<TEST BED NAME1> <TEST BED NAME2> ...]", 148 help="Specify which test beds to run tests on.") 149 parser.add_argument('-lp', 150 '--logpath', 151 type=str, 152 metavar="<PATH>", 153 help="Root path under which all logs will be placed.") 154 parser.add_argument( 155 '-tp', 156 '--testpaths', 157 nargs='*', 158 type=str, 159 metavar="<PATH> <PATH>", 160 help="One or more non-recursive test class search paths.") 161 162 group = parser.add_mutually_exclusive_group(required=True) 163 group.add_argument('-tc', 164 '--testclass', 165 nargs='+', 166 type=str, 167 metavar="[TestClass1 TestClass2:test_xxx ...]", 168 help="A list of test classes/cases to run.") 169 group.add_argument( 170 '-tf', 171 '--testfile', 172 nargs=1, 173 type=str, 174 metavar="<PATH>", 175 help=("Path to a file containing a comma delimited list of test " 176 "classes to run.")) 177 parser.add_argument('-ti', 178 '--test_case_iterations', 179 metavar="<TEST_CASE_ITERATIONS>", 180 nargs='?', 181 type=int, 182 help="Number of times to run every test case.") 183 184 args = parser.parse_args(sys.argv[1:]) 185 test_list = None 186 if args.testfile: 187 test_list = config_parser.parse_test_file(args.testfile[0]) 188 elif args.testclass: 189 test_list = args.testclass 190 if re.search(r'\.ya?ml$', args.config): 191 parsed_configs = mobly_config_parser.load_test_config_file( 192 args.config, args.testbed) 193 else: 194 parsed_configs = config_parser.load_test_config_file( 195 args.config, args.testbed) 196 197 for test_run_config in parsed_configs: 198 if args.testpaths: 199 tp_key = keys.Config.key_test_paths.value 200 test_run_config.controller_configs[tp_key] = args.testpaths 201 if args.logpath: 202 test_run_config.log_path = args.logpath 203 if args.test_case_iterations: 204 ti_key = keys.Config.key_test_case_iterations.value 205 test_run_config.user_params[ti_key] = args.test_case_iterations 206 207 # Sets the --testpaths flag to the default test directory if left unset. 208 testpath_key = keys.Config.key_test_paths.value 209 if (testpath_key not in test_run_config.controller_configs 210 or test_run_config.controller_configs[testpath_key] is None): 211 test_run_config.controller_configs[testpath_key] = utils.abs_path( 212 utils.os.path.join(os.path.dirname(__file__), 213 '../../../../acts_tests/tests/')) 214 215 # TODO(markdr): Find a way to merge this with the validation done in 216 # Mobly's load_test_config_file. 217 if not test_run_config.log_path: 218 raise ActsConfigError("Required key %s missing in test config." % 219 keys.Config.key_log_path.value) 220 test_run_config.log_path = utils.abs_path(test_run_config.log_path) 221 222 # Prepare args for test runs 223 test_identifiers = config_parser.parse_test_list(test_list) 224 225 exec_result = _run_tests(parsed_configs, test_identifiers, 226 args.campaign_iterations) 227 if exec_result is False: 228 # return 1 upon test failure. 229 sys.exit(1) 230 sys.exit(0) 231 232 233if __name__ == "__main__": 234 main() 235