| Index: scripts/slave/unittests/recipe_configs_test.py
|
| diff --git a/scripts/slave/unittests/recipe_configs_test.py b/scripts/slave/unittests/recipe_configs_test.py
|
| index 6c258a98645ec59dc6c509a3f531a7b59ab02653..b977d29b6b475963db9a519ecdfae4988aa0d16b 100755
|
| --- a/scripts/slave/unittests/recipe_configs_test.py
|
| +++ b/scripts/slave/unittests/recipe_configs_test.py
|
| @@ -1,161 +1,15 @@
|
| -#!/usr/bin/python
|
| -# Copyright 2013 The Chromium Authors. All rights reserved.
|
| +#!/usr/bin/env python
|
| +# Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
| # Use of this source code is governed by a BSD-style license that can be
|
| # found in the LICENSE file.
|
|
|
| -"""Provides test coverage for common recipe configurations.
|
| -
|
| -recipe config expectations are located in ../recipe_configs_test/*.expected
|
| -
|
| -In training mode, this will loop over every config item in ../recipe_configs.py
|
| -crossed with every platform, and spit out the as_json() representation to
|
| -../recipe_configs_test
|
| -
|
| -You must have 100% coverage of ../recipe_configs.py for this test to pass.
|
| -"""
|
| -
|
| -import argparse
|
| -import multiprocessing
|
| import os
|
| import sys
|
| -import traceback
|
| -from itertools import product, imap
|
| -
|
| -import test_env # "relative import" pylint: disable=W0403,W0611
|
| -
|
| -from slave import recipe_loader
|
| -from slave import recipe_util
|
| -
|
| -import coverage
|
| -
|
| -SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__))
|
| -SLAVE_DIR = os.path.abspath(os.path.join(SCRIPT_PATH, os.pardir))
|
| -
|
| -COVERAGE = (lambda: coverage.coverage(
|
| - include=[os.path.join(x, '*', '*config.py')
|
| - for x in recipe_util.MODULE_DIRS()],
|
| - data_file='.recipe_configs_test_coverage', data_suffix=True))()
|
| -
|
| -def covered(fn, *args, **kwargs):
|
| - COVERAGE.start()
|
| - try:
|
| - return fn(*args, **kwargs)
|
| - finally:
|
| - COVERAGE.stop()
|
| -
|
| -UNIVERSE = recipe_loader.RecipeUniverse()
|
| -
|
| -def load_recipe_modules():
|
| - modules = {}
|
| - for modpath in recipe_loader.loop_over_recipe_modules():
|
| - # That's right, we're using the path as the local name! The local
|
| - # name really could be anything unique, we don't use it.
|
| - modules[modpath] = UNIVERSE.load(recipe_loader.PathDependency(
|
| - modpath, local_name=modpath, base_path=os.curdir))
|
| - return modules
|
| -
|
| -
|
| -RECIPE_MODULES = None
|
| -def init_recipe_modules():
|
| - global RECIPE_MODULES
|
| - RECIPE_MODULES = covered(load_recipe_modules)
|
| -
|
| -from slave import recipe_config # pylint: disable=F0401
|
| -
|
| -
|
| -def evaluate_configurations(args):
|
| - mod_id, var_assignments = args
|
| - mod = RECIPE_MODULES[mod_id]
|
| - ctx = mod.CONFIG_CTX
|
| -
|
| - config_name = None
|
| - try:
|
| - make_item = lambda: covered(ctx.CONFIG_SCHEMA, **var_assignments)
|
| -
|
| - # Try ROOT_CONFIG_ITEM first. If it raises BadConf, then we can skip
|
| - # this config.
|
| - root_item = ctx.ROOT_CONFIG_ITEM
|
| - if root_item:
|
| - config_name = root_item.__name__
|
| - try:
|
| - result = covered(root_item, make_item())
|
| - if result.complete():
|
| - covered(result.as_jsonish)
|
| - except recipe_config.BadConf, e:
|
| - pass # This is a possibly expected failure mode.
|
| -
|
| - for config_name, fn in ctx.CONFIG_ITEMS.iteritems():
|
| - if fn.NO_TEST or fn.IS_ROOT:
|
| - continue
|
| - try:
|
| - result = covered(fn, make_item())
|
| - if result.complete():
|
| - covered(result.as_jsonish)
|
| - except recipe_config.BadConf:
|
| - pass # This is a possibly expected failure mode.
|
| - return True
|
| - except Exception as e:
|
| - print ('Caught unknown exception [%s] for config name [%s] for module '
|
| - '[%s] with args %s') % (e, config_name, mod_id, var_assignments)
|
| - traceback.print_exc()
|
| - return False
|
| -
|
| -
|
| -def multiprocessing_init():
|
| - # HACK: multiprocessing doesn't work with atexit, so shim the exit functions
|
| - # instead. This allows us to save exactly one coverage file per subprocess.
|
| - # pylint: disable=W0212
|
| - real_os_exit = multiprocessing.forking.exit
|
| - def exitfn(code):
|
| - COVERAGE.save()
|
| - real_os_exit(code)
|
| - multiprocessing.forking.exit = exitfn
|
| -
|
| - # This check mirrors the logic in multiprocessing.forking.exit
|
| - if sys.platform != 'win32':
|
| - # Even though multiprocessing.forking.exit is defined, it's not used in the
|
| - # non-win32 version of multiprocessing.forking.Popen... *loss for words*
|
| - os._exit = exitfn
|
| -
|
| -
|
| -def coverage_parallel_map(fn):
|
| - combination_generator = (
|
| - (mod_id, var_assignments)
|
| - for mod_id, mod in RECIPE_MODULES.iteritems()
|
| - if mod.CONFIG_CTX
|
| - for var_assignments in imap(dict, product(*[
|
| - [(key_name, val) for val in vals]
|
| - for key_name, vals in mod.CONFIG_CTX.VAR_TEST_MAP.iteritems()
|
| - ]))
|
| - )
|
| -
|
| - pool = multiprocessing.Pool(initializer=multiprocessing_init)
|
| - try:
|
| - return pool.map_async(fn, combination_generator).get(999999)
|
| - finally:
|
| - # necessary so that the subprocesses will write out their coverage due to
|
| - # the hack in multiprocessing_init()
|
| - pool.close()
|
| - pool.join()
|
| -
|
| -
|
| -def main():
|
| - COVERAGE.erase()
|
| - init_recipe_modules()
|
| -
|
| - success = all(coverage_parallel_map(evaluate_configurations))
|
| -
|
| - COVERAGE.combine()
|
| - total_covered = COVERAGE.report()
|
| - all_covered = total_covered == 100.0
|
| -
|
| - if not success:
|
| - print 'FATAL: Some recipe configuration(s) failed'
|
| - if not all_covered:
|
| - print 'FATAL: Recipes configs are not at 100% coverage.'
|
|
|
| - return 1 if (not success or not all_covered) else 0
|
| +import test_env # pylint: disable=W0403,W0611
|
|
|
| +from recipe_engine import configs_test
|
| +from slave import recipe_universe
|
|
|
| if __name__ == '__main__':
|
| - sys.exit(main())
|
| + configs_test.main(recipe_universe.get_universe())
|
|
|