| 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 |