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 |
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 EVERYTHING = object() | |
Vadim Sh.
2014/06/27 21:40:08
It's a not very descriptive name. every what?
ALL
iannucci
2014/06/28 16:22:17
Done.
| |
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 ) | |
Vadim Sh.
2014/06/27 21:40:08
nit: keep ) on previous line
or move ) on line 91
iannucci
2014/06/28 16:22:17
Done.
| |
97 | |
86 sp.add_argument( | 98 sp.add_argument( |
87 '--test_list', metavar='FILE', | 99 '--test_list', metavar='FILE', |
88 help='take the list of test globs from the FILE (use "-" for stdin)' | 100 help='take the list of test globs from the FILE (use "-" for stdin)' |
89 ).completer = lambda **_: [] | 101 ).completer = lambda **_: [] |
90 | 102 |
91 sp.add_argument( | 103 sp.add_argument( |
92 '--html_report', metavar='DIR', | 104 '--html_report', metavar='DIR', |
93 help='directory to write html report (default: disabled)' | 105 help='directory to write html report (default: disabled)' |
94 ).completer = lambda **_: [] | 106 ).completer = lambda **_: [] |
95 | 107 |
96 sp.add_argument( | 108 sp.add_argument( |
97 'test_glob', nargs='*', help=( | 109 'test_glob', nargs='*', help=( |
98 'glob to filter the tests acted on. If the glob begins with "-" ' | 110 '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 ' | 111 '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 ' | 112 'will be skipped. If a glob doesn\'t have "*" in it, "*" will be ' |
101 'implicitly appended to the end') | 113 'implicitly appended to the end') |
102 ).completer = _test_completer(test_gen) | 114 ).completer = _test_completer(test_modules) |
103 | 115 |
104 opts = parser.parse_args(args) | 116 opts = parser.parse_args(args) |
105 | 117 |
106 if not hasattr(opts, 'jobs'): | 118 if not hasattr(opts, 'jobs'): |
107 opts.jobs = 0 | 119 opts.jobs = 0 |
108 elif opts.jobs < 1: | 120 elif opts.jobs < 1: |
109 parser.error('--jobs was less than 1') | 121 parser.error('--jobs was less than 1') |
110 | 122 |
111 if opts.test_list: | 123 if opts.test_list: |
112 fh = sys.stdin if opts.test_list == '-' else open(opts.test_list, 'rb') | 124 fh = sys.stdin if opts.test_list == '-' else open(opts.test_list, 'rb') |
113 with fh as tl: | 125 with fh as tl: |
114 opts.test_glob += [l.strip() for l in tl.readlines()] | 126 opts.test_glob += [l.strip() for l in tl.readlines()] |
115 | 127 |
116 opts.handler = HANDLERS[opts.mode] | 128 opts.handler = HANDLERS[opts.mode] |
117 | 129 |
118 del opts.test_list | 130 del opts.test_list |
119 del opts.mode | 131 del opts.mode |
120 | 132 |
121 return opts | 133 return opts |
122 | 134 |
123 | 135 |
124 def main(name, test_gen, cover_branches=False, args=None): | 136 def get_test_gens(test_modules): |
137 test_gens = [] | |
138 if not test_modules or test_modules is EVERYTHING: | |
139 # if we're running directly | |
140 if __name__ == '__main__' or test_modules is EVERYTHING: | |
141 test_modules = [] | |
142 for importer, modname, ispkg in pkgutil.walk_packages(path=['.']): | |
143 if not ispkg and modname.endswith('_test'): | |
144 if modname in sys.modules: | |
145 test_modules.append(sys.modules[modname]) | |
146 else: | |
147 test_modules.append( | |
148 importer.find_module(modname).load_module(modname)) | |
149 else: # a wrapper main() script | |
150 test_modules = [sys.modules['__main__']] | |
151 for mod in test_modules: | |
152 for obj in mod.__dict__.values(): | |
153 if getattr(obj, '_expect_tests_generator', False): | |
154 test_gens.append(obj) | |
155 elif _is_unittest(obj): | |
156 test_gens.append(UnittestTestCase(obj)) | |
157 return test_gens | |
158 | |
159 | |
160 # TODO(iannucci): have Test determine cover_branches | |
161 def main(cover_branches=False, test_modules=None, args=None): | |
125 """Entry point for tests using expect_tests. | 162 """Entry point for tests using expect_tests. |
126 | 163 |
127 Example: | 164 Example: |
128 import expect_tests | 165 import expect_tests |
129 | 166 |
130 def happy_fn(val): | 167 def happy_fn(val): |
131 # Usually you would return data which is the result of some deterministic | 168 # Usually you would return data which is the result of some deterministic |
132 # computation. | 169 # computation. |
133 return expect_tests.Result({'neet': '%s string value' % val}) | 170 return expect_tests.Result({'neet': '%s string value' % val}) |
134 | 171 |
135 def Gen(): | 172 def Gen(): |
Vadim Sh.
2014/06/27 21:40:08
You seemed to converge on convention to use Capita
iannucci
2014/06/28 16:22:17
I think it's just inconsistent. The idea was Camel
pgervais
2014/06/30 13:56:03
Don't we have a style guide for this? I really don
Vadim Sh.
2014/06/30 17:15:46
+1 to keeping single naming convention for public
| |
136 yield expect_tests.Test('happy', happy_fn, args=('happy',)) | 173 yield expect_tests.Test('happy', happy_fn, args=('happy',)) |
137 | 174 |
138 if __name__ == '__main__': | 175 if __name__ == '__main__': |
139 expect_tests.main('happy_test_suite', Gen) | 176 expect_tests.main('happy_test_suite', Gen) |
140 | 177 |
141 @param name: Name of the test suite. | 178 @param test_modules: Modules containing expect_tests generators and/or |
142 @param test_gen: A Generator which yields Test objects. | 179 unittests. Defaults to the __main__ module, or if |
180 this script is invoked directly, all '_test' modules | |
181 under the current working directory. | |
143 @param cover_branches: Include branch coverage data (rather than just line | 182 @param cover_branches: Include branch coverage data (rather than just line |
144 coverage) | 183 coverage) |
145 @param args: Commandline args (starting at argv[1]) | 184 @param args: Commandline args (starting at argv[1]) |
146 """ | 185 """ |
147 try: | 186 try: |
148 opts = _parse_args(args, test_gen) | 187 opts = _parse_args(args, test_modules) |
149 | 188 |
150 cover_ctx = CoverageContext(name, cover_branches, opts.html_report, | 189 cover_ctx = CoverageContext(cover_branches, opts.html_report, |
151 not opts.handler.SKIP_RUNLOOP) | 190 not opts.handler.SKIP_RUNLOOP) |
152 | 191 |
192 with cover_ctx.create_subprocess_context(): | |
193 test_gens = get_test_gens(test_modules) | |
194 | |
153 error, killed = result_loop( | 195 error, killed = result_loop( |
154 test_gen, cover_ctx.create_subprocess_context(), opts) | 196 test_gens, cover_ctx.create_subprocess_context(), opts) |
155 | 197 |
156 cover_ctx.cleanup() | 198 cover_ctx.cleanup() |
157 if not killed and not opts.test_glob: | 199 if not killed and (opts.force_coverage or not opts.test_glob): |
158 if not cover_ctx.report(opts.verbose): | 200 if not cover_ctx.report(opts.verbose): |
159 sys.exit(2) | 201 sys.exit(2) |
160 | 202 |
161 sys.exit(error or killed) | 203 sys.exit(error or killed) |
162 except KeyboardInterrupt: | 204 except KeyboardInterrupt: |
163 pass | 205 pass |
OLD | NEW |