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

Side by Side Diff: scripts/slave/unittests/recipes_test.py

Issue 15270004: Add step generator protocol, remove annotated_checkout, remove script. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Address comments and change expected json output format Created 7 years, 7 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 | Annotate | Revision Log
« no previous file with comments | « scripts/slave/unittests/recipe_util_test.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 # Copyright (c) 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 individual recipes.
7
8 Recipe tests are located in ../recipes_test/*.py.
9
10 Each py file's splitext'd name is expected to match a recipe in ../recipes/*.py.
11
12 Each test py file contains one or more test functions:
13 * A test function's name ends with '_test' and takes an instance of TestAPI
14 as its only parameter.
15 * The test should return a dictionary with any of the following keys:
16 * factory_properties
17 * build_properties
18 * test_data
19 * test_data's value should be a dictionary in the form of
20 {stepname -> (retcode, json_data)}
21 * Since the test doesn't run any steps, test_data allows you to simulate
22 return values for particular steps.
23
24 Once your test methods are set up, run `recipes_test.py --train`. This will
25 take your tests and simulate what steps would have run, given the test inputs,
26 and will record them as JSON into files of the form:
27 ../recipes_test/<recipe_name>.<test_name>.expected
28
29 If those files look right, make sure they get checked in with your changes.
30
31 When this file runs as a test (i.e. as `recipes_test.py`), it will re-evaluate
32 the recipes using the test function input data and compare the result to the
33 values recorded in the .expected files.
34
35 Additionally, this test cannot pass unless every recipe in ../recipes has 100%
36 code coverage when executed via the tests in ../recipes_test.
37 """
38
6 import contextlib 39 import contextlib
7 import json 40 import json
8 import os 41 import os
9 import sys 42 import sys
10 import unittest 43 import unittest
11 44
12 from glob import glob 45 from glob import glob
13 46
14 import test_env # pylint: disable=W0611 47 import test_env # pylint: disable=W0611
15 48
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
78 for name, value in gvars.iteritems(): 111 for name, value in gvars.iteritems():
79 if name.endswith('_test'): 112 if name.endswith('_test'):
80 ret[name[:-len('_test')]] = value 113 ret[name[:-len('_test')]] = value
81 return ret 114 return ret
82 115
83 116
84 def execute_test_case(test_fn, recipe_path): 117 def execute_test_case(test_fn, recipe_path):
85 test_data = test_fn(TestAPI()) 118 test_data = test_fn(TestAPI())
86 bp = test_data.get('build_properties', {}) 119 bp = test_data.get('build_properties', {})
87 fp = test_data.get('factory_properties', {}) 120 fp = test_data.get('factory_properties', {})
121 td = test_data.get('test_data', {})
88 fp['recipe'] = os.path.basename(os.path.splitext(recipe_path)[0]) 122 fp['recipe'] = os.path.basename(os.path.splitext(recipe_path)[0])
89 123
90 stream = annotator.StructuredAnnotationStream(stream=open(os.devnull, 'w')) 124 stream = annotator.StructuredAnnotationStream(stream=open(os.devnull, 'w'))
91 with cover(): 125 with cover():
92 with recipe_util.mock_paths(): 126 with recipe_util.mock_paths():
93 retval = annotated_run.make_steps(stream, bp, fp, True) 127 step_data = annotated_run.run_steps(stream, bp, fp, td).steps_ran.values()
94 assert retval.status_code is None 128 return [s.step for s in step_data]
95 return retval.script or retval.steps
96 129
97 130
98 def train_from_tests(recipe_path): 131 def train_from_tests(recipe_path):
99 if not has_test(recipe_path): 132 if not has_test(recipe_path):
100 print 'FATAL: Recipe %s has NO tests!' % recipe_path 133 print 'FATAL: Recipe %s has NO tests!' % recipe_path
101 return False 134 return False
102 135
103 for path in glob(expected_for(recipe_path, '*')): 136 for path in glob(expected_for(recipe_path, '*')):
104 os.unlink(path) 137 os.unlink(path)
105 138
106 for name, test_fn in exec_test_file(recipe_path).iteritems(): 139 for name, test_fn in exec_test_file(recipe_path).iteritems():
107 steps = execute_test_case(test_fn, recipe_path) 140 steps = execute_test_case(test_fn, recipe_path)
108 expected_path = expected_for(recipe_path, name) 141 expected_path = expected_for(recipe_path, name)
109 print 'Writing', expected_path 142 print 'Writing', expected_path
110 with open(expected_path, 'w') as f: 143 with open(expected_path, 'w') as f:
111 json.dump(steps, f, indent=2, sort_keys=True) 144 f.write('[')
145 first = True
146 for step in steps:
147 f.write(('' if first else '\n },')+'\n {')
148 first_dict_item = True
149 for key, value in sorted(step.items(), key=lambda x: x[0]):
150 f.write(('' if first_dict_item else ',')+'\n ')
151 f.write('"%s": ' % key)
152 json.dump(value, f, sort_keys=True)
153 first_dict_item = False
154 first = False
155 f.write('\n }\n]')
112 156
113 return True 157 return True
114 158
115 159
116 def load_tests(loader, _standard_tests, _pattern): 160 def load_tests(loader, _standard_tests, _pattern):
117 """This method is invoked by unittest.main's automatic testloader.""" 161 """This method is invoked by unittest.main's automatic testloader."""
118 def create_test_class(recipe_path): 162 def create_test_class(recipe_path):
119 class RecipeTest(unittest.TestCase): 163 class RecipeTest(unittest.TestCase):
120 def testExists(self): 164 def testExists(self):
121 self.assertTrue(has_test(recipe_path)) 165 self.assertTrue(has_test(recipe_path))
(...skipping 10 matching lines...) Expand all
132 with open(expected_path, 'r') as f: 176 with open(expected_path, 'r') as f:
133 expected = json.load(f) 177 expected = json.load(f)
134 self.assertEqual(steps, expected) 178 self.assertEqual(steps, expected)
135 test_.__name__ += name 179 test_.__name__ += name
136 setattr(cls, test_.__name__, test_) 180 setattr(cls, test_.__name__, test_)
137 add_test(test_fn, expected_path) 181 add_test(test_fn, expected_path)
138 182
139 if has_test(recipe_path): 183 if has_test(recipe_path):
140 RecipeTest.add_test_methods() 184 RecipeTest.add_test_methods()
141 185
142 RecipeTest.__name__ += 'for_%s' % os.path.basename(recipe_path) 186 RecipeTest.__name__ += '_for_%s' % (
187 os.path.splitext(os.path.basename(recipe_path))[0])
143 return RecipeTest 188 return RecipeTest
144 189
145 suite = unittest.TestSuite() 190 suite = unittest.TestSuite()
146 for test_class in map(create_test_class, loop_over_recipes()): 191 for test_class in map(create_test_class, loop_over_recipes()):
147 suite.addTest(loader.loadTestsFromTestCase(test_class)) 192 suite.addTest(loader.loadTestsFromTestCase(test_class))
148 return suite 193 return suite
149 194
150 195
151 def loop_over_recipes(): 196 def loop_over_recipes():
152 for _name, path in BASE_DIRS.iteritems(): 197 for _name, path in BASE_DIRS.iteritems():
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
195 total_covered = COVERAGE.report() 240 total_covered = COVERAGE.report()
196 if total_covered != 100.0: 241 if total_covered != 100.0:
197 print 'FATAL: Recipes are not at 100% coverage.' 242 print 'FATAL: Recipes are not at 100% coverage.'
198 retcode = retcode or 2 243 retcode = retcode or 2
199 244
200 return retcode 245 return retcode
201 246
202 247
203 if __name__ == '__main__': 248 if __name__ == '__main__':
204 sys.exit(main(sys.argv)) 249 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « scripts/slave/unittests/recipe_util_test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698