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

Side by Side Diff: unittests/test_test.py

Issue 2721613004: simulation_test_ng: initial CL (Closed)
Patch Set: revised Created 3 years, 9 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
« recipe_engine/test.py ('K') | « unittests/stdlib_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
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2017 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file.
5
6 import json
7 import os
8 import shutil
9 import subprocess
10 import sys
11 import tempfile
12 import unittest
13
14
15 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16 sys.path.insert(0, ROOT_DIR)
17 import recipe_engine.env
18
19
20 from recipe_engine import package
21 from recipe_engine import package_pb2
22
23
24 class RecipeWriter(object):
25 """Helper to write a recipe for tests."""
26
27 def __init__(self, recipes_dir, name):
28 self.recipes_dir = recipes_dir
29 self.name = name
30
31 # These are expected to be set appropriately by the caller.
32 self.DEPS = []
33 self.RunStepsLines = ['pass']
34 self.GenTestsLines = ['yield api.test("basic")']
35 self.expectations = {}
36
37 @property
38 def expect_dir(self):
39 return os.path.join(self.recipes_dir, '%s.expected' % self.name)
40
41 def add_expectation(self, test_name, commands=None, recipe_result=None,
42 status_code=0):
43 """Adds expectation for a simulation test.
44
45 Arguments:
46 test_name(str): name of the test
47 commands(list): list of expectation dictionaries
48 recipe_result(object): expected result of the recipe
49 status_code(int): expected exit code
50 """
51 self.expectations[test_name] = (commands or []) + [{
52 'name': '$result',
53 'recipe_result': recipe_result,
54 'status_code': status_code
55 }]
56
57 def write(self):
58 """Writes the recipe to disk."""
59 for d in (self.recipes_dir, self.expect_dir):
60 if not os.path.exists(d):
61 os.makedirs(d)
62 with open(os.path.join(self.recipes_dir, '%s.py' % self.name), 'w') as f:
63 f.write('\n'.join([
64 'from recipe_engine import post_process',
65 '',
66 'DEPS = %r' % self.DEPS,
67 '',
68 'def RunSteps(api):',
69 ] + [' %s' % l for l in self.RunStepsLines] + [
70 '',
71 'def GenTests(api):',
72 ] + [' %s' % l for l in self.GenTestsLines]))
73 for test_name, test_contents in self.expectations.iteritems():
74 with open(os.path.join(self.expect_dir, '%s.json' % test_name), 'w') as f:
75 json.dump(test_contents, f)
76
77
78 class RecipeModuleWriter(object):
79 """Helper to write a recipe module for tests."""
80
81 def __init__(self, root_dir, name):
82 self.root_dir = root_dir
83 self.name = name
84
85 # These are expected to be set appropriately by the caller.
86 self.DEPS = []
87 self.disable_strict_coverage = False
88 self.methods = {}
89
90 self.example = RecipeWriter(self.module_dir, 'example')
91
92 @property
93 def module_dir(self):
94 return os.path.join(self.root_dir, 'recipe_modules', self.name)
95
96 def write(self):
97 """Writes the recipe module to disk."""
98
99 if not os.path.exists(self.module_dir):
100 os.makedirs(self.module_dir)
101
102 with open(os.path.join(self.module_dir, '__init__.py'), 'w') as f:
103 f.write('DEPS = %r\n' % self.DEPS)
104 if self.disable_strict_coverage:
105 f.write('\nDISABLE_STRICT_COVERAGE = True')
106
107 api_lines = [
108 'from recipe_engine import recipe_api',
109 '',
110 'class MyApi(recipe_api.RecipeApi):',
111 ]
112 if self.methods:
113 for m_name, m_lines in self.methods.iteritems():
114 api_lines.extend([
115 '',
116 ' def %s(self):' % m_name,
117 ] + [' %s' % l for l in m_lines] + [
118 '',
119 ])
120 else:
121 api_lines.append(' pass')
122 with open(os.path.join(self.module_dir, 'api.py'), 'w') as f:
123 f.write('\n'.join(api_lines))
124
125
126 class TestTest(unittest.TestCase):
127 def setUp(self):
128 root_dir = tempfile.mkdtemp()
129 config_dir = os.path.join(root_dir, 'infra', 'config')
130 os.makedirs(config_dir)
131
132 self._root_dir = root_dir
133 self._recipes_cfg = os.path.join(config_dir, 'recipes.cfg')
134 self._recipe_tool = os.path.join(ROOT_DIR, 'recipes.py')
135
136 test_pkg = package_pb2.Package(
137 api_version=1,
138 project_id='test_pkg',
139 recipes_path='',
140 deps=[
141 package_pb2.DepSpec(
142 project_id='recipe_engine',
143 url='file://'+ROOT_DIR),
144 ],
145 )
146 package.ProtoFile(self._recipes_cfg).write(test_pkg)
147
148 def tearDown(self):
149 shutil.rmtree(self._root_dir)
150
151 def _run_recipes(self, *args):
152 return subprocess.check_output((
153 sys.executable,
154 self._recipe_tool,
155 '--use-bootstrap',
156 '--package', self._recipes_cfg,
157 ) + args, stderr=subprocess.STDOUT)
158
159 def test_list(self):
160 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
161 rw.RunStepsLines = ['pass']
162 rw.write()
163 json_path = os.path.join(self._root_dir, 'tests.json')
164 self._run_recipes('test', 'list', '--json', json_path)
165 with open(json_path) as f:
166 json_data = json.load(f)
167 self.assertEqual(
168 {'format': 1, 'tests': ['foo.basic']},
169 json_data)
170
171 def test_test(self):
172 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
173 rw.RunStepsLines = ['pass']
174 rw.add_expectation('basic')
175 rw.write()
176 self._run_recipes('test', 'run')
177
178 def test_test_expectation_failure_empty(self):
179 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
180 rw.RunStepsLines = ['pass']
181 rw.write()
182 with self.assertRaises(subprocess.CalledProcessError) as cm:
183 self._run_recipes('test', 'run')
184 self.assertNotIn('FATAL: Insufficient coverage', cm.exception.output)
185 self.assertNotIn('CHECK(FAIL)', cm.exception.output)
186 self.assertIn(
187 'foo.basic failed',
188 cm.exception.output)
189
190 def test_test_expectation_failure_different(self):
191 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
192 rw.DEPS = ['recipe_engine/step']
193 rw.RunStepsLines = ['api.step("test", ["echo", "bar"])']
194 rw.add_expectation('basic')
195 rw.write()
196 with self.assertRaises(subprocess.CalledProcessError) as cm:
197 self._run_recipes('test', 'run')
198 self.assertNotIn('FATAL: Insufficient coverage', cm.exception.output)
199 self.assertNotIn('CHECK(FAIL)', cm.exception.output)
200 self.assertIn(
201 'foo.basic failed',
202 cm.exception.output)
203 self.assertIn(
204 '+[{\'cmd\': [\'echo\', \'bar\'], \'name\': \'test\'},\n',
205 cm.exception.output)
206
207 def test_test_expectation_pass(self):
208 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
209 rw.DEPS = ['recipe_engine/step']
210 rw.RunStepsLines = ['api.step("test", ["echo", "bar"])']
211 rw.add_expectation('basic', [{'cmd': ['echo', 'bar'], 'name': 'test'}])
212 rw.write()
213 self._run_recipes('test', 'run')
214
215 def test_test_recipe_not_covered(self):
216 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
217 rw.RunStepsLines = ['if False:', ' pass']
218 rw.add_expectation('basic')
219 rw.write()
220 with self.assertRaises(subprocess.CalledProcessError) as cm:
221 self._run_recipes('test', 'run')
222 self.assertIn('FATAL: Insufficient coverage', cm.exception.output)
223 self.assertNotIn('CHECK(FAIL)', cm.exception.output)
224 self.assertNotIn('foo.basic failed', cm.exception.output)
225
226 def test_test_check_failure(self):
227 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
228 rw.RunStepsLines = ['pass']
229 rw.GenTestsLines = [
230 'yield api.test("basic") + \\',
231 ' api.post_process(post_process.MustRun, "bar")'
232 ]
233 rw.add_expectation('basic')
234 rw.write()
235 with self.assertRaises(subprocess.CalledProcessError) as cm:
236 self._run_recipes('test', 'run')
237 self.assertNotIn('FATAL: Insufficient coverage', cm.exception.output)
238 self.assertIn('CHECK(FAIL)', cm.exception.output)
239 self.assertIn('foo.basic failed', cm.exception.output)
240
241 def test_test_check_success(self):
242 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
243 rw.RunStepsLines = ['pass']
244 rw.GenTestsLines = [
245 'yield api.test("basic") + \\',
246 ' api.post_process(post_process.DoesNotRun, "bar")'
247 ]
248 rw.add_expectation('basic')
249 rw.write()
250 self._run_recipes('test', 'run')
251
252 def test_test_recipe_syntax_error(self):
253 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo')
254 rw.RunStepsLines = ['baz']
255 rw.add_expectation('basic')
256 rw.write()
257 with self.assertRaises(subprocess.CalledProcessError) as cm:
258 self._run_recipes('test', 'run')
259 self.assertIn('NameError: global name \'baz\' is not defined',
260 cm.exception.output)
261
262 def test_test_recipe_module_uncovered(self):
263 mw = RecipeModuleWriter(self._root_dir, 'foo')
264 mw.write()
265 with self.assertRaises(subprocess.CalledProcessError) as cm:
266 self._run_recipes('test', 'run')
267 self.assertIn('The following modules lack test coverage: foo',
268 cm.exception.output)
269
270 def test_test_recipe_module_syntax_error(self):
271 mw = RecipeModuleWriter(self._root_dir, 'foo_module')
272 mw.methods['foo'] = ['baz']
273 mw.write()
274 mw.example.DEPS = ['foo_module']
275 mw.example.RunStepsLines = ['api.foo_module.foo()']
276 mw.example.write()
277 with self.assertRaises(subprocess.CalledProcessError) as cm:
278 self._run_recipes('test', 'run')
279 self.assertIn('NameError: global name \'baz\' is not defined',
280 cm.exception.output)
281 self.assertIn('FATAL: Insufficient coverage', cm.exception.output)
282
283 def test_test_recipe_module_syntax_error_in_example(self):
284 mw = RecipeModuleWriter(self._root_dir, 'foo_module')
285 mw.methods['foo'] = ['pass']
286 mw.write()
287 mw.example.DEPS = ['foo_module']
288 mw.example.RunStepsLines = ['baz']
289 mw.example.write()
290 with self.assertRaises(subprocess.CalledProcessError) as cm:
291 self._run_recipes('test', 'run')
292 self.assertIn('NameError: global name \'baz\' is not defined',
293 cm.exception.output)
294 self.assertIn('FATAL: Insufficient coverage', cm.exception.output)
295
296 def test_test_recipe_module_example_not_covered(self):
297 mw = RecipeModuleWriter(self._root_dir, 'foo_module')
298 mw.methods['foo'] = ['pass']
299 mw.write()
300 mw.example.DEPS = ['foo_module']
301 mw.example.RunStepsLines = ['if False:', ' pass']
302 mw.example.write()
303 with self.assertRaises(subprocess.CalledProcessError) as cm:
304 self._run_recipes('test', 'run')
305 self.assertIn('FATAL: Insufficient coverage', cm.exception.output)
306
307 def test_test_recipe_module_uncovered_not_strict(self):
308 mw = RecipeModuleWriter(self._root_dir, 'foo')
309 mw.disable_strict_coverage = True
310 mw.write()
311 self._run_recipes('test', 'run')
312
313 def test_test_recipe_module_covered_by_recipe_not_strict(self):
314 mw = RecipeModuleWriter(self._root_dir, 'foo_module')
315 mw.methods['bar'] = ['pass']
316 mw.disable_strict_coverage = True
317 mw.write()
318 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo_recipe')
319 rw.DEPS = ['foo_module']
320 rw.RunStepsLines = ['api.foo_module.bar()']
321 rw.add_expectation('basic')
322 rw.write()
323 self._run_recipes('test', 'run')
324
325 def test_test_recipe_module_covered_by_recipe(self):
326 mw = RecipeModuleWriter(self._root_dir, 'foo_module')
327 mw.methods['bar'] = ['pass']
328 mw.write()
329 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo_recipe')
330 rw.DEPS = ['foo_module']
331 rw.RunStepsLines = ['api.foo_module.bar()']
332 rw.add_expectation('basic')
333 rw.write()
334 with self.assertRaises(subprocess.CalledProcessError) as cm:
335 self._run_recipes('test', 'run')
336 self.assertIn('The following modules lack test coverage: foo_module',
337 cm.exception.output)
338
339 def test_test_recipe_module_partially_covered_by_recipe_not_strict(self):
340 mw = RecipeModuleWriter(self._root_dir, 'foo_module')
341 mw.methods['bar'] = ['pass']
342 mw.methods['baz'] = ['pass']
343 mw.disable_strict_coverage = True
344 mw.write()
345 mw.example.DEPS = ['foo_module']
346 mw.example.RunStepsLines = ['api.foo_module.baz()']
347 mw.example.add_expectation('basic')
348 mw.example.write()
349 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo_recipe')
350 rw.DEPS = ['foo_module']
351 rw.RunStepsLines = ['api.foo_module.bar()']
352 rw.add_expectation('basic')
353 rw.write()
354 self._run_recipes('test', 'run')
355
356 def test_test_recipe_module_partially_covered_by_recipe(self):
357 mw = RecipeModuleWriter(self._root_dir, 'foo_module')
358 mw.methods['bar'] = ['pass']
359 mw.methods['baz'] = ['pass']
360 mw.write()
361 mw.example.DEPS = ['foo_module']
362 mw.example.RunStepsLines = ['api.foo_module.baz()']
363 mw.example.add_expectation('basic')
364 mw.example.write()
365 rw = RecipeWriter(os.path.join(self._root_dir, 'recipes'), 'foo_recipe')
366 rw.DEPS = ['foo_module']
367 rw.RunStepsLines = ['api.foo_module.bar()']
368 rw.add_expectation('basic')
369 rw.write()
370 with self.assertRaises(subprocess.CalledProcessError) as cm:
371 self._run_recipes('test', 'run')
372 self.assertIn('FATAL: Insufficient coverage', cm.exception.output)
373
374
375 if __name__ == '__main__':
376 sys.exit(unittest.main())
OLDNEW
« recipe_engine/test.py ('K') | « unittests/stdlib_test.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698