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