Home | History | Annotate | Download | only in utils
      1 #!/usr/bin/python -u
      2 
      3 import os, sys, unittest, optparse
      4 import common
      5 from autotest_lib.utils import parallel
      6 from autotest_lib.client.common_lib.test_utils import unittest as custom_unittest
      7 
      8 parser = optparse.OptionParser()
      9 parser.add_option("-r", action="store", type="string", dest="start",
     10                   default='',
     11                   help="root directory to start running unittests")
     12 parser.add_option("--full", action="store_true", dest="full", default=False,
     13                   help="whether to run the shortened version of the test")
     14 parser.add_option("--debug", action="store_true", dest="debug", default=False,
     15                   help="run in debug mode")
     16 parser.add_option("--skip-tests", dest="skip_tests",  default=[],
     17                   help="A space separated list of tests to skip")
     18 
     19 parser.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 
     30 REQUIRES_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 
     54 REQUIRES_MYSQLDB = set((
     55         'migrate_unittest.py',
     56         'db_utils_unittest.py',
     57         ))
     58 
     59 REQUIRES_GWT = set((
     60         'client_compilation_unittest.py',
     61         ))
     62 
     63 REQUIRES_SIMPLEJSON = set((
     64         'resources_test.py',
     65         'serviceHandler_unittest.py',
     66         ))
     67 
     68 REQUIRES_AUTH = set ((
     69     'trigger_unittest.py',
     70     ))
     71 
     72 REQUIRES_HTTPLIB2 = set((
     73         ))
     74 
     75 REQUIRES_PROTOBUFS = set((
     76         'job_serializer_unittest.py',
     77         ))
     78 
     79 REQUIRES_SELENIUM = set((
     80         'ap_configurator_factory_unittest.py',
     81         'ap_batch_locker_unittest.py'
     82     ))
     83 
     84 LONG_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".
     92 REQUIRES_CHROOT = set((
     93     'mbim_channel_unittest',
     94     ))
     95 
     96 SKIP = 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 
    115 LONG_TESTS = (REQUIRES_MYSQLDB |
    116               REQUIRES_GWT |
    117               REQUIRES_HTTPLIB2 |
    118               REQUIRES_AUTH |
    119               REQUIRES_PROTOBUFS |
    120               REQUIRES_SELENIUM |
    121               LONG_RUNTIME)
    122 
    123 ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
    124 
    125 # The set of files in LONG_TESTS with its full path
    126 LONG_TESTS_FULL_PATH = {os.path.join(ROOT, t) for t in LONG_TESTS}
    127 
    128 class TestFailure(Exception):
    129     """Exception type for any test failure."""
    130     pass
    131 
    132 
    133 def 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 
    156 def 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 
    205 def 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 
    219 def 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 
    264 def 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 
    287 if __name__ == "__main__":
    288     main()
    289