1#!/usr/bin/python3 -u 2 3from __future__ import absolute_import 4from __future__ import division 5from __future__ import print_function 6import os, sys, unittest, optparse 7import common 8from autotest_lib.utils import parallel 9 10parser = optparse.OptionParser() 11parser.add_option("-r", action="store", type="string", dest="start", 12 default='', 13 help="root directory to start running unittests") 14parser.add_option("--full", action="store_true", dest="full", default=False, 15 help="whether to run the shortened version of the test") 16parser.add_option("--debug", action="store_true", dest="debug", default=False, 17 help="run in debug mode") 18parser.add_option("--skip-tests", dest="skip_tests", default=[], 19 help="A space separated list of tests to skip") 20 21parser.set_defaults(module_list=None) 22 23# Following sets are used to define a collection of modules that are optional 24# tests and do not need to be executed in unittest suite for various reasons. 25# Each entry can be file name or relative path that's relative to the parent 26# folder of the folder containing this file (unittest_suite.py). The list 27# will be used to filter any test file with matching name or matching full 28# path. If a file's name is too general and has a chance to collide with files 29# in other folder, it is recommended to specify its relative path here, e.g., 30# using 'mirror/trigger_unittest.py', instead of 'trigger_unittest.py' only. 31 32REQUIRES_DJANGO = set(( 33 'frontend_unittest.py', 34 'csv_encoder_unittest.py', 35 'rpc_interface_unittest.py', 36 'models_test.py', 37 'rpc_utils_unittest.py', 38 'site_rpc_utils_unittest.py', 39 'execution_engine_unittest.py', 40 'service_proxy_lib_test.py', 41 'site_parse_unittest.py', 42 )) 43 44REQUIRES_MYSQLDB = set(( 45 'migrate_unittest.py', 46 'db_utils_unittest.py', 47 )) 48 49REQUIRES_GWT = set(( 50 'client_compilation_unittest.py', 51 )) 52 53REQUIRES_SIMPLEJSON = set(( 54 'serviceHandler_unittest.py', 55 )) 56 57REQUIRES_AUTH = set (( 58 'trigger_unittest.py', 59 )) 60 61REQUIRES_HTTPLIB2 = set(( 62 )) 63 64REQUIRES_PROTOBUFS = set(( 65 'cloud_console_client_unittest.py', 66 'job_serializer_unittest.py', 67 )) 68 69REQUIRES_SELENIUM = set(( 70 'ap_configurator_factory_unittest.py', 71 'ap_batch_locker_unittest.py' 72 )) 73 74LONG_RUNTIME = set(( 75 'barrier_unittest.py', 76 'logging_manager_test.py', 77 'task_loop_unittest.py' # crbug.com/254030 78 )) 79 80# Unitests that only work in chroot. The names are for module name, thus no 81# file extension of ".py". 82REQUIRES_CHROOT = set(( 83 'mbim_channel_unittest', 84 )) 85 86SKIP = set(( 87 # This particular KVM autotest test is not a unittest 88 'guest_test.py', 89 'ap_configurator_test.py', 90 'chaos_base_test.py', 91 'chaos_interop_test.py', 92 # crbug.com/251395 93 'dev_server_test.py', 94 'full_release_test.py', 95 'scheduler_lib_unittest.py', 96 'webstore_test.py', 97 # crbug.com/432621 These files are not tests, and will disappear soon. 98 'des_01_test.py', 99 'des_02_test.py', 100 # Require lxc to be installed 101 'base_image_unittest.py', 102 'container_bucket_unittest.py', 103 'container_factory_unittest.py', 104 'container_unittest.py', 105 'lxc_functional_test.py', 106 'service_unittest.py', 107 'zygote_unittest.py', 108 )) 109 110LONG_TESTS = (REQUIRES_MYSQLDB | 111 REQUIRES_GWT | 112 REQUIRES_HTTPLIB2 | 113 REQUIRES_AUTH | 114 REQUIRES_PROTOBUFS | 115 REQUIRES_SELENIUM | 116 LONG_RUNTIME) 117 118ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 119 120# The set of files in LONG_TESTS with its full path 121LONG_TESTS_FULL_PATH = {os.path.join(ROOT, t) for t in LONG_TESTS} 122 123class TestFailure(Exception): 124 """Exception type for any test failure.""" 125 pass 126 127 128def run_test(mod_names, options): 129 """ 130 @param mod_names: A list of individual parts of the module name to import 131 and run as a test suite. 132 @param options: optparse options. 133 """ 134 if not options.debug: 135 parallel.redirect_io() 136 137 print("Running %s" % '.'.join(mod_names)) 138 mod = common.setup_modules.import_module(mod_names[-1], 139 '.'.join(mod_names[:-1])) 140 test = unittest.defaultTestLoader.loadTestsFromModule(mod) 141 suite = unittest.TestSuite(test) 142 runner = unittest.TextTestRunner(verbosity=2) 143 result = runner.run(suite) 144 if result.errors or result.failures: 145 msg = '%s had %d failures and %d errors.' 146 msg %= '.'.join(mod_names), len(result.failures), len(result.errors) 147 raise TestFailure(msg) 148 149 150def scan_for_modules(start, options): 151 """Scan folders and find all test modules that are not included in the 152 denylist (defined in LONG_TESTS). 153 154 @param start: The absolute directory to look for tests under. 155 @param options: optparse options. 156 @return a list of modules to be executed. 157 """ 158 modules = [] 159 160 skip_tests = SKIP 161 if options.skip_tests: 162 skip_tests.update(options.skip_tests.split()) 163 skip_tests_full_path = {os.path.join(ROOT, t) for t in skip_tests} 164 165 for dir_path, sub_dirs, file_names in os.walk(start): 166 # Only look in and below subdirectories that are python modules. 167 if '__init__.py' not in file_names: 168 if options.full: 169 for file_name in file_names: 170 if file_name.endswith('.pyc'): 171 os.unlink(os.path.join(dir_path, file_name)) 172 # Skip all subdirectories below this one, it is not a module. 173 del sub_dirs[:] 174 if options.debug: 175 print('Skipping', dir_path) 176 continue # Skip this directory. 177 178 # Look for unittest files. 179 for file_name in file_names: 180 if (file_name.endswith('_unittest.py') or 181 file_name.endswith('_test.py')): 182 file_path = os.path.join(dir_path, file_name) 183 if (not options.full and 184 (file_name in LONG_TESTS or 185 file_path in LONG_TESTS_FULL_PATH)): 186 continue 187 if (file_name in skip_tests or 188 file_path in skip_tests_full_path): 189 continue 190 path_no_py = os.path.join(dir_path, file_name).rstrip('.py') 191 assert path_no_py.startswith(ROOT) 192 names = path_no_py[len(ROOT)+1:].split('/') 193 modules.append(['autotest_lib'] + names) 194 if options.debug: 195 print('testing', path_no_py) 196 return modules 197 198 199def is_inside_chroot(): 200 """Check if the process is running inside the chroot. 201 202 @return: True if the process is running inside the chroot, False otherwise. 203 """ 204 return os.path.exists('/etc/cros_chroot_version') 205 206 207def find_and_run_tests(start, options): 208 """ 209 Find and run Python unittest suites below the given directory. Only look 210 in subdirectories of start that are actual importable Python modules. 211 212 @param start: The absolute directory to look for tests under. 213 @param options: optparse options. 214 """ 215 if options.module_list: 216 modules = [] 217 for m in options.module_list: 218 modules.append(m.split('.')) 219 else: 220 modules = scan_for_modules(start, options) 221 222 if options.debug: 223 print('Number of test modules found:', len(modules)) 224 225 chroot = is_inside_chroot() 226 functions = {} 227 for module_names in modules: 228 if not chroot and module_names[-1] in REQUIRES_CHROOT: 229 if options.debug: 230 print('Test %s requires to run in chroot, skipped.' % 231 module_names[-1]) 232 continue 233 # Create a function that'll test a particular module. module=module 234 # is a hack to force python to evaluate the params now. We then 235 # rename the function to make error reporting nicer. 236 run_module = lambda module=module_names: run_test(module, options) 237 name = '.'.join(module_names) 238 run_module.__name__ = name 239 functions[run_module] = set() 240 241 try: 242 dargs = {} 243 if options.debug: 244 dargs['max_simultaneous_procs'] = 1 245 pe = parallel.ParallelExecute(functions, **dargs) 246 pe.run_until_completion() 247 except parallel.ParallelError as err: 248 return err.errors 249 return [] 250 251 252def main(): 253 """Entry point for unittest_suite.py""" 254 options, args = parser.parse_args() 255 if args: 256 options.module_list = args 257 258 # Strip the arguments off the command line, so that the unit tests do not 259 # see them. 260 del sys.argv[1:] 261 262 absolute_start = os.path.join(ROOT, options.start) 263 errors = find_and_run_tests(absolute_start, options) 264 if errors: 265 print("%d tests resulted in an error/failure:" % len(errors)) 266 for error in errors: 267 print("\t%s" % error) 268 print("Rerun", sys.argv[0], "--debug to see the failure details.") 269 sys.exit(1) 270 else: 271 print("All passed!") 272 sys.exit(0) 273 274 275if __name__ == "__main__": 276 main() 277