| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 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 """Tests that recipes are on their best behavior. | 6 """Tests that recipes are on their best behavior. |
| 7 | 7 |
| 8 Checks that recipes only import modules from a whitelist. Imports are | 8 Checks that recipes only import modules from a whitelist. Imports are |
| 9 generally not safe in recipes if they depend on the platform, since | 9 generally not safe in recipes if they depend on the platform, since |
| 10 e.g. you can run a recipe simulation for a Windows recipe on Linux. | 10 e.g. you can run a recipe simulation for a Windows recipe on Linux. |
| 11 """ | 11 """ |
| 12 | 12 |
| 13 # TODO(luqui): Implement lint for recipe modules also. |
| 14 |
| 13 import re | 15 import re |
| 16 import os |
| 17 import sys |
| 14 import types | 18 import types |
| 15 | 19 |
| 16 import test_env # pylint: disable=W0611,W0403 | |
| 17 | 20 |
| 18 from slave import recipe_loader | 21 MODULES_WHITELIST = [ |
| 19 | |
| 20 | |
| 21 MODULES_WHITELIST = map(re.compile, [ | |
| 22 r'base64', | 22 r'base64', |
| 23 r'collections', | 23 r'collections', |
| 24 r'datetime', | 24 r'datetime', |
| 25 r'json', | 25 r'json', |
| 26 r'math', | 26 r'math', |
| 27 r're', | 27 r're', |
| 28 r'urlparse', | 28 r'urlparse', |
| 29 | 29 ] |
| 30 r'slave\.recipe_api', | |
| 31 | |
| 32 # TODO(luqui): Move skia modules into recipe resources | |
| 33 r'common\.skia\..*', | |
| 34 r'slave\.skia\..*', | |
| 35 | |
| 36 # TODO(luqui): Move cros modules into recipe resources | |
| 37 r'common\.cros_chromite', | |
| 38 ]) | |
| 39 | 30 |
| 40 | 31 |
| 41 class ImportViolationError(Exception): | 32 class ImportViolationError(Exception): |
| 42 pass | 33 pass |
| 43 | 34 |
| 44 | 35 |
| 45 class TestFailure(Exception): | 36 class TestFailure(Exception): |
| 46 pass | 37 pass |
| 47 | 38 |
| 48 | 39 |
| 49 def ImportsTest(recipe_path, recipe_name, universe): | 40 def ImportsTest(recipe_path, recipe_name, whitelist, universe): |
| 50 """Tests that recipe_name only uses allowed imports. | 41 """Tests that recipe_name only uses allowed imports. |
| 51 | 42 |
| 52 Returns a list of errors, or an empty list if there are no errors (duh). | 43 Returns a list of errors, or an empty list if there are no errors (duh). |
| 53 """ | 44 """ |
| 54 | 45 |
| 55 recipe = universe.load_recipe(recipe_name) | 46 recipe = universe.load_recipe(recipe_name) |
| 56 for attr in dir(recipe): | 47 for attr in dir(recipe): |
| 57 val = getattr(recipe, attr) | 48 val = getattr(recipe, attr) |
| 58 if isinstance(val, types.ModuleType): | 49 if isinstance(val, types.ModuleType): |
| 59 module_name = val.__name__ | 50 module_name = val.__name__ |
| 60 for pattern in MODULES_WHITELIST: | 51 for pattern in whitelist: |
| 61 if pattern.match(val.__name__): | 52 if pattern.match(val.__name__): |
| 62 break | 53 break |
| 63 else: | 54 else: |
| 64 yield ('In %s:\n' | 55 yield ('In %s:\n' |
| 65 ' Non-whitelisted import of %s' % | 56 ' Non-whitelisted import of %s' % |
| 66 (recipe_path, module_name)) | 57 (recipe_path, module_name)) |
| 67 | 58 |
| 68 | 59 |
| 69 def MainTest(): | 60 def main(universe, whitelist=[]): |
| 70 universe = recipe_loader.RecipeUniverse() | 61 whitelist = map(re.compile, MODULES_WHITELIST + whitelist) |
| 71 | 62 |
| 72 errors = [] | 63 errors = [] |
| 73 for recipe_path, recipe_name in recipe_loader.loop_over_recipes(): | 64 for recipe_path, recipe_name in universe.loop_over_recipes(): |
| 74 errors.extend(ImportsTest(recipe_path, recipe_name, universe)) | 65 errors.extend(ImportsTest(recipe_path, recipe_name, whitelist, universe)) |
| 75 | 66 |
| 76 if errors: | 67 if errors: |
| 77 raise TestFailure('\n'.join(map(str, errors))) | 68 raise TestFailure('\n'.join(map(str, errors))) |
| 78 | 69 |
| 79 | |
| 80 if __name__ == '__main__': | |
| 81 MainTest() | |
| OLD | NEW |