OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import argparse | 5 import argparse |
6 import multiprocessing | 6 import multiprocessing |
| 7 import pkgutil |
7 import sys | 8 import sys |
8 | 9 |
9 from .cover import CoverageContext | 10 from .cover import CoverageContext |
10 | 11 |
11 from . import handle_list, handle_debug, handle_train, handle_test | 12 from . import handle_list, handle_debug, handle_train, handle_test, util |
12 | 13 |
13 from .pipeline import result_loop | 14 from .pipeline import result_loop |
14 | 15 |
| 16 from .unittest_helper import _is_unittest, UnittestTestCase |
| 17 |
| 18 |
| 19 ALL_MODULES = object() |
| 20 |
15 | 21 |
16 HANDLERS = { | 22 HANDLERS = { |
17 'list': handle_list.ListHandler, | 23 'list': handle_list.ListHandler, |
18 'debug': handle_debug.DebugHandler, | 24 'debug': handle_debug.DebugHandler, |
19 'train': handle_train.TrainHandler, | 25 'train': handle_train.TrainHandler, |
20 'test': handle_test.TestHandler, | 26 'test': handle_test.TestHandler, |
21 } | 27 } |
22 | 28 |
23 | 29 |
24 class _test_completer(object): | 30 class _test_completer(object): |
25 """Implements the argcomplete completer interface for the test_glob command | 31 """Implements the argcomplete completer interface for the test_glob command |
26 line argument. | 32 line argument. |
27 | 33 |
28 See: https://pypi.python.org/pypi/argcomplete | 34 See: https://pypi.python.org/pypi/argcomplete |
29 | 35 |
30 This is automatically wired up if you have enabled bash completion in the | 36 This is automatically wired up if you have enabled bash completion in the |
31 infra repo: https://chromium.googlesource.com/infra/infra | 37 infra repo: https://chromium.googlesource.com/infra/infra |
32 """ | 38 """ |
33 class FakeOptions(object): | 39 class FakeOptions(object): |
34 def __init__(self, **kwargs): | 40 def __init__(self, **kwargs): |
35 for k, v in kwargs.iteritems(): | 41 for k, v in kwargs.iteritems(): |
36 setattr(self, k, v) | 42 setattr(self, k, v) |
37 | 43 |
38 def __init__(self, gen): | 44 def __init__(self, test_modules): |
39 self._gen = gen | 45 self._test_modules = test_modules |
40 | 46 |
41 def __call__(self, prefix, **_): | 47 def __call__(self, prefix, **_): |
42 handle_list.ListHandler.COMPLETION_LIST = [] | 48 handle_list.ListHandler.COMPLETION_LIST = [] |
43 options = self.FakeOptions( | 49 options = self.FakeOptions( |
44 handler=handle_list.ListHandler, | 50 handler=handle_list.ListHandler, |
45 test_glob=[prefix], | 51 test_glob=[prefix], |
46 jobs=1, | 52 jobs=1, |
47 ) | 53 ) |
48 ctx = CoverageContext('', [], [], False, None, None, False) | 54 ctx = CoverageContext(False, False, False) |
49 result_loop(self._gen, ctx.create_subprocess_context(), options) | 55 test_gens = get_test_gens(self._test_modules) |
| 56 result_loop(test_gens, ctx.create_subprocess_context(), options) |
50 return handle_list.ListHandler.COMPLETION_LIST | 57 return handle_list.ListHandler.COMPLETION_LIST |
51 | 58 |
52 | 59 |
53 def _parse_args(args, test_gen): | 60 def _parse_args(args, test_modules): |
54 args = args or sys.argv[1:] | 61 args = args or sys.argv[1:] |
55 | 62 |
56 # Set the default mode if not specified and not passing --help | 63 # Set the default mode if not specified and not passing --help |
57 search_names = set(HANDLERS.keys() + ['-h', '--help']) | 64 search_names = set(HANDLERS.keys() + ['-h', '--help']) |
58 if not any(arg in search_names for arg in args): | 65 if not any(arg in search_names for arg in args): |
59 args.insert(0, 'test') | 66 args.insert(0, 'test') |
60 | 67 |
61 parser = argparse.ArgumentParser() | 68 parser = argparse.ArgumentParser() |
62 subparsers = parser.add_subparsers( | 69 subparsers = parser.add_subparsers( |
63 title='Mode (default "test")', dest='mode', | 70 title='Mode (default "test")', dest='mode', |
(...skipping 12 matching lines...) Expand all Loading... |
76 help='be quiet (only print failures)') | 83 help='be quiet (only print failures)') |
77 mg.add_argument( | 84 mg.add_argument( |
78 '--verbose', action='store_true', help='be verbose') | 85 '--verbose', action='store_true', help='be verbose') |
79 | 86 |
80 if not h.SKIP_RUNLOOP: | 87 if not h.SKIP_RUNLOOP: |
81 sp.add_argument( | 88 sp.add_argument( |
82 '--jobs', metavar='N', type=int, | 89 '--jobs', metavar='N', type=int, |
83 default=multiprocessing.cpu_count(), | 90 default=multiprocessing.cpu_count(), |
84 help='run N jobs in parallel (default %(default)s)') | 91 help='run N jobs in parallel (default %(default)s)') |
85 | 92 |
| 93 sp.add_argument( |
| 94 '--force_coverage', action='store_true', |
| 95 help='Enable coverage report even when specifying a test filter.') |
| 96 |
86 sp.add_argument( | 97 sp.add_argument( |
87 '--test_list', metavar='FILE', | 98 '--test_list', metavar='FILE', |
88 help='take the list of test globs from the FILE (use "-" for stdin)' | 99 help='take the list of test globs from the FILE (use "-" for stdin)' |
89 ).completer = lambda **_: [] | 100 ).completer = lambda **_: [] |
90 | 101 |
91 sp.add_argument( | 102 sp.add_argument( |
92 '--html_report', metavar='DIR', | 103 '--html_report', metavar='DIR', |
93 help='directory to write html report (default: disabled)' | 104 help='directory to write html report (default: disabled)' |
94 ).completer = lambda **_: [] | 105 ).completer = lambda **_: [] |
95 | 106 |
96 sp.add_argument( | 107 sp.add_argument( |
97 'test_glob', nargs='*', help=( | 108 'test_glob', nargs='*', help=( |
98 'glob to filter the tests acted on. If the glob begins with "-" ' | 109 'glob to filter the tests acted on. If the glob begins with "-" ' |
99 'then it acts as a negation glob and anything which matches it ' | 110 'then it acts as a negation glob and anything which matches it ' |
100 'will be skipped. If a glob doesn\'t have "*" in it, "*" will be ' | 111 'will be skipped. If a glob doesn\'t have "*" in it, "*" will be ' |
101 'implicitly appended to the end') | 112 'implicitly appended to the end') |
102 ).completer = _test_completer(test_gen) | 113 ).completer = _test_completer(test_modules) |
103 | 114 |
104 opts = parser.parse_args(args) | 115 opts = parser.parse_args(args) |
105 | 116 |
106 if not hasattr(opts, 'jobs'): | 117 if not hasattr(opts, 'jobs'): |
107 opts.jobs = 0 | 118 opts.jobs = 0 |
108 elif opts.jobs < 1: | 119 elif opts.jobs < 1: |
109 parser.error('--jobs was less than 1') | 120 parser.error('--jobs was less than 1') |
110 | 121 |
111 if opts.test_list: | 122 if opts.test_list: |
112 fh = sys.stdin if opts.test_list == '-' else open(opts.test_list, 'rb') | 123 fh = sys.stdin if opts.test_list == '-' else open(opts.test_list, 'rb') |
113 with fh as tl: | 124 with fh as tl: |
114 opts.test_glob += [l.strip() for l in tl.readlines()] | 125 opts.test_glob += [l.strip() for l in tl.readlines()] |
115 | 126 |
116 opts.handler = HANDLERS[opts.mode] | 127 opts.handler = HANDLERS[opts.mode] |
117 | 128 |
118 del opts.test_list | 129 del opts.test_list |
119 del opts.mode | 130 del opts.mode |
120 | 131 |
121 return opts | 132 return opts |
122 | 133 |
123 | 134 |
124 def main(name, test_gen, cover_branches=False, args=None): | 135 def get_test_gens(test_modules): |
| 136 test_gens = [] |
| 137 if not test_modules or test_modules is ALL_MODULES: |
| 138 # if we're running directly |
| 139 if __name__ == '__main__' or test_modules is ALL_MODULES: |
| 140 test_modules = [] |
| 141 for importer, modname, ispkg in pkgutil.walk_packages(path=['.']): |
| 142 if not ispkg and modname.endswith('_test'): |
| 143 if modname in sys.modules: |
| 144 test_modules.append(sys.modules[modname]) |
| 145 else: |
| 146 test_modules.append( |
| 147 importer.find_module(modname).load_module(modname)) |
| 148 else: # a wrapper main() script |
| 149 test_modules = [sys.modules['__main__']] |
| 150 for mod in test_modules: |
| 151 for obj in mod.__dict__.values(): |
| 152 if util.is_test_generator(obj): |
| 153 test_gens.append(obj) |
| 154 elif _is_unittest(obj): |
| 155 test_gens.append(UnittestTestCase(obj)) |
| 156 return test_gens |
| 157 |
| 158 |
| 159 # TODO(iannucci): have Test determine cover_branches |
| 160 def main(cover_branches=False, test_modules=None, args=None): |
125 """Entry point for tests using expect_tests. | 161 """Entry point for tests using expect_tests. |
126 | 162 |
127 Example: | 163 Example: |
128 import expect_tests | 164 >>> import expect_tests |
129 | 165 |
130 def happy_fn(val): | 166 >>> def happy_fn(val): |
131 # Usually you would return data which is the result of some deterministic | 167 >>> # Usually you would return data which is the result of some |
132 # computation. | 168 >>> #deterministic computation. |
133 return expect_tests.Result({'neet': '%s string value' % val}) | 169 >>> return expect_tests.Result({'neet': '%s string value' % val}) |
134 | 170 |
135 def Gen(): | 171 >>> @expect_tests.test_generator |
136 yield expect_tests.Test('happy', happy_fn, args=('happy',)) | 172 >>> def gen(): |
| 173 >>> yield expect_tests.Test( |
| 174 >>> __package__ + '.happy', |
| 175 >>> expect_tests.FuncCall(happy_fn, 'happy')) |
137 | 176 |
138 if __name__ == '__main__': | 177 >>> if __name__ == '__main__': |
139 expect_tests.main('happy_test_suite', Gen) | 178 >>> expect_tests.main(cover_branches=True) |
140 | 179 |
141 @param name: Name of the test suite. | |
142 @param test_gen: A Generator which yields Test objects. | |
143 @param cover_branches: Include branch coverage data (rather than just line | 180 @param cover_branches: Include branch coverage data (rather than just line |
144 coverage) | 181 coverage) |
| 182 @param test_modules: Modules containing expect_tests generators and/or |
| 183 unittests. Defaults to the __main__ module, or if |
| 184 this script is invoked directly, all '_test' modules |
| 185 under the current working directory. |
145 @param args: Commandline args (starting at argv[1]) | 186 @param args: Commandline args (starting at argv[1]) |
146 """ | 187 """ |
147 try: | 188 try: |
148 opts = _parse_args(args, test_gen) | 189 opts = _parse_args(args, test_modules) |
149 | 190 |
150 cover_ctx = CoverageContext(name, cover_branches, opts.html_report, | 191 cover_ctx = CoverageContext(cover_branches, opts.html_report, |
151 not opts.handler.SKIP_RUNLOOP) | 192 not opts.handler.SKIP_RUNLOOP) |
152 | 193 |
| 194 with cover_ctx.create_subprocess_context(): |
| 195 test_gens = get_test_gens(test_modules) |
| 196 |
153 error, killed = result_loop( | 197 error, killed = result_loop( |
154 test_gen, cover_ctx.create_subprocess_context(), opts) | 198 test_gens, cover_ctx.create_subprocess_context(), opts) |
155 | 199 |
156 cover_ctx.cleanup() | 200 cover_ctx.cleanup() |
157 if not killed and not opts.test_glob: | 201 if not killed and (opts.force_coverage or not opts.test_glob): |
158 if not cover_ctx.report(opts.verbose): | 202 if not cover_ctx.report(opts.verbose): |
159 sys.exit(2) | 203 sys.exit(2) |
160 | 204 |
161 sys.exit(error or killed) | 205 sys.exit(error or killed) |
162 except KeyboardInterrupt: | 206 except KeyboardInterrupt: |
163 pass | 207 pass |
OLD | NEW |