unittest_suite.py revision 78b0595e3841a41aae3c0ce20178629c42c1e3b3
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
22REQUIRES_DJANGO = set((
23        'monitor_db_unittest.py',
24        'monitor_db_functional_test.py',
25        'monitor_db_cleanup_test.py',
26        'frontend_unittest.py',
27        'csv_encoder_unittest.py',
28        'rpc_interface_unittest.py',
29        'models_test.py',
30        'scheduler_models_unittest.py',
31        'metahost_scheduler_unittest.py',
32        'site_metahost_scheduler_unittest.py',
33        'rpc_utils_unittest.py',
34        'site_rpc_utils_unittest.py',
35        'execution_engine_unittest.py',
36        'service_proxy_lib_test.py',
37        ))
38
39REQUIRES_MYSQLDB = set((
40        'migrate_unittest.py',
41        'db_utils_unittest.py',
42        ))
43
44REQUIRES_GWT = set((
45        'client_compilation_unittest.py',
46        ))
47
48REQUIRES_SIMPLEJSON = set((
49        'resources_test.py',
50        'serviceHandler_unittest.py',
51        ))
52
53REQUIRES_AUTH = set ((
54    'trigger_unittest.py',
55    ))
56
57REQUIRES_HTTPLIB2 = set((
58        ))
59
60REQUIRES_PROTOBUFS = set((
61        'job_serializer_unittest.py',
62        ))
63
64LONG_RUNTIME = set((
65    'base_barrier_unittest.py',
66    'logging_manager_test.py',
67    ))
68
69
70SKIP = set((
71    # This particular KVM autotest test is not a unittest
72    'guest_test.py',
73    'ap_configurator_test.py'
74    ))
75
76LONG_TESTS = (REQUIRES_DJANGO |
77              REQUIRES_MYSQLDB |
78              REQUIRES_GWT |
79              REQUIRES_SIMPLEJSON |
80              REQUIRES_HTTPLIB2 |
81              REQUIRES_AUTH |
82              REQUIRES_PROTOBUFS |
83              LONG_RUNTIME)
84
85
86ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
87
88
89class TestFailure(Exception):
90    """Exception type for any test failure."""
91    pass
92
93
94def run_test(mod_names, options):
95    """
96    @param mod_names: A list of individual parts of the module name to import
97            and run as a test suite.
98    @param options: optparse options.
99    """
100    if not options.debug:
101        parallel.redirect_io()
102
103    print "Running %s" % '.'.join(mod_names)
104    mod = common.setup_modules.import_module(mod_names[-1],
105                                             '.'.join(mod_names[:-1]))
106    for ut_module in [unittest, custom_unittest]:
107        test = ut_module.defaultTestLoader.loadTestsFromModule(mod)
108        suite = ut_module.TestSuite(test)
109        runner = ut_module.TextTestRunner(verbosity=2)
110        result = runner.run(suite)
111        if result.errors or result.failures:
112            msg = '%s had %d failures and %d errors.'
113            msg %= '.'.join(mod_names), len(result.failures), len(result.errors)
114            raise TestFailure(msg)
115
116
117def scan_for_modules(start, options):
118    """Scan folders and find all test modules.
119    @param start: The absolute directory to look for tests under.
120    @param options: optparse options.
121    @return a list of modules to be executed.
122    """
123    modules = []
124
125    skip_tests = SKIP
126    if options.skip_tests:
127        skip_tests.update(options.skip_tests.split())
128
129    for dirpath, subdirs, filenames in os.walk(start):
130        # Only look in and below subdirectories that are python modules.
131        if '__init__.py' not in filenames:
132            if options.full:
133                for filename in filenames:
134                    if filename.endswith('.pyc'):
135                        os.unlink(os.path.join(dirpath, filename))
136            # Skip all subdirectories below this one, it is not a module.
137            del subdirs[:]
138            if options.debug:
139                print 'Skipping', dirpath
140            continue  # Skip this directory.
141
142        # Look for unittest files.
143        for fname in filenames:
144            if fname.endswith('_unittest.py') or fname.endswith('_test.py'):
145                if not options.full and fname in LONG_TESTS:
146                    continue
147                if fname in skip_tests:
148                    continue
149                path_no_py = os.path.join(dirpath, fname).rstrip('.py')
150                assert path_no_py.startswith(ROOT)
151                names = path_no_py[len(ROOT)+1:].split('/')
152                modules.append(['autotest_lib'] + names)
153                if options.debug:
154                    print 'testing', path_no_py
155    return modules
156
157def find_and_run_tests(start, options):
158    """
159    Find and run Python unittest suites below the given directory.  Only look
160    in subdirectories of start that are actual importable Python modules.
161
162    @param start: The absolute directory to look for tests under.
163    @param options: optparse options.
164    """
165    if options.module_list:
166        modules = []
167        for m in options.module_list:
168            modules.append(m.split('.'))
169    else:
170        modules = scan_for_modules(start, options)
171
172    if options.debug:
173        print 'Number of test modules found:', len(modules)
174
175    functions = {}
176    for module_names in modules:
177        # Create a function that'll test a particular module.  module=module
178        # is a hack to force python to evaluate the params now.  We then
179        # rename the function to make error reporting nicer.
180        run_module = lambda module=module_names: run_test(module, options)
181        name = '.'.join(module_names)
182        run_module.__name__ = name
183        functions[run_module] = set()
184
185    try:
186        dargs = {}
187        if options.debug:
188            dargs['max_simultaneous_procs'] = 1
189        pe = parallel.ParallelExecute(functions, **dargs)
190        pe.run_until_completion()
191    except parallel.ParallelError, err:
192        return err.errors
193    return []
194
195
196def main():
197    """Entry point for unittest_suite.py"""
198    options, args = parser.parse_args()
199    if args:
200        options.module_list = args
201
202    # Strip the arguments off the command line, so that the unit tests do not
203    # see them.
204    del sys.argv[1:]
205
206    absolute_start = os.path.join(ROOT, options.start)
207    errors = find_and_run_tests(absolute_start, options)
208    if errors:
209        print "%d tests resulted in an error/failure:" % len(errors)
210        for error in errors:
211            print "\t%s" % error
212        print "Rerun", sys.argv[0], "--debug to see the failure details."
213        sys.exit(1)
214    else:
215        print "All passed!"
216        sys.exit(0)
217
218
219if __name__ == "__main__":
220    main()
221