| 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 |
| 11 crossed with every platform, and spit out the as_json() representation to | 11 crossed with every platform, and spit out the as_json() representation to |
| 12 ../recipe_configs_test | 12 ../recipe_configs_test |
| 13 | 13 |
| 14 You must have 100% coverage of ../recipe_configs.py for this test to pass. | 14 You must have 100% coverage of ../recipe_configs.py for this test to pass. |
| 15 """ | 15 """ |
| 16 | 16 |
| 17 import argparse | 17 import argparse |
| 18 import multiprocessing | 18 import multiprocessing |
| 19 import os | 19 import os |
| 20 import sys | 20 import sys |
| 21 import traceback | 21 import traceback |
| 22 from itertools import product, imap | 22 from itertools import product, imap |
| 23 | 23 |
| 24 import test_env # "relative import" pylint: disable=W0403,W0611 | 24 from . import recipe_loader |
| 25 | 25 from . import recipe_util |
| 26 from slave import recipe_loader | 26 from . import recipe_config |
| 27 from slave import recipe_util | |
| 28 | 27 |
| 29 import coverage | 28 import coverage |
| 30 | 29 |
| 31 SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__)) | 30 SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__)) |
| 32 SLAVE_DIR = os.path.abspath(os.path.join(SCRIPT_PATH, os.pardir)) | 31 SLAVE_DIR = os.path.abspath(os.path.join(SCRIPT_PATH, os.pardir)) |
| 33 | 32 |
| 34 COVERAGE = (lambda: coverage.coverage( | 33 UNIVERSE = None |
| 35 include=[os.path.join(x, '*', '*config.py') | 34 COVERAGE = None |
| 36 for x in recipe_util.MODULE_DIRS()], | |
| 37 data_file='.recipe_configs_test_coverage', data_suffix=True))() | |
| 38 | 35 |
| 39 def covered(fn, *args, **kwargs): | 36 def covered(fn, *args, **kwargs): |
| 40 COVERAGE.start() | 37 COVERAGE.start() |
| 41 try: | 38 try: |
| 42 return fn(*args, **kwargs) | 39 return fn(*args, **kwargs) |
| 43 finally: | 40 finally: |
| 44 COVERAGE.stop() | 41 COVERAGE.stop() |
| 45 | 42 |
| 46 UNIVERSE = recipe_loader.RecipeUniverse() | |
| 47 | 43 |
| 48 def load_recipe_modules(): | 44 def load_recipe_modules(): |
| 49 modules = {} | 45 modules = {} |
| 50 for modpath in recipe_loader.loop_over_recipe_modules(): | 46 for modpath in UNIVERSE.loop_over_recipe_modules(): |
| 51 # That's right, we're using the path as the local name! The local | 47 # 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. | 48 # name really could be anything unique, we don't use it. |
| 53 modules[modpath] = UNIVERSE.load(recipe_loader.PathDependency( | 49 modules[modpath] = UNIVERSE.load(recipe_loader.PathDependency( |
| 54 modpath, local_name=modpath, base_path=os.curdir)) | 50 modpath, local_name=modpath, base_path=os.curdir, universe=UNIVERSE)) |
| 55 return modules | 51 return modules |
| 56 | 52 |
| 57 | 53 |
| 58 RECIPE_MODULES = None | 54 RECIPE_MODULES = None |
| 59 def init_recipe_modules(): | 55 def init_recipe_modules(): |
| 60 global RECIPE_MODULES | 56 global RECIPE_MODULES |
| 61 RECIPE_MODULES = covered(load_recipe_modules) | 57 RECIPE_MODULES = covered(load_recipe_modules) |
| 62 | 58 |
| 63 from slave import recipe_config # pylint: disable=F0401 | |
| 64 | |
| 65 | 59 |
| 66 def evaluate_configurations(args): | 60 def evaluate_configurations(args): |
| 67 mod_id, var_assignments = args | 61 mod_id, var_assignments = args |
| 68 mod = RECIPE_MODULES[mod_id] | 62 mod = RECIPE_MODULES[mod_id] |
| 69 ctx = mod.CONFIG_CTX | 63 ctx = mod.CONFIG_CTX |
| 70 | 64 |
| 71 config_name = None | 65 config_name = None |
| 72 try: | 66 try: |
| 73 make_item = lambda: covered(ctx.CONFIG_SCHEMA, **var_assignments) | 67 make_item = lambda: covered(ctx.CONFIG_SCHEMA, **var_assignments) |
| 74 | 68 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 132 pool = multiprocessing.Pool(initializer=multiprocessing_init) | 126 pool = multiprocessing.Pool(initializer=multiprocessing_init) |
| 133 try: | 127 try: |
| 134 return pool.map_async(fn, combination_generator).get(999999) | 128 return pool.map_async(fn, combination_generator).get(999999) |
| 135 finally: | 129 finally: |
| 136 # necessary so that the subprocesses will write out their coverage due to | 130 # necessary so that the subprocesses will write out their coverage due to |
| 137 # the hack in multiprocessing_init() | 131 # the hack in multiprocessing_init() |
| 138 pool.close() | 132 pool.close() |
| 139 pool.join() | 133 pool.join() |
| 140 | 134 |
| 141 | 135 |
| 142 def main(): | 136 def main(universe): |
| 137 global UNIVERSE |
| 138 global COVERAGE |
| 139 UNIVERSE = universe |
| 140 COVERAGE = coverage.coverage( |
| 141 include=[os.path.join(x, '*', '*config.py') |
| 142 for x in UNIVERSE.module_dirs], |
| 143 data_file='.recipe_configs_test_coverage', data_suffix=True) |
| 143 COVERAGE.erase() | 144 COVERAGE.erase() |
| 144 init_recipe_modules() | 145 init_recipe_modules() |
| 145 | 146 |
| 146 success = all(coverage_parallel_map(evaluate_configurations)) | 147 success = all(coverage_parallel_map(evaluate_configurations)) |
| 147 | 148 |
| 148 COVERAGE.combine() | 149 COVERAGE.combine() |
| 149 total_covered = COVERAGE.report() | 150 total_covered = COVERAGE.report() |
| 150 all_covered = total_covered == 100.0 | 151 all_covered = total_covered == 100.0 |
| 151 | 152 |
| 152 if not success: | 153 if not success: |
| 153 print 'FATAL: Some recipe configuration(s) failed' | 154 print 'FATAL: Some recipe configuration(s) failed' |
| 154 if not all_covered: | 155 if not all_covered: |
| 155 print 'FATAL: Recipes configs are not at 100% coverage.' | 156 print 'FATAL: Recipes configs are not at 100% coverage.' |
| 156 | 157 |
| 157 return 1 if (not success or not all_covered) else 0 | 158 return 1 if (not success or not all_covered) else 0 |
| 158 | 159 |
| 159 | 160 |
| 160 if __name__ == '__main__': | 161 if __name__ == '__main__': |
| 161 sys.exit(main()) | 162 sys.exit(main()) |
| OLD | NEW |