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

Side by Side Diff: recipe_engine/unittests/run_test.py

Issue 1959563002: Use subprocess42 in recipe_engine to avoid threading madness. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/recipes-py@master
Patch Set: *sigh* presubmit Created 4 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
« no previous file with comments | « recipe_engine/third_party/subprocess42.py ('k') | recipe_engine/unittests/step_runner_test.py » ('j') | 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/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 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 import json 6 import json
7 import os 7 import os
8 import re 8 import re
9 import subprocess 9 import subprocess
10 import sys 10 import sys
11 import unittest 11 import unittest
12 import time
12 13
13 BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname( 14 BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
14 os.path.abspath(__file__)))) 15 os.path.abspath(__file__))))
15 THIRD_PARTY = os.path.join(BASE_DIR, 'recipe_engine', 'third_party') 16 THIRD_PARTY = os.path.join(BASE_DIR, 'recipe_engine', 'third_party')
16 sys.path.insert(0, os.path.join(THIRD_PARTY, 'mock-1.0.1')) 17 sys.path.insert(0, os.path.join(THIRD_PARTY, 'mock-1.0.1'))
17 sys.path.insert(0, BASE_DIR) 18 sys.path.insert(0, BASE_DIR)
18 19
19 import recipe_engine.run 20 import recipe_engine.run
20 import recipe_engine.step_runner 21 import recipe_engine.step_runner
21 from recipe_engine import recipe_test_api 22 from recipe_engine import recipe_test_api
22 import mock 23 import mock
23 24
25 ENABLE_SUBPROCESS42 = {
26 "$recipe_engine": {"mode_flags": {"use_subprocess42": True}}
27 }
28
24 class RunTest(unittest.TestCase): 29 class RunTest(unittest.TestCase):
25 def _run_cmd(self, recipe, properties=None): 30 def _run_cmd(self, recipe, properties=None):
26 script_path = os.path.join(BASE_DIR, 'recipes.py') 31 script_path = os.path.join(BASE_DIR, 'recipes.py')
27 32
28 if properties: 33 if properties:
29 proplist = [ '%s=%s' % (k, json.dumps(v)) 34 proplist = [ '%s=%s' % (k, json.dumps(v))
30 for k,v in properties.iteritems() ] 35 for k,v in properties.iteritems() ]
31 else: 36 else:
32 proplist = [] 37 proplist = []
33 38
34 return ([ 39 return ([
35 'python', script_path, 40 'python', script_path,
36 '--package', os.path.join(BASE_DIR, 'infra', 'config', 'recipes.cfg'), 41 '--package', os.path.join(BASE_DIR, 'infra', 'config', 'recipes.cfg'),
37 'run', recipe] + proplist) 42 'run', recipe] + proplist)
38 43
39 def _test_recipe(self, recipe, properties=None): 44 def _test_recipe(self, recipe, properties=None):
40 exit_code = subprocess.call(self._run_cmd(recipe, properties)) 45 exit_code = subprocess.call(self._run_cmd(recipe, properties))
41 self.assertEqual(0, exit_code) 46 self.assertEqual(0, exit_code)
42 47
43 def test_examples(self): 48 def test_examples(self):
44 self._test_recipe('step:example') 49 tests = [
45 self._test_recipe('path:example') 50 ['step:example'],
46 self._test_recipe('raw_io:example') 51 ['path:example'],
47 self._test_recipe('python:example') 52 ['raw_io:example'],
48 self._test_recipe('json:example') 53 ['python:example'],
49 self._test_recipe('uuid:example') 54 ['json:example'],
55 ['uuid:example'],
50 56
51 self._test_recipe('engine_tests/depend_on/top', {'to_pass': 42}) 57 ['engine_tests/depend_on/top', {'to_pass': 42}],
52 self._test_recipe('engine_tests/functools_partial') 58 ['engine_tests/functools_partial'],
59 ]
60 for test in tests:
61 self._test_recipe(*test)
62
63 recipe = test[0]
64 props = {}
65 if len(test) > 1:
66 props = test[1]
67 props.update(ENABLE_SUBPROCESS42)
68 self._test_recipe(recipe, props)
69
70 def test_bad_subprocess(self):
71 now = time.time()
72 self._test_recipe('engine_tests/bad_subprocess', ENABLE_SUBPROCESS42)
73 after = time.time()
74
75 # Test has a daemon that holds on to stdout for 30s, but the daemon's parent
76 # process (e.g. the one that recipe engine actually runs) quits immediately.
77 # If this takes longer than 5 seconds to run, we consider it failed.
78 self.assertLess(after - now, 5)
79
53 80
54 def test_nonexistent_command(self): 81 def test_nonexistent_command(self):
55 subp = subprocess.Popen( 82 subp = subprocess.Popen(
56 self._run_cmd('engine_tests/nonexistent_command'), 83 self._run_cmd('engine_tests/nonexistent_command'),
57 stdout=subprocess.PIPE) 84 stdout=subprocess.PIPE)
58 stdout, _ = subp.communicate() 85 stdout, _ = subp.communicate()
59 self.assertRegexpMatches(stdout, '(?m)^@@@STEP_EXCEPTION@@@$') 86 self.assertRegexpMatches(stdout, '(?m)^@@@STEP_EXCEPTION@@@$')
60 self.assertRegexpMatches(stdout, 'OSError') 87 self.assertRegexpMatches(stdout, 'OSError')
61 self.assertEqual(255, subp.returncode) 88 self.assertEqual(255, subp.returncode)
62 89
90 def test_nonexistent_command_s42(self):
91 subp = subprocess.Popen(
92 self._run_cmd('engine_tests/nonexistent_command', ENABLE_SUBPROCESS42),
93 stdout=subprocess.PIPE)
94 stdout, _ = subp.communicate()
95 self.assertRegexpMatches(stdout, '(?m)^@@@STEP_EXCEPTION@@@$')
96 self.assertRegexpMatches(stdout, 'OSError')
97 self.assertEqual(255, subp.returncode)
98
63 def test_trigger(self): 99 def test_trigger(self):
64 subp = subprocess.Popen( 100 subp = subprocess.Popen(
65 self._run_cmd('engine_tests/trigger'), 101 self._run_cmd('engine_tests/trigger'),
66 stdout=subprocess.PIPE) 102 stdout=subprocess.PIPE)
67 stdout, _ = subp.communicate() 103 stdout, _ = subp.communicate()
68 self.assertEqual(0, subp.returncode) 104 self.assertEqual(0, subp.returncode)
69 m = re.compile(r'^@@@STEP_TRIGGER@(.*)@@@$', re.MULTILINE).search(stdout) 105 m = re.compile(r'^@@@STEP_TRIGGER@(.*)@@@$', re.MULTILINE).search(stdout)
70 self.assertTrue(m) 106 self.assertTrue(m)
71 blob = m.group(1) 107 blob = m.group(1)
72 json.loads(blob) # Raises an exception if the blob is not valid json. 108 json.loads(blob) # Raises an exception if the blob is not valid json.
73 109
110 def test_trigger_s42(self):
111 subp = subprocess.Popen(
112 self._run_cmd('engine_tests/trigger', ENABLE_SUBPROCESS42),
113 stdout=subprocess.PIPE)
114 stdout, _ = subp.communicate()
115 self.assertEqual(0, subp.returncode)
116 m = re.compile(r'^@@@STEP_TRIGGER@(.*)@@@$', re.MULTILINE).search(stdout)
117 self.assertTrue(m)
118 blob = m.group(1)
119 json.loads(blob) # Raises an exception if the blob is not valid json.
120
74 def test_trigger_no_such_command(self): 121 def test_trigger_no_such_command(self):
75 """Tests that trigger still happens even if running the command fails.""" 122 """Tests that trigger still happens even if running the command fails."""
76 subp = subprocess.Popen( 123 subp = subprocess.Popen(
77 self._run_cmd( 124 self._run_cmd(
78 'engine_tests/trigger', properties={'command': ['na-huh']}), 125 'engine_tests/trigger', properties={'command': ['na-huh']}),
79 stdout=subprocess.PIPE) 126 stdout=subprocess.PIPE)
80 stdout, _ = subp.communicate() 127 stdout, _ = subp.communicate()
81 self.assertRegexpMatches(stdout, r'(?m)^@@@STEP_TRIGGER@(.*)@@@$') 128 self.assertRegexpMatches(stdout, r'(?m)^@@@STEP_TRIGGER@(.*)@@@$')
82 self.assertEqual(255, subp.returncode) 129 self.assertEqual(255, subp.returncode)
83 130
131 def test_trigger_no_such_command_s42(self):
132 """Tests that trigger still happens even if running the command fails."""
133 props = {'command': ['na-huh']}
134 props.update(ENABLE_SUBPROCESS42)
135 subp = subprocess.Popen(
136 self._run_cmd('engine_tests/trigger', props),
137 stdout=subprocess.PIPE)
138 stdout, _ = subp.communicate()
139 self.assertRegexpMatches(stdout, r'(?m)^@@@STEP_TRIGGER@(.*)@@@$')
140 self.assertEqual(255, subp.returncode)
141
142
84 def test_shell_quote(self): 143 def test_shell_quote(self):
85 # For regular-looking commands we shouldn't need any specialness. 144 # For regular-looking commands we shouldn't need any specialness.
86 self.assertEqual( 145 self.assertEqual(
87 recipe_engine.step_runner._shell_quote('/usr/bin/python-wrapper.bin'), 146 recipe_engine.step_runner._shell_quote('/usr/bin/python-wrapper.bin'),
88 '/usr/bin/python-wrapper.bin') 147 '/usr/bin/python-wrapper.bin')
89 148
90 STRINGS = [ 149 STRINGS = [
91 'Simple.Command123/run', 150 'Simple.Command123/run',
92 'Command with spaces', 151 'Command with spaces',
93 'Command with "quotes"', 152 'Command with "quotes"',
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
136 return None 195 return None
137 196
138 with self.assertRaises(AssertionError): 197 with self.assertRaises(AssertionError):
139 engine.run(FakeScript(), api) 198 engine.run(FakeScript(), api)
140 199
141 def test_subannotations(self): 200 def test_subannotations(self):
142 proc = subprocess.Popen( 201 proc = subprocess.Popen(
143 self._run_cmd('engine_tests/subannotations'), 202 self._run_cmd('engine_tests/subannotations'),
144 stdout=subprocess.PIPE, 203 stdout=subprocess.PIPE,
145 stderr=subprocess.PIPE) 204 stderr=subprocess.PIPE)
146 stdout, stderr = proc.communicate() 205 stdout, _ = proc.communicate()
147 self.assertRegexpMatches(stdout, r'(?m)^!@@@BUILD_STEP@steppy@@@$') 206 self.assertRegexpMatches(stdout, r'(?m)^!@@@BUILD_STEP@steppy@@@$')
148 self.assertRegexpMatches(stdout, r'(?m)^@@@BUILD_STEP@pippy@@@$') 207 self.assertRegexpMatches(stdout, r'(?m)^@@@BUILD_STEP@pippy@@@$')
149 # Before 'Subannotate me' we expect an extra STEP_CURSOR to reset the 208 # Before 'Subannotate me' we expect an extra STEP_CURSOR to reset the
209 # state.
210 self.assertRegexpMatches(stdout,
211 r'(?m)^@@@STEP_CURSOR@Subannotate me@@@\n@@@STEP_CLOSED@@@$')
212
213 def test_subannotations_s42(self):
214 proc = subprocess.Popen(
215 self._run_cmd('engine_tests/subannotations', ENABLE_SUBPROCESS42),
216 stdout=subprocess.PIPE,
217 stderr=subprocess.PIPE)
218 stdout, _ = proc.communicate()
219 self.assertRegexpMatches(stdout, r'(?m)^!@@@BUILD_STEP@steppy@@@$')
220 self.assertRegexpMatches(stdout, r'(?m)^@@@BUILD_STEP@pippy@@@$')
221 # Before 'Subannotate me' we expect an extra STEP_CURSOR to reset the
150 # state. 222 # state.
151 self.assertRegexpMatches(stdout, 223 self.assertRegexpMatches(stdout,
152 r'(?m)^@@@STEP_CURSOR@Subannotate me@@@\n@@@STEP_CLOSED@@@$') 224 r'(?m)^@@@STEP_CURSOR@Subannotate me@@@\n@@@STEP_CLOSED@@@$')
153 225
154 226
155 if __name__ == '__main__': 227 if __name__ == '__main__':
156 unittest.TestCase.maxDiff = None 228 unittest.TestCase.maxDiff = None
157 unittest.main() 229 unittest.main()
OLDNEW
« no previous file with comments | « recipe_engine/third_party/subprocess42.py ('k') | recipe_engine/unittests/step_runner_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698