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