Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: recipe_engine/loader.py

Issue 2798053003: introduce recipe_exception in result.proto (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | recipe_engine/result.proto » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 import collections 5 import collections
6 import functools
6 import imp 7 import imp
7 import inspect 8 import inspect
8 import os 9 import os
9 import sys 10 import sys
10 11
11 from . import env 12 from . import env
12 13
13 from .config import ConfigContext, ConfigGroupSchema 14 from .config import ConfigContext, ConfigGroupSchema
14 from .config_types import Path, ModuleBasePath, PackageRepoBasePath 15 from .config_types import Path, ModuleBasePath, PackageRepoBasePath
15 from .config_types import RecipeScriptBasePath 16 from .config_types import RecipeScriptBasePath
16 from .config_types import RECIPE_MODULE_PREFIX 17 from .config_types import RECIPE_MODULE_PREFIX
17 from .recipe_api import RecipeApiPlain, RecipeScriptApi 18 from .recipe_api import RecipeApiPlain, RecipeScriptApi
18 from .recipe_api import _UnresolvedRequirement 19 from .recipe_api import _UnresolvedRequirement
19 from .recipe_api import BoundProperty 20 from .recipe_api import BoundProperty
20 from .recipe_api import UndefinedPropertyException, PROPERTY_SENTINEL 21 from .recipe_api import UndefinedPropertyException, PROPERTY_SENTINEL
22 from .recipe_api import StepFailure
21 from .recipe_test_api import RecipeTestApi, DisabledTestData 23 from .recipe_test_api import RecipeTestApi, DisabledTestData
24 from .types import StepDataAttributeError
25 import subprocess42
22 26
23 27
24 class LoaderError(Exception): 28 class LoaderError(Exception):
25 """Raised when something goes wrong loading recipes or modules.""" 29 """Raised when something goes wrong loading recipes or modules."""
26 30
27 31
28 class NoSuchRecipe(LoaderError): 32 class NoSuchRecipe(LoaderError):
29 """Raised by load_recipe is recipe is not found.""" 33 """Raised by load_recipe is recipe is not found."""
30 def __init__(self, recipe): 34 def __init__(self, recipe):
31 super(NoSuchRecipe, self).__init__('No such recipe: %s' % recipe) 35 super(NoSuchRecipe, self).__init__('No such recipe: %s' % recipe)
32 36
33 37
38 class RecipeException(Exception):
39 """Raised when an unhandeled exception is raised in a recipe."""
40 def __init__(self, inner_exception, traceback):
41 self.inner_exception = inner_exception
42 self.traceback = traceback
43 super(RecipeException, self).__init__(inner_exception.message)
44
45
34 class RecipeScript(object): 46 class RecipeScript(object):
35 """Holds dict of an evaluated recipe script.""" 47 """Holds dict of an evaluated recipe script."""
36 48
37 def __init__(self, recipe_globals, path): 49 def __init__(self, recipe_globals, path):
38 self.path = path 50 self.path = path
39 self._recipe_globals = recipe_globals 51 self._recipe_globals = recipe_globals
40 52
41 self.run_steps, self.gen_tests = [ 53 self.run_steps, self.gen_tests = [
42 recipe_globals.get(k) for k in ('RunSteps', 'GenTests')] 54 recipe_globals.get(k) for k in ('RunSteps', 'GenTests')]
43 55
(...skipping 25 matching lines...) Expand all
69 return self._recipe_globals['LOADED_DEPS'] 81 return self._recipe_globals['LOADED_DEPS']
70 82
71 @property 83 @property
72 def RETURN_SCHEMA(self): 84 def RETURN_SCHEMA(self):
73 return self._recipe_globals.get('RETURN_SCHEMA') 85 return self._recipe_globals.get('RETURN_SCHEMA')
74 86
75 def run(self, api, properties): 87 def run(self, api, properties):
76 """ 88 """
77 Run this recipe, with the given api and property arguments. 89 Run this recipe, with the given api and property arguments.
78 Check the return value, if we have a RETURN_SCHEMA. 90 Check the return value, if we have a RETURN_SCHEMA.
91
92 Raises:
93 RecipeException if an unhandled exception happened in the recipe.
94 TypeError if recipe did not return an expected result.
79 """ 95 """
96
97 orig_run_steps = self.run_steps
98
99 def run_steps(*args, **kwargs):
100 try:
101 return orig_run_steps(*args, **kwargs)
102 except (
103 StepFailure,
104 StepDataAttributeError,
105 subprocess42.TimeoutExpired):
106 raise
107 except Exception as ex:
108 raise RecipeException(ex, sys.exc_info()[2])
109
80 recipe_result = invoke_with_properties( 110 recipe_result = invoke_with_properties(
81 self.run_steps, properties, self.PROPERTIES, api=api) 111 run_steps, properties, self.PROPERTIES,
112 inspectable_obj=orig_run_steps,
113 api=api)
82 114
83 if self.RETURN_SCHEMA: 115 if self.RETURN_SCHEMA:
84 if not recipe_result: 116 if not recipe_result:
85 raise ValueError("Recipe %s did not return a value." % self.name) 117 raise ValueError("Recipe %s did not return a value." % self.name)
86 return recipe_result.as_jsonish(True) 118 return recipe_result.as_jsonish(True)
87 else: 119 else:
88 return None 120 return None
89 121
90 @classmethod 122 @classmethod
91 def from_script_path(cls, script_path, universe_view): 123 def from_script_path(cls, script_path, universe_view):
(...skipping 447 matching lines...) Expand 10 before | Expand all | Expand 10 after
539 "Missing property value for '{}'.".format(prop_name)) 571 "Missing property value for '{}'.".format(prop_name))
540 572
541 prop = prop_defs[prop_name] 573 prop = prop_defs[prop_name]
542 props.append(prop.interpret(all_props.get( 574 props.append(prop.interpret(all_props.get(
543 prop_name, PROPERTY_SENTINEL))) 575 prop_name, PROPERTY_SENTINEL)))
544 576
545 return callable_obj(*props, **additional_args) 577 return callable_obj(*props, **additional_args)
546 578
547 579
548 def invoke_with_properties(callable_obj, all_props, prop_defs, 580 def invoke_with_properties(callable_obj, all_props, prop_defs,
549 **additional_args): 581 inspectable_obj=None, **additional_args):
550 """ 582 """
551 Invokes callable with filtered, type-checked properties. 583 Invokes callable with filtered, type-checked properties.
552 584
553 Args: 585 Args:
554 callable_obj: The function to call, or class to instantiate. 586 callable_obj: The function to call, or class to instantiate.
555 This supports passing in either RunSteps, or a recipe module, 587 This supports passing in either RunSteps, or a recipe module,
556 which is a class. 588 which is a class.
557 all_props: A dictionary containing all the properties (strings) currently 589 all_props: A dictionary containing all the properties (strings) currently
558 defined in the system. 590 defined in the system.
559 prop_defs: A dictionary of property name to property definitions 591 prop_defs: A dictionary of property name to property definitions
560 (BoundProperty) for this callable. 592 (BoundProperty) for this callable.
593 inspectable_obj: a function to inspect instead of callable_obj.
594 Useful when callback_obj is wrapped.
561 additional_args: kwargs to pass through to the callable. 595 additional_args: kwargs to pass through to the callable.
562 Note that the names of the arguments can correspond to 596 Note that the names of the arguments can correspond to
563 positional arguments as well. 597 positional arguments as well.
564 598
565 Returns: 599 Returns:
566 The result of calling callable with the filtered properties 600 The result of calling callable with the filtered properties
567 and additional arguments. 601 and additional arguments.
568 """ 602 """
569 # Check that we got passed BoundProperties, and not Properties 603 # Check that we got passed BoundProperties, and not Properties
570 604
605 inspectable_obj = inspectable_obj or callable_obj
606
571 607
572 # To detect when they didn't specify a property that they have as a 608 # To detect when they didn't specify a property that they have as a
573 # function argument, list the arguments, through inspection, 609 # function argument, list the arguments, through inspection,
574 # and then comparing this list to the provided properties. We use a list 610 # and then comparing this list to the provided properties. We use a list
575 # instead of a dict because getargspec returns a list which we would have to 611 # instead of a dict because getargspec returns a list which we would have to
576 # convert to a dictionary, and the benefit of the dictionary is pretty small. 612 # convert to a dictionary, and the benefit of the dictionary is pretty small.
577 if inspect.isclass(callable_obj): 613 if inspect.isclass(inspectable_obj):
578 arg_names = inspect.getargspec(callable_obj.__init__).args 614 arg_names = inspect.getargspec(inspectable_obj.__init__).args
579 615
580 arg_names.pop(0) 616 arg_names.pop(0)
581 else: 617 else:
582 arg_names = inspect.getargspec(callable_obj).args 618 arg_names = inspect.getargspec(inspectable_obj).args
583 return _invoke_with_properties(callable_obj, all_props, prop_defs, arg_names, 619 return _invoke_with_properties(callable_obj, all_props, prop_defs, arg_names,
584 **additional_args) 620 **additional_args)
585 621
586 622
587 def create_recipe_api(toplevel_package, toplevel_deps, recipe_script_path, 623 def create_recipe_api(toplevel_package, toplevel_deps, recipe_script_path,
588 engine, test_data=DisabledTestData()): 624 engine, test_data=DisabledTestData()):
589 def instantiator(mod, deps): 625 def instantiator(mod, deps):
590 kwargs = { 626 kwargs = {
591 'module': mod, 627 'module': mod,
592 # TODO(luqui): test_data will need to use canonical unique names. 628 # TODO(luqui): test_data will need to use canonical unique names.
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
662 for k,v in toplevel_deps.iteritems(): 698 for k,v in toplevel_deps.iteritems():
663 setattr(api, k, mapper.instantiate(v)) 699 setattr(api, k, mapper.instantiate(v))
664 return api 700 return api
665 701
666 702
667 def _resolve_requirement(req, engine): 703 def _resolve_requirement(req, engine):
668 if req._typ == 'client': 704 if req._typ == 'client':
669 return engine._get_client(req._name) 705 return engine._get_client(req._name)
670 else: 706 else:
671 raise ValueError('Unknown requirement type [%s]' % (req._typ,)) 707 raise ValueError('Unknown requirement type [%s]' % (req._typ,))
OLDNEW
« no previous file with comments | « no previous file | recipe_engine/result.proto » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698