1#!/usr/bin/env python3.4 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 17from builtins import str 18 19import argparse 20import multiprocessing 21import signal 22import sys 23import traceback 24 25from acts import config_parser 26from acts import keys 27from acts import signals 28from acts import test_runner 29 30 31def _run_test(parsed_config, test_identifiers, repeat=1): 32 """Instantiate and runs test_runner.TestRunner. 33 34 This is the function to start separate processes with. 35 36 Args: 37 parsed_config: A dict that is a set of configs for one 38 test_runner.TestRunner. 39 test_identifiers: A list of tuples, each identifies what test case to 40 run on what test class. 41 repeat: Number of times to iterate the specified tests. 42 43 Returns: 44 True if all tests passed without any error, False otherwise. 45 """ 46 runner = _create_test_runner(parsed_config, test_identifiers) 47 try: 48 for i in range(repeat): 49 runner.run() 50 return runner.results.is_all_pass 51 except signals.TestAbortAll: 52 return True 53 except: 54 print("Exception when executing %s, iteration %s." % 55 (runner.testbed_name, i)) 56 print(traceback.format_exc()) 57 finally: 58 runner.stop() 59 60 61def _create_test_runner(parsed_config, test_identifiers): 62 """Instantiates one test_runner.TestRunner object and register termination 63 signal handlers that properly shut down the test_runner.TestRunner run. 64 65 Args: 66 parsed_config: A dict that is a set of configs for one 67 test_runner.TestRunner. 68 test_identifiers: A list of tuples, each identifies what test case to 69 run on what test class. 70 71 Returns: 72 A test_runner.TestRunner object. 73 """ 74 try: 75 t = test_runner.TestRunner(parsed_config, test_identifiers) 76 except: 77 print("Failed to instantiate test runner, abort.") 78 print(traceback.format_exc()) 79 sys.exit(1) 80 # Register handler for termination signals. 81 handler = config_parser.gen_term_signal_handler([t]) 82 signal.signal(signal.SIGTERM, handler) 83 signal.signal(signal.SIGINT, handler) 84 return t 85 86 87def _run_tests_parallel(parsed_configs, test_identifiers, repeat): 88 """Executes requested tests in parallel. 89 90 Each test run will be in its own process. 91 92 Args: 93 parsed_configs: A list of dicts, each is a set of configs for one 94 test_runner.TestRunner. 95 test_identifiers: A list of tuples, each identifies what test case to 96 run on what test class. 97 repeat: Number of times to iterate the specified tests. 98 99 Returns: 100 True if all test runs executed successfully, False otherwise. 101 """ 102 print("Executing {} concurrent test runs.".format(len(parsed_configs))) 103 arg_list = [(c, test_identifiers, repeat) for c in parsed_configs] 104 results = [] 105 with multiprocessing.Pool(processes=len(parsed_configs)) as pool: 106 # Can't use starmap for py2 compatibility. One day, one day...... 107 for args in arg_list: 108 results.append(pool.apply_async(_run_test, args)) 109 pool.close() 110 pool.join() 111 for r in results: 112 if r.get() is False or isinstance(r, Exception): 113 return False 114 115 116def _run_tests_sequential(parsed_configs, test_identifiers, repeat): 117 """Executes requested tests sequentially. 118 119 Requested test runs will commence one after another according to the order 120 of their corresponding configs. 121 122 Args: 123 parsed_configs: A list of dicts, each is a set of configs for one 124 test_runner.TestRunner. 125 test_identifiers: A list of tuples, each identifies what test case to 126 run on what test class. 127 repeat: Number of times to iterate the specified tests. 128 129 Returns: 130 True if all test runs executed successfully, False otherwise. 131 """ 132 ok = True 133 for c in parsed_configs: 134 try: 135 ret = _run_test(c, test_identifiers, repeat) 136 ok = ok and ret 137 except Exception as e: 138 print("Exception occurred when executing test bed %s. %s" % 139 (c[keys.Config.key_testbed.value], e)) 140 return ok 141 142 143def main(argv): 144 """This is a sample implementation of a cli entry point for ACTS test 145 execution. 146 147 Or you could implement your own cli entry point using acts.config_parser 148 functions and acts.test_runner.execute_one_test_class. 149 """ 150 parser = argparse.ArgumentParser( 151 description=("Specify tests to run. If nothing specified, " 152 "run all test cases found.")) 153 parser.add_argument( 154 '-c', 155 '--config', 156 nargs=1, 157 type=str, 158 required=True, 159 metavar="<PATH>", 160 help="Path to the test configuration file.") 161 parser.add_argument( 162 '--test_args', 163 nargs='+', 164 type=str, 165 metavar="Arg1 Arg2 ...", 166 help=("Command-line arguments to be passed to every test case in a " 167 "test run. Use with caution.")) 168 parser.add_argument( 169 '-p', 170 '--parallel', 171 action="store_true", 172 help=("If set, tests will be executed on all testbeds in parallel. " 173 "Otherwise, tests are executed iteratively testbed by testbed.")) 174 parser.add_argument( 175 '-ci', 176 '--campaign_iterations', 177 metavar="<CAMPAIGN_ITERATIONS>", 178 nargs='?', 179 type=int, 180 const=1, 181 default=1, 182 help="Number of times to run the campaign or a group of test cases.") 183 parser.add_argument( 184 '-tb', 185 '--testbed', 186 nargs='+', 187 type=str, 188 metavar="[<TEST BED NAME1> <TEST BED NAME2> ...]", 189 help="Specify which test beds to run tests on.") 190 parser.add_argument( 191 '-lp', 192 '--logpath', 193 type=str, 194 metavar="<PATH>", 195 help="Root path under which all logs will be placed.") 196 parser.add_argument( 197 '-tp', 198 '--testpaths', 199 nargs='*', 200 type=str, 201 metavar="<PATH> <PATH>", 202 help="One or more non-recursive test class search paths.") 203 204 group = parser.add_mutually_exclusive_group(required=True) 205 group.add_argument( 206 '-tc', 207 '--testclass', 208 nargs='+', 209 type=str, 210 metavar="[TestClass1 TestClass2:test_xxx ...]", 211 help="A list of test classes/cases to run.") 212 group.add_argument( 213 '-tf', 214 '--testfile', 215 nargs=1, 216 type=str, 217 metavar="<PATH>", 218 help=("Path to a file containing a comma delimited list of test " 219 "classes to run.")) 220 parser.add_argument( 221 '-r', 222 '--random', 223 action="store_true", 224 help="If set, tests will be executed in random order.") 225 parser.add_argument( 226 '-ti', 227 '--test_case_iterations', 228 metavar="<TEST_CASE_ITERATIONS>", 229 nargs='?', 230 type=int, 231 help="Number of times to run every test case.") 232 233 args = parser.parse_args(argv) 234 test_list = None 235 if args.testfile: 236 test_list = config_parser.parse_test_file(args.testfile[0]) 237 elif args.testclass: 238 test_list = args.testclass 239 parsed_configs = config_parser.load_test_config_file( 240 args.config[0], args.testbed, args.testpaths, args.logpath, 241 args.test_args, args.random, args.test_case_iterations) 242 # Prepare args for test runs 243 test_identifiers = config_parser.parse_test_list(test_list) 244 245 # Execute test runners. 246 if args.parallel and len(parsed_configs) > 1: 247 print('Running tests in parallel.') 248 exec_result = _run_tests_parallel(parsed_configs, test_identifiers, 249 args.campaign_iterations) 250 else: 251 print('Running tests sequentially.') 252 exec_result = _run_tests_sequential(parsed_configs, test_identifiers, 253 args.campaign_iterations) 254 if exec_result is False: 255 # return 1 upon test failure. 256 sys.exit(1) 257 sys.exit(0) 258 259 260if __name__ == "__main__": 261 main(sys.argv[1:]) 262