| OLD | NEW |
| 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 Loading... |
| 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() |
| OLD | NEW |