Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | 2 # Copyright 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Provides test coverage for common recipe configurations. | 6 """Provides test coverage for common recipe configurations. |
| 7 | 7 |
| 8 recipe config expectations are located in ../recipe_configs_test/*.expected | 8 recipe config expectations are located in ../recipe_configs_test/*.expected |
| 9 | 9 |
| 10 In training mode, this will loop over every config item in ../recipe_configs.py | 10 In training mode, this will loop over every config item in ../recipe_configs.py |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 36 for x in recipe_util.MODULE_DIRS()], | 36 for x in recipe_util.MODULE_DIRS()], |
| 37 data_file='.recipe_configs_test_coverage', data_suffix=True))() | 37 data_file='.recipe_configs_test_coverage', data_suffix=True))() |
| 38 | 38 |
| 39 def covered(fn, *args, **kwargs): | 39 def covered(fn, *args, **kwargs): |
| 40 COVERAGE.start() | 40 COVERAGE.start() |
| 41 try: | 41 try: |
| 42 return fn(*args, **kwargs) | 42 return fn(*args, **kwargs) |
| 43 finally: | 43 finally: |
| 44 COVERAGE.stop() | 44 COVERAGE.stop() |
| 45 | 45 |
| 46 LOADER = recipe_loader.ModuleLoader() | |
| 47 | |
| 48 def load_recipe_modules(): | |
| 49 modules = {} | |
| 50 for modpath in recipe_loader.loop_over_recipe_modules(): | |
| 51 # That's right, we're using the path as the local name! The local | |
| 52 # name really could be anything unique, we don't use it. | |
| 53 modules[modpath] = LOADER.load(recipe_loader.PathDependency( | |
| 54 modpath, local_name=modpath, base_path=os.curdir)) | |
| 55 return modules | |
| 56 | |
| 57 | |
| 46 RECIPE_MODULES = None | 58 RECIPE_MODULES = None |
| 47 def init_recipe_modules(): | 59 def init_recipe_modules(): |
| 48 global RECIPE_MODULES | 60 global RECIPE_MODULES |
| 49 RECIPE_MODULES = covered(recipe_loader.load_recipe_modules, | 61 RECIPE_MODULES = covered(load_recipe_modules) |
| 50 recipe_util.MODULE_DIRS()) | |
| 51 | 62 |
| 52 from slave import recipe_config # pylint: disable=F0401 | 63 from slave import recipe_config # pylint: disable=F0401 |
| 53 | 64 |
| 54 | 65 |
| 55 def evaluate_configurations(args): | 66 def evaluate_configurations(args): |
| 56 mod_id, var_assignments, verbose = args | 67 mod_id, var_assignments, verbose = args |
| 57 mod = getattr(RECIPE_MODULES, mod_id) | 68 mod = RECIPE_MODULES[mod_id] |
| 58 ctx = mod.CONFIG_CTX | 69 ctx = mod.CONFIG_CTX |
| 59 | 70 |
| 60 config_name = None | 71 config_name = None |
| 61 try: | 72 try: |
| 62 make_item = lambda: covered(ctx.CONFIG_SCHEMA, **var_assignments) | 73 make_item = lambda: covered(ctx.CONFIG_SCHEMA, **var_assignments) |
| 63 | 74 |
| 64 # Try ROOT_CONFIG_ITEM first. If it raises BadConf, then we can skip | 75 # Try ROOT_CONFIG_ITEM first. If it raises BadConf, then we can skip |
| 65 # this config. | 76 # this config. |
| 66 root_item = ctx.ROOT_CONFIG_ITEM | 77 root_item = ctx.ROOT_CONFIG_ITEM |
| 67 if root_item: | 78 if root_item: |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 88 return True | 99 return True |
| 89 except Exception as e: | 100 except Exception as e: |
| 90 print 'Caught exception [%s] with args (%s, %s): %s' % ( | 101 print 'Caught exception [%s] with args (%s, %s): %s' % ( |
| 91 e, mod_id, var_assignments, config_name) | 102 e, mod_id, var_assignments, config_name) |
| 92 if verbose: | 103 if verbose: |
| 93 traceback.print_exc() | 104 traceback.print_exc() |
| 94 return False | 105 return False |
| 95 | 106 |
| 96 | 107 |
| 97 def multiprocessing_init(): | 108 def multiprocessing_init(): |
| 98 init_recipe_modules() | |
|
iannucci
2015/05/05 23:35:59
???????
| |
| 99 | |
| 100 # HACK: multiprocessing doesn't work with atexit, so shim the exit functions | 109 # HACK: multiprocessing doesn't work with atexit, so shim the exit functions |
| 101 # instead. This allows us to save exactly one coverage file per subprocess. | 110 # instead. This allows us to save exactly one coverage file per subprocess. |
| 102 # pylint: disable=W0212 | 111 # pylint: disable=W0212 |
| 103 real_os_exit = multiprocessing.forking.exit | 112 real_os_exit = multiprocessing.forking.exit |
| 104 def exitfn(code): | 113 def exitfn(code): |
| 105 COVERAGE.save() | 114 COVERAGE.save() |
| 106 real_os_exit(code) | 115 real_os_exit(code) |
| 107 multiprocessing.forking.exit = exitfn | 116 multiprocessing.forking.exit = exitfn |
| 108 | 117 |
| 109 # This check mirrors the logic in multiprocessing.forking.exit | 118 # This check mirrors the logic in multiprocessing.forking.exit |
| 110 if sys.platform != 'win32': | 119 if sys.platform != 'win32': |
| 111 # Even though multiprocessing.forking.exit is defined, it's not used in the | 120 # Even though multiprocessing.forking.exit is defined, it's not used in the |
| 112 # non-win32 version of multiprocessing.forking.Popen... *loss for words* | 121 # non-win32 version of multiprocessing.forking.Popen... *loss for words* |
| 113 os._exit = exitfn | 122 os._exit = exitfn |
| 114 | 123 |
| 115 | 124 |
| 116 def coverage_parallel_map(fn, verbose): | 125 def coverage_parallel_map(fn, verbose): |
| 117 combination_generator = ( | 126 combination_generator = ( |
| 118 (mod_id, var_assignments, verbose) | 127 (mod_id, var_assignments, verbose) |
| 119 for mod_id, mod in RECIPE_MODULES.__dict__.iteritems() | 128 for mod_id, mod in RECIPE_MODULES.iteritems() |
| 120 if mod_id[0] != '_' and mod.CONFIG_CTX | 129 if mod.CONFIG_CTX |
| 121 for var_assignments in imap(dict, product(*[ | 130 for var_assignments in imap(dict, product(*[ |
| 122 [(key_name, val) for val in vals] | 131 [(key_name, val) for val in vals] |
| 123 for key_name, vals in mod.CONFIG_CTX.VAR_TEST_MAP.iteritems() | 132 for key_name, vals in mod.CONFIG_CTX.VAR_TEST_MAP.iteritems() |
| 124 ])) | 133 ])) |
| 125 ) | 134 ) |
| 126 | 135 |
| 127 pool = multiprocessing.Pool(initializer=multiprocessing_init) | 136 pool = multiprocessing.Pool(initializer=multiprocessing_init) |
| 128 try: | 137 try: |
| 129 return pool.map_async(fn, combination_generator).get(999999) | 138 return pool.map_async(fn, combination_generator).get(999999) |
| 130 finally: | 139 finally: |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 152 if not success: | 161 if not success: |
| 153 print 'FATAL: Some recipe configuration(s) failed' | 162 print 'FATAL: Some recipe configuration(s) failed' |
| 154 if not all_covered: | 163 if not all_covered: |
| 155 print 'FATAL: Recipes configs are not at 100% coverage.' | 164 print 'FATAL: Recipes configs are not at 100% coverage.' |
| 156 | 165 |
| 157 return 1 if (not success or not all_covered) else 0 | 166 return 1 if (not success or not all_covered) else 0 |
| 158 | 167 |
| 159 | 168 |
| 160 if __name__ == '__main__': | 169 if __name__ == '__main__': |
| 161 sys.exit(main(sys.argv)) | 170 sys.exit(main(sys.argv)) |
| OLD | NEW |