1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more |
||
2 | # contributor license agreements. See the NOTICE file distributed with |
||
3 | # this work for additional information regarding copyright ownership. |
||
4 | # The ASF licenses this file to You under the Apache License, Version 2.0 |
||
5 | # (the "License"); you may not use this file except in compliance with |
||
6 | # the License. You may obtain a copy of the License at |
||
7 | # |
||
8 | # http://www.apache.org/licenses/LICENSE-2.0 |
||
9 | # |
||
10 | # Unless required by applicable law or agreed to in writing, software |
||
11 | # distributed under the License is distributed on an "AS IS" BASIS, |
||
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
13 | # See the License for the specific language governing permissions and |
||
14 | # limitations under the License. |
||
15 | |||
16 | from __future__ import absolute_import |
||
17 | |||
18 | import os |
||
19 | import re |
||
20 | import sys |
||
21 | |||
22 | import six |
||
23 | import mock |
||
24 | from oslo_config import cfg |
||
25 | |||
26 | from python_runner import python_runner |
||
27 | from st2actions.container.base import RunnerContainer |
||
28 | from st2common.runners.base_action import Action |
||
29 | from st2common.runners.utils import get_action_class_instance |
||
30 | from st2common.services import config as config_service |
||
31 | from st2common.constants.action import ACTION_OUTPUT_RESULT_DELIMITER |
||
32 | from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED, LIVEACTION_STATUS_FAILED |
||
33 | from st2common.constants.action import LIVEACTION_STATUS_TIMED_OUT |
||
34 | from st2common.constants.action import MAX_PARAM_LENGTH |
||
35 | from st2common.constants.pack import SYSTEM_PACK_NAME |
||
36 | from st2common.persistence.execution import ActionExecutionOutput |
||
37 | from python_runner.python_action_wrapper import PythonActionWrapper |
||
38 | from st2tests.base import RunnerTestCase |
||
39 | from st2tests.base import CleanDbTestCase |
||
40 | from st2tests.base import blocking_eventlet_spawn |
||
41 | from st2tests.base import make_mock_stream_readline |
||
42 | from st2tests.fixturesloader import assert_submodules_are_checked_out |
||
43 | import st2tests.base as tests_base |
||
44 | |||
45 | |||
46 | PASCAL_ROW_ACTION_PATH = os.path.join(tests_base.get_resources_path(), 'packs', |
||
47 | 'pythonactions/actions/pascal_row.py') |
||
48 | ECHOER_ACTION_PATH = os.path.join(tests_base.get_resources_path(), 'packs', |
||
49 | 'pythonactions/actions/echoer.py') |
||
50 | TEST_ACTION_PATH = os.path.join(tests_base.get_resources_path(), 'packs', |
||
51 | 'pythonactions/actions/test.py') |
||
52 | PATHS_ACTION_PATH = os.path.join(tests_base.get_resources_path(), 'packs', |
||
53 | 'pythonactions/actions/python_paths.py') |
||
54 | ACTION_1_PATH = os.path.join(tests_base.get_fixtures_path(), |
||
55 | 'packs/dummy_pack_9/actions/list_repos_doesnt_exist.py') |
||
56 | ACTION_2_PATH = os.path.join(tests_base.get_fixtures_path(), |
||
57 | 'packs/dummy_pack_9/actions/invalid_syntax.py') |
||
58 | NON_SIMPLE_TYPE_ACTION = os.path.join(tests_base.get_resources_path(), 'packs', |
||
59 | 'pythonactions/actions/non_simple_type.py') |
||
60 | PRINT_VERSION_ACTION = os.path.join(tests_base.get_fixtures_path(), 'packs', |
||
61 | 'test_content_version/actions/print_version.py') |
||
62 | PRINT_VERSION_LOCAL_MODULE_ACTION = os.path.join(tests_base.get_fixtures_path(), 'packs', |
||
63 | 'test_content_version/actions/print_version_local_import.py') |
||
64 | |||
65 | PRINT_CONFIG_ITEM_ACTION = os.path.join(tests_base.get_resources_path(), 'packs', |
||
66 | 'pythonactions/actions/print_config_item_doesnt_exist.py') |
||
67 | |||
68 | |||
69 | # Note: runner inherits parent args which doesn't work with tests since test pass additional |
||
70 | # unrecognized args |
||
71 | mock_sys = mock.Mock() |
||
72 | mock_sys.argv = [] |
||
73 | mock_sys.executable = sys.executable |
||
74 | |||
75 | MOCK_EXECUTION = mock.Mock() |
||
76 | MOCK_EXECUTION.id = '598dbf0c0640fd54bffc688b' |
||
77 | |||
78 | |||
79 | @mock.patch('python_runner.python_runner.sys', mock_sys) |
||
80 | class PythonRunnerTestCase(RunnerTestCase, CleanDbTestCase): |
||
81 | register_packs = True |
||
82 | register_pack_configs = True |
||
83 | |||
84 | @classmethod |
||
85 | def setUpClass(cls): |
||
86 | super(PythonRunnerTestCase, cls).setUpClass() |
||
87 | assert_submodules_are_checked_out() |
||
88 | |||
89 | def test_runner_creation(self): |
||
90 | runner = python_runner.get_runner() |
||
91 | self.assertTrue(runner is not None, 'Creation failed. No instance.') |
||
92 | self.assertEqual(type(runner), python_runner.PythonRunner, 'Creation failed. No instance.') |
||
93 | |||
94 | def test_action_returns_non_serializable_result(self): |
||
95 | # Actions returns non-simple type which can't be serialized, verify result is simple str() |
||
96 | # representation of the result |
||
97 | runner = self._get_mock_runner_obj() |
||
98 | runner.entry_point = NON_SIMPLE_TYPE_ACTION |
||
99 | runner.pre_run() |
||
100 | (status, output, _) = runner.run({}) |
||
101 | |||
102 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
103 | self.assertTrue(output is not None) |
||
104 | |||
105 | if six.PY2: |
||
106 | expected_result_re = (r"\[{'a': '1'}, {'h': 3, 'c': 2}, {'e': " |
||
107 | "<non_simple_type.Test object at .*?>}\]") |
||
108 | else: |
||
109 | expected_result_re = (r"\[{'a': '1'}, {'c': 2, 'h': 3}, {'e': " |
||
110 | "<non_simple_type.Test object at .*?>}\]") |
||
111 | |||
112 | match = re.match(expected_result_re, output['result']) |
||
113 | self.assertTrue(match) |
||
114 | |||
115 | def test_simple_action_with_result_no_status(self): |
||
116 | runner = self._get_mock_runner_obj() |
||
117 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
118 | runner.pre_run() |
||
119 | (status, output, _) = runner.run({'row_index': 5}) |
||
120 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
121 | self.assertTrue(output is not None) |
||
122 | self.assertEqual(output['result'], [1, 5, 10, 10, 5, 1]) |
||
123 | |||
124 | def test_simple_action_with_result_as_None_no_status(self): |
||
125 | runner = self._get_mock_runner_obj() |
||
126 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
127 | runner.pre_run() |
||
128 | (status, output, _) = runner.run({'row_index': 'b'}) |
||
129 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
130 | self.assertTrue(output is not None) |
||
131 | self.assertEqual(output['exit_code'], 0) |
||
132 | self.assertEqual(output['result'], None) |
||
133 | |||
134 | def test_simple_action_timeout(self): |
||
135 | timeout = 0 |
||
136 | runner = self._get_mock_runner_obj() |
||
137 | runner.runner_parameters = {python_runner.RUNNER_TIMEOUT: timeout} |
||
138 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
139 | runner.pre_run() |
||
140 | (status, output, _) = runner.run({'row_index': 4}) |
||
141 | self.assertEqual(status, LIVEACTION_STATUS_TIMED_OUT) |
||
142 | self.assertTrue(output is not None) |
||
143 | self.assertEqual(output['result'], 'None') |
||
144 | self.assertEqual(output['error'], 'Action failed to complete in 0 seconds') |
||
145 | self.assertEqual(output['exit_code'], -9) |
||
146 | |||
147 | def test_simple_action_with_status_succeeded(self): |
||
148 | runner = self._get_mock_runner_obj() |
||
149 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
150 | runner.pre_run() |
||
151 | (status, output, _) = runner.run({'row_index': 4}) |
||
152 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
153 | self.assertTrue(output is not None) |
||
154 | self.assertEqual(output['result'], [1, 4, 6, 4, 1]) |
||
155 | |||
156 | def test_simple_action_with_status_failed(self): |
||
157 | runner = self._get_mock_runner_obj() |
||
158 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
159 | runner.pre_run() |
||
160 | (status, output, _) = runner.run({'row_index': 'a'}) |
||
161 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
162 | self.assertTrue(output is not None) |
||
163 | self.assertEqual(output['result'], "This is suppose to fail don't worry!!") |
||
164 | |||
165 | def test_simple_action_with_status_complex_type_returned_for_result(self): |
||
166 | # Result containing a complex type shouldn't break the returning a tuple with status |
||
167 | # behavior |
||
168 | runner = self._get_mock_runner_obj() |
||
169 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
170 | runner.pre_run() |
||
171 | (status, output, _) = runner.run({'row_index': 'complex_type'}) |
||
172 | |||
173 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
174 | self.assertTrue(output is not None) |
||
175 | self.assertTrue('<pascal_row.PascalRowAction object at' in output['result']) |
||
176 | |||
177 | def test_simple_action_with_status_failed_result_none(self): |
||
178 | runner = self._get_mock_runner_obj() |
||
179 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
180 | runner.pre_run() |
||
181 | (status, output, _) = runner.run({'row_index': 'c'}) |
||
182 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
183 | self.assertTrue(output is not None) |
||
184 | self.assertEqual(output['result'], None) |
||
185 | |||
186 | def test_exception_in_simple_action_with_invalid_status(self): |
||
187 | runner = self._get_mock_runner_obj() |
||
188 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
189 | runner.pre_run() |
||
190 | self.assertRaises(ValueError, |
||
191 | runner.run, action_parameters={'row_index': 'd'}) |
||
192 | |||
193 | def test_simple_action_no_status_backward_compatibility(self): |
||
194 | runner = self._get_mock_runner_obj() |
||
195 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
196 | runner.pre_run() |
||
197 | (status, output, _) = runner.run({'row_index': 'e'}) |
||
198 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
199 | self.assertTrue(output is not None) |
||
200 | self.assertEqual(output['result'], [1, 2]) |
||
201 | |||
202 | def test_simple_action_config_value_provided_overriden_in_datastore(self): |
||
203 | pack = 'dummy_pack_5' |
||
204 | user = 'joe' |
||
205 | |||
206 | # No values provided in the datastore |
||
207 | runner = self._get_mock_runner_obj_from_container(pack=pack, user=user) |
||
208 | |||
209 | self.assertEqual(runner._config['api_key'], 'some_api_key') # static value |
||
210 | self.assertEqual(runner._config['regions'], ['us-west-1']) # static value |
||
211 | self.assertEqual(runner._config['api_secret'], None) |
||
212 | self.assertEqual(runner._config['private_key_path'], None) |
||
213 | |||
214 | # api_secret overriden in the datastore (user scoped value) |
||
215 | config_service.set_datastore_value_for_config_key(pack_name='dummy_pack_5', |
||
216 | key_name='api_secret', |
||
217 | user='joe', |
||
218 | value='foosecret', |
||
219 | secret=True) |
||
220 | |||
221 | # private_key_path overriden in the datastore (global / non-user scoped value) |
||
222 | config_service.set_datastore_value_for_config_key(pack_name='dummy_pack_5', |
||
223 | key_name='private_key_path', |
||
224 | value='foopath') |
||
225 | |||
226 | runner = self._get_mock_runner_obj_from_container(pack=pack, user=user) |
||
227 | self.assertEqual(runner._config['api_key'], 'some_api_key') # static value |
||
228 | self.assertEqual(runner._config['regions'], ['us-west-1']) # static value |
||
229 | self.assertEqual(runner._config['api_secret'], 'foosecret') |
||
230 | self.assertEqual(runner._config['private_key_path'], 'foopath') |
||
231 | |||
232 | def test_simple_action_fail(self): |
||
233 | runner = self._get_mock_runner_obj() |
||
234 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
235 | runner.pre_run() |
||
236 | (status, result, _) = runner.run({'row_index': '4'}) |
||
237 | self.assertTrue(result is not None) |
||
238 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
239 | |||
240 | def test_simple_action_no_file(self): |
||
241 | runner = self._get_mock_runner_obj() |
||
242 | runner.entry_point = 'foo.py' |
||
243 | runner.pre_run() |
||
244 | (status, result, _) = runner.run({}) |
||
245 | self.assertTrue(result is not None) |
||
246 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
247 | |||
248 | def test_simple_action_no_entry_point(self): |
||
249 | runner = self._get_mock_runner_obj() |
||
250 | runner.entry_point = '' |
||
251 | |||
252 | expected_msg = 'Action .*? is missing entry_point attribute' |
||
253 | self.assertRaisesRegexp(Exception, expected_msg, runner.run, {}) |
||
254 | |||
255 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
256 | def test_action_with_user_supplied_env_vars(self, mock_popen): |
||
257 | env_vars = {'key1': 'val1', 'key2': 'val2', 'PYTHONPATH': 'foobar'} |
||
258 | |||
259 | mock_process = mock.Mock() |
||
260 | mock_process.communicate.return_value = ('', '') |
||
261 | mock_popen.return_value = mock_process |
||
262 | |||
263 | runner = self._get_mock_runner_obj() |
||
264 | runner.runner_parameters = {'env': env_vars} |
||
265 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
266 | runner.pre_run() |
||
267 | (_, _, _) = runner.run({'row_index': 4}) |
||
268 | |||
269 | _, call_kwargs = mock_popen.call_args |
||
270 | actual_env = call_kwargs['env'] |
||
271 | |||
272 | for key, value in env_vars.items(): |
||
273 | # Verify that a blacklsited PYTHONPATH has been filtered out |
||
274 | if key == 'PYTHONPATH': |
||
275 | self.assertTrue(actual_env[key] != value) |
||
276 | else: |
||
277 | self.assertEqual(actual_env[key], value) |
||
278 | |||
279 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
280 | @mock.patch('st2common.util.green.shell.eventlet.spawn') |
||
281 | def test_action_stdout_and_stderr_is_not_stored_in_db_by_default(self, mock_spawn, mock_popen): |
||
282 | # Feature should be disabled by default |
||
283 | values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER} |
||
284 | |||
285 | # Note: We need to mock spawn function so we can test everything in single event loop |
||
286 | # iteration |
||
287 | mock_spawn.side_effect = blocking_eventlet_spawn |
||
288 | |||
289 | # No output to stdout and no result (implicit None) |
||
290 | mock_stdout = [ |
||
291 | 'pre result line 1\n', |
||
292 | '%(delimiter)sTrue%(delimiter)s' % values, |
||
293 | 'post result line 1' |
||
294 | ] |
||
295 | mock_stderr = [ |
||
296 | 'stderr line 1\n', |
||
297 | 'stderr line 2\n', |
||
298 | 'stderr line 3\n' |
||
299 | ] |
||
300 | |||
301 | mock_process = mock.Mock() |
||
302 | mock_process.returncode = 0 |
||
303 | mock_popen.return_value = mock_process |
||
304 | mock_process.stdout.closed = False |
||
305 | mock_process.stderr.closed = False |
||
306 | mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout, |
||
307 | stop_counter=3) |
||
308 | mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr, |
||
309 | stop_counter=3) |
||
310 | |||
311 | runner = self._get_mock_runner_obj() |
||
312 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
313 | runner.pre_run() |
||
314 | (_, output, _) = runner.run({'row_index': 4}) |
||
315 | |||
316 | self.assertEqual(output['stdout'], 'pre result line 1\npost result line 1') |
||
317 | self.assertEqual(output['stderr'], 'stderr line 1\nstderr line 2\nstderr line 3\n') |
||
318 | self.assertEqual(output['result'], 'True') |
||
319 | self.assertEqual(output['exit_code'], 0) |
||
320 | |||
321 | output_dbs = ActionExecutionOutput.get_all() |
||
322 | self.assertEqual(len(output_dbs), 0) |
||
323 | |||
324 | # False is a default behavior so end result should be the same |
||
325 | cfg.CONF.set_override(name='stream_output', group='actionrunner', override=False) |
||
326 | |||
327 | mock_process = mock.Mock() |
||
328 | mock_process.returncode = 0 |
||
329 | mock_popen.return_value = mock_process |
||
330 | mock_process.stdout.closed = False |
||
331 | mock_process.stderr.closed = False |
||
332 | mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout, |
||
333 | stop_counter=3) |
||
334 | mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr, |
||
335 | stop_counter=3) |
||
336 | |||
337 | runner.pre_run() |
||
338 | (_, output, _) = runner.run({'row_index': 4}) |
||
339 | |||
340 | self.assertEqual(output['stdout'], 'pre result line 1\npost result line 1') |
||
341 | self.assertEqual(output['stderr'], 'stderr line 1\nstderr line 2\nstderr line 3\n') |
||
342 | self.assertEqual(output['result'], 'True') |
||
343 | self.assertEqual(output['exit_code'], 0) |
||
344 | |||
345 | output_dbs = ActionExecutionOutput.get_all() |
||
346 | self.assertEqual(len(output_dbs), 0) |
||
347 | |||
348 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
349 | @mock.patch('st2common.util.green.shell.eventlet.spawn') |
||
350 | def test_action_stdout_and_stderr_is_stored_in_the_db(self, mock_spawn, mock_popen): |
||
351 | # Feature is enabled |
||
352 | cfg.CONF.set_override(name='stream_output', group='actionrunner', override=True) |
||
353 | |||
354 | values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER} |
||
355 | |||
356 | # Note: We need to mock spawn function so we can test everything in single event loop |
||
357 | # iteration |
||
358 | mock_spawn.side_effect = blocking_eventlet_spawn |
||
359 | |||
360 | # No output to stdout and no result (implicit None) |
||
361 | mock_stdout = [ |
||
362 | 'pre result line 1\n', |
||
363 | 'pre result line 2\n', |
||
364 | '%(delimiter)sTrue%(delimiter)s' % values, |
||
365 | 'post result line 1' |
||
366 | ] |
||
367 | mock_stderr = [ |
||
368 | 'stderr line 1\n', |
||
369 | 'stderr line 2\n', |
||
370 | 'stderr line 3\n' |
||
371 | ] |
||
372 | |||
373 | mock_process = mock.Mock() |
||
374 | mock_process.returncode = 0 |
||
375 | mock_popen.return_value = mock_process |
||
376 | mock_process.stdout.closed = False |
||
377 | mock_process.stderr.closed = False |
||
378 | mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout, |
||
379 | stop_counter=4) |
||
380 | mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr, |
||
381 | stop_counter=3) |
||
382 | |||
383 | runner = self._get_mock_runner_obj() |
||
384 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
385 | runner.pre_run() |
||
386 | (_, output, _) = runner.run({'row_index': 4}) |
||
387 | |||
388 | self.assertEqual(output['stdout'], |
||
389 | 'pre result line 1\npre result line 2\npost result line 1') |
||
390 | self.assertEqual(output['stderr'], 'stderr line 1\nstderr line 2\nstderr line 3\n') |
||
391 | self.assertEqual(output['result'], 'True') |
||
392 | self.assertEqual(output['exit_code'], 0) |
||
393 | |||
394 | # Verify stdout and stderr lines have been correctly stored in the db |
||
395 | # Note - result delimiter should not be stored in the db |
||
396 | output_dbs = ActionExecutionOutput.query(output_type='stdout') |
||
397 | self.assertEqual(len(output_dbs), 3) |
||
398 | self.assertEqual(output_dbs[0].runner_ref, 'python-script') |
||
399 | self.assertEqual(output_dbs[0].data, mock_stdout[0]) |
||
400 | self.assertEqual(output_dbs[1].data, mock_stdout[1]) |
||
401 | self.assertEqual(output_dbs[2].data, mock_stdout[3]) |
||
402 | |||
403 | output_dbs = ActionExecutionOutput.query(output_type='stderr') |
||
404 | self.assertEqual(len(output_dbs), 3) |
||
405 | self.assertEqual(output_dbs[0].runner_ref, 'python-script') |
||
406 | self.assertEqual(output_dbs[0].data, mock_stderr[0]) |
||
407 | self.assertEqual(output_dbs[1].data, mock_stderr[1]) |
||
408 | self.assertEqual(output_dbs[2].data, mock_stderr[2]) |
||
409 | |||
410 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
411 | def test_stdout_interception_and_parsing(self, mock_popen): |
||
412 | values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER} |
||
413 | |||
414 | # No output to stdout and no result (implicit None) |
||
415 | mock_stdout = ['%(delimiter)sNone%(delimiter)s' % values] |
||
416 | mock_stderr = ['foo stderr'] |
||
417 | |||
418 | mock_process = mock.Mock() |
||
419 | mock_process.returncode = 0 |
||
420 | mock_popen.return_value = mock_process |
||
421 | mock_process.stdout.closed = False |
||
422 | mock_process.stderr.closed = False |
||
423 | mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout) |
||
424 | mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr) |
||
425 | |||
426 | runner = self._get_mock_runner_obj() |
||
427 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
428 | runner.pre_run() |
||
429 | (_, output, _) = runner.run({'row_index': 4}) |
||
430 | |||
431 | self.assertEqual(output['stdout'], '') |
||
432 | self.assertEqual(output['stderr'], mock_stderr[0]) |
||
433 | self.assertEqual(output['result'], 'None') |
||
434 | self.assertEqual(output['exit_code'], 0) |
||
435 | |||
436 | # Output to stdout, no result (implicit None), return_code 1 and status failed |
||
437 | mock_stdout = ['pre result%(delimiter)sNone%(delimiter)spost result' % values] |
||
438 | mock_stderr = ['foo stderr'] |
||
439 | |||
440 | mock_process = mock.Mock() |
||
441 | mock_process.returncode = 1 |
||
442 | mock_popen.return_value = mock_process |
||
443 | mock_process.stdout.closed = False |
||
444 | mock_process.stderr.closed = False |
||
445 | mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout) |
||
446 | mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr) |
||
447 | |||
448 | runner = self._get_mock_runner_obj() |
||
449 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
450 | runner.pre_run() |
||
451 | (status, output, _) = runner.run({'row_index': 4}) |
||
452 | |||
453 | self.assertEqual(output['stdout'], 'pre resultpost result') |
||
454 | self.assertEqual(output['stderr'], mock_stderr[0]) |
||
455 | self.assertEqual(output['result'], 'None') |
||
456 | self.assertEqual(output['exit_code'], 1) |
||
457 | self.assertEqual(status, 'failed') |
||
458 | |||
459 | # Output to stdout, no result (implicit None), return_code 1 and status succeeded |
||
460 | mock_stdout = ['pre result%(delimiter)sNone%(delimiter)spost result' % values] |
||
461 | mock_stderr = ['foo stderr'] |
||
462 | |||
463 | mock_process = mock.Mock() |
||
464 | mock_process.returncode = 0 |
||
465 | mock_popen.return_value = mock_process |
||
466 | mock_process.stdout.closed = False |
||
467 | mock_process.stderr.closed = False |
||
468 | mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout) |
||
469 | mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr) |
||
470 | |||
471 | runner = self._get_mock_runner_obj() |
||
472 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
473 | runner.pre_run() |
||
474 | (status, output, _) = runner.run({'row_index': 4}) |
||
475 | |||
476 | self.assertEqual(output['stdout'], 'pre resultpost result') |
||
477 | self.assertEqual(output['stderr'], mock_stderr[0]) |
||
478 | self.assertEqual(output['result'], 'None') |
||
479 | self.assertEqual(output['exit_code'], 0) |
||
480 | self.assertEqual(status, 'succeeded') |
||
481 | |||
482 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
483 | def test_common_st2_env_vars_are_available_to_the_action(self, mock_popen): |
||
484 | mock_process = mock.Mock() |
||
485 | mock_process.communicate.return_value = ('', '') |
||
486 | mock_popen.return_value = mock_process |
||
487 | |||
488 | runner = self._get_mock_runner_obj() |
||
489 | runner.auth_token = mock.Mock() |
||
490 | runner.auth_token.token = 'ponies' |
||
491 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
492 | runner.pre_run() |
||
493 | (_, _, _) = runner.run({'row_index': 4}) |
||
494 | |||
495 | _, call_kwargs = mock_popen.call_args |
||
496 | actual_env = call_kwargs['env'] |
||
497 | self.assertCommonSt2EnvVarsAvailableInEnv(env=actual_env) |
||
498 | |||
499 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
500 | def test_pythonpath_env_var_contains_common_libs_config_enabled(self, mock_popen): |
||
501 | mock_process = mock.Mock() |
||
502 | mock_process.communicate.return_value = ('', '') |
||
503 | mock_popen.return_value = mock_process |
||
504 | |||
505 | runner = self._get_mock_runner_obj() |
||
506 | runner._enable_common_pack_libs = True |
||
0 ignored issues
–
show
|
|||
507 | runner.auth_token = mock.Mock() |
||
508 | runner.auth_token.token = 'ponies' |
||
509 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
510 | runner.pre_run() |
||
511 | (_, _, _) = runner.run({'row_index': 4}) |
||
512 | |||
513 | _, call_kwargs = mock_popen.call_args |
||
514 | actual_env = call_kwargs['env'] |
||
515 | pack_common_lib_path = 'fixtures/packs/core/lib' |
||
516 | self.assertTrue('PYTHONPATH' in actual_env) |
||
517 | self.assertTrue(pack_common_lib_path in actual_env['PYTHONPATH']) |
||
518 | |||
519 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
520 | def test_pythonpath_env_var_not_contains_common_libs_config_disabled(self, mock_popen): |
||
521 | mock_process = mock.Mock() |
||
522 | mock_process.communicate.return_value = ('', '') |
||
523 | mock_popen.return_value = mock_process |
||
524 | |||
525 | runner = self._get_mock_runner_obj() |
||
526 | runner._enable_common_pack_libs = False |
||
0 ignored issues
–
show
It seems like
_enable_common_pack_libs was declared protected and should not be accessed from this context.
Prefixing a member variable class MyParent:
def __init__(self):
self._x = 1;
self.y = 2;
class MyChild(MyParent):
def some_method(self):
return self._x # Ok, since accessed from a child class
class AnotherClass:
def some_method(self, instance_of_my_child):
return instance_of_my_child._x # Would be flagged as AnotherClass is not
# a child class of MyParent
Loading history...
|
|||
527 | runner.auth_token = mock.Mock() |
||
528 | runner.auth_token.token = 'ponies' |
||
529 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
530 | runner.pre_run() |
||
531 | (_, _, _) = runner.run({'row_index': 4}) |
||
532 | |||
533 | _, call_kwargs = mock_popen.call_args |
||
534 | actual_env = call_kwargs['env'] |
||
535 | pack_common_lib_path = '/mnt/src/storm/st2/st2tests/st2tests/fixtures/packs/core/lib' |
||
536 | self.assertTrue('PYTHONPATH' in actual_env) |
||
537 | self.assertTrue(pack_common_lib_path not in actual_env['PYTHONPATH']) |
||
538 | |||
539 | def test_action_class_instantiation_action_service_argument(self): |
||
540 | class Action1(Action): |
||
541 | # Constructor not overriden so no issue here |
||
542 | pass |
||
543 | |||
544 | def run(self): |
||
545 | pass |
||
546 | |||
547 | class Action2(Action): |
||
548 | # Constructor overriden, but takes action_service argument |
||
549 | def __init__(self, config, action_service=None): |
||
550 | super(Action2, self).__init__(config=config, |
||
551 | action_service=action_service) |
||
552 | |||
553 | def run(self): |
||
554 | pass |
||
555 | |||
556 | class Action3(Action): |
||
557 | # Constructor overriden, but doesn't take to action service |
||
558 | def __init__(self, config): |
||
559 | super(Action3, self).__init__(config=config) |
||
560 | |||
561 | def run(self): |
||
562 | pass |
||
563 | |||
564 | config = {'a': 1, 'b': 2} |
||
565 | action_service = 'ActionService!' |
||
566 | |||
567 | action1 = get_action_class_instance(action_cls=Action1, config=config, |
||
568 | action_service=action_service) |
||
569 | self.assertEqual(action1.config, config) |
||
570 | self.assertEqual(action1.action_service, action_service) |
||
571 | |||
572 | action2 = get_action_class_instance(action_cls=Action2, config=config, |
||
573 | action_service=action_service) |
||
574 | self.assertEqual(action2.config, config) |
||
575 | self.assertEqual(action2.action_service, action_service) |
||
576 | |||
577 | action3 = get_action_class_instance(action_cls=Action3, config=config, |
||
578 | action_service=action_service) |
||
579 | self.assertEqual(action3.config, config) |
||
580 | self.assertEqual(action3.action_service, action_service) |
||
581 | |||
582 | def test_action_with_same_module_name_as_module_in_stdlib(self): |
||
583 | runner = self._get_mock_runner_obj() |
||
584 | runner.entry_point = TEST_ACTION_PATH |
||
585 | runner.pre_run() |
||
586 | (status, output, _) = runner.run({}) |
||
587 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
588 | self.assertTrue(output is not None) |
||
589 | self.assertEqual(output['result'], 'test action') |
||
590 | |||
591 | def test_python_action_wrapper_script_doesnt_get_added_to_sys_path(self): |
||
592 | # Validate that the directory where python_action_wrapper.py script is located |
||
593 | # (st2common/runners) doesn't get added to sys.path |
||
594 | runner = self._get_mock_runner_obj() |
||
595 | runner.entry_point = PATHS_ACTION_PATH |
||
596 | runner.pre_run() |
||
597 | (status, output, _) = runner.run({}) |
||
598 | |||
599 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
600 | self.assertTrue(output is not None) |
||
601 | |||
602 | lines = output['stdout'].split('\n') |
||
603 | process_sys_path = lines[0] |
||
604 | process_pythonpath = lines[1] |
||
605 | |||
606 | assert 'sys.path' in process_sys_path |
||
607 | assert 'PYTHONPATH' in process_pythonpath |
||
608 | |||
609 | wrapper_script_path = 'st2common/runners' |
||
610 | |||
611 | assertion_msg = 'Found python wrapper script path in subprocess path' |
||
612 | self.assertTrue(wrapper_script_path not in process_sys_path, assertion_msg) |
||
613 | self.assertTrue(wrapper_script_path not in process_pythonpath, assertion_msg) |
||
614 | |||
615 | def test_python_action_wrapper_action_script_file_doesnt_exist_friendly_error(self): |
||
616 | # File in a directory which is not a Python package |
||
617 | wrapper = PythonActionWrapper(pack='dummy_pack_5', file_path='/tmp/doesnt.exist', |
||
618 | user='joe') |
||
619 | |||
620 | expected_msg = 'File "/tmp/doesnt.exist" has no action class or the file doesn\'t exist.' |
||
621 | self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance) |
||
622 | |||
623 | # File in a directory which is a Python package |
||
624 | wrapper = PythonActionWrapper(pack='dummy_pack_5', file_path=ACTION_1_PATH, |
||
625 | user='joe') |
||
626 | |||
627 | expected_msg = ('Failed to load action class from file ".*?list_repos_doesnt_exist.py" ' |
||
628 | '\(action file most likely doesn\'t exist or contains invalid syntax\): ' |
||
629 | '\[Errno 2\] No such file or directory') |
||
630 | self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance) |
||
631 | |||
632 | def test_python_action_wrapper_action_script_file_contains_invalid_syntax_friendly_error(self): |
||
633 | wrapper = PythonActionWrapper(pack='dummy_pack_5', file_path=ACTION_2_PATH, |
||
634 | user='joe') |
||
635 | expected_msg = ('Failed to load action class from file ".*?invalid_syntax.py" ' |
||
636 | '\(action file most likely doesn\'t exist or contains invalid syntax\): ' |
||
637 | 'No module named \'?invalid\'?') |
||
638 | self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance) |
||
639 | |||
640 | def test_simple_action_log_messages_and_log_level_runner_param(self): |
||
641 | expected_msg_1 = 'st2.actions.python.PascalRowAction: DEBUG Creating new Client object.' |
||
642 | expected_msg_2 = 'Retrieving all the values from the datastore' |
||
643 | |||
644 | expected_msg_3 = 'st2.actions.python.PascalRowAction: INFO test info log message' |
||
645 | expected_msg_4 = 'st2.actions.python.PascalRowAction: DEBUG test debug log message' |
||
646 | expected_msg_5 = 'st2.actions.python.PascalRowAction: ERROR test error log message' |
||
647 | |||
648 | runner = self._get_mock_runner_obj() |
||
649 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
650 | runner.pre_run() |
||
651 | (status, output, _) = runner.run({'row_index': 'e'}) |
||
652 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
653 | self.assertTrue(output is not None) |
||
654 | self.assertEqual(output['result'], [1, 2]) |
||
655 | |||
656 | self.assertTrue(expected_msg_1 in output['stderr']) |
||
657 | self.assertTrue(expected_msg_2 in output['stderr']) |
||
658 | self.assertTrue(expected_msg_3 in output['stderr']) |
||
659 | self.assertTrue(expected_msg_4 in output['stderr']) |
||
660 | self.assertTrue(expected_msg_5 in output['stderr']) |
||
661 | |||
662 | stderr = output['stderr'].strip().split('\n') |
||
663 | expected_count = 5 |
||
664 | |||
665 | # Remove lines we don't care about |
||
666 | lines = [] |
||
667 | for line in stderr: |
||
668 | if 'configuration option is not configured' in line: |
||
669 | continue |
||
670 | |||
671 | if 'No handlers could be found for logger' in line: |
||
672 | continue |
||
673 | |||
674 | lines.append(line) |
||
675 | |||
676 | msg = ('Expected %s lines, got %s - "%s"' % (expected_count, len(lines), str(lines))) |
||
677 | self.assertEqual(len(lines), expected_count, msg) |
||
678 | |||
679 | # Only log messages with level info and above should be displayed |
||
680 | runner = self._get_mock_runner_obj() |
||
681 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
682 | runner.runner_parameters = { |
||
683 | 'log_level': 'info' |
||
684 | } |
||
685 | runner.pre_run() |
||
686 | (status, output, _) = runner.run({'row_index': 'e'}) |
||
687 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
688 | self.assertTrue(output is not None) |
||
689 | self.assertEqual(output['result'], [1, 2]) |
||
690 | |||
691 | self.assertTrue(expected_msg_3 in output['stderr']) |
||
692 | self.assertTrue(expected_msg_4 not in output['stderr']) |
||
693 | self.assertTrue(expected_msg_5 in output['stderr']) |
||
694 | |||
695 | # Only log messages with level error and above should be displayed |
||
696 | runner = self._get_mock_runner_obj() |
||
697 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
698 | runner.runner_parameters = { |
||
699 | 'log_level': 'error' |
||
700 | } |
||
701 | runner.pre_run() |
||
702 | (status, output, _) = runner.run({'row_index': 'e'}) |
||
703 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
704 | self.assertTrue(output is not None) |
||
705 | self.assertEqual(output['result'], [1, 2]) |
||
706 | |||
707 | self.assertTrue(expected_msg_3 not in output['stderr']) |
||
708 | self.assertTrue(expected_msg_4 not in output['stderr']) |
||
709 | self.assertTrue(expected_msg_5 in output['stderr']) |
||
710 | |||
711 | # Default log level is changed in st2.config |
||
712 | cfg.CONF.set_override(name='python_runner_log_level', override='INFO', |
||
713 | group='actionrunner') |
||
714 | |||
715 | runner = self._get_mock_runner_obj() |
||
716 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
717 | runner.runner_parameters = {} |
||
718 | runner.pre_run() |
||
719 | (status, output, _) = runner.run({'row_index': 'e'}) |
||
720 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
721 | self.assertTrue(output is not None) |
||
722 | self.assertEqual(output['result'], [1, 2]) |
||
723 | |||
724 | self.assertTrue(expected_msg_3 in output['stderr']) |
||
725 | self.assertTrue(expected_msg_4 not in output['stderr']) |
||
726 | self.assertTrue(expected_msg_5 in output['stderr']) |
||
727 | |||
728 | def test_traceback_messages_are_not_duplicated_in_stderr(self): |
||
729 | # Verify tracebacks are not duplicated |
||
730 | runner = self._get_mock_runner_obj() |
||
731 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
732 | runner.pre_run() |
||
733 | (status, output, _) = runner.run({'row_index': 'f'}) |
||
734 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
735 | self.assertTrue(output is not None) |
||
736 | |||
737 | expected_msg_1 = 'Traceback (most recent' |
||
738 | expected_msg_2 = 'ValueError: Duplicate traceback test' |
||
739 | |||
740 | self.assertTrue(expected_msg_1 in output['stderr']) |
||
741 | self.assertTrue(expected_msg_2 in output['stderr']) |
||
742 | |||
743 | self.assertEqual(output['stderr'].count(expected_msg_1), 1) |
||
744 | self.assertEqual(output['stderr'].count(expected_msg_2), 1) |
||
745 | |||
746 | def test_execution_with_very_large_parameter(self): |
||
747 | runner = self._get_mock_runner_obj() |
||
748 | runner.entry_point = ECHOER_ACTION_PATH |
||
749 | runner.pre_run() |
||
750 | large_value = ''.join(['1' for _ in range(MAX_PARAM_LENGTH)]) |
||
751 | (status, output, _) = runner.run({'action_input': large_value}) |
||
752 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
753 | self.assertTrue(output is not None) |
||
754 | self.assertEqual(output['result']['action_input'], large_value) |
||
755 | |||
756 | def test_execution_with_close_to_very_large_parameter(self): |
||
757 | runner = self._get_mock_runner_obj() |
||
758 | runner.entry_point = ECHOER_ACTION_PATH |
||
759 | runner.pre_run() |
||
760 | # 21 is the minimum overhead required to make the param fall back to |
||
761 | # param based payload. The linux max includes all parts of the param |
||
762 | # not just the value portion. So we need to subtract the remaining |
||
763 | # overhead from the initial padding. |
||
764 | large_value = ''.join(['1' for _ in range(MAX_PARAM_LENGTH - 21)]) |
||
765 | (status, output, _) = runner.run({'action_input': large_value}) |
||
766 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
767 | self.assertTrue(output is not None) |
||
768 | self.assertEqual(output['result']['action_input'], large_value) |
||
769 | |||
770 | @mock.patch('python_runner.python_runner.get_sandbox_virtualenv_path') |
||
771 | def test_content_version_success(self, mock_get_sandbox_virtualenv_path): |
||
772 | mock_get_sandbox_virtualenv_path.return_value = None |
||
773 | |||
774 | # 1. valid version - 0.2.0 |
||
775 | runner = self._get_mock_runner_obj(pack='test_content_version', sandbox=False) |
||
776 | runner.entry_point = PRINT_VERSION_ACTION |
||
777 | runner.runner_parameters = {'content_version': 'v0.2.0'} |
||
778 | runner.pre_run() |
||
779 | |||
780 | (status, output, _) = runner.run({}) |
||
781 | |||
782 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
783 | self.assertEqual(output['result'], 'v0.2.0') |
||
784 | self.assertEqual(output['stdout'].strip(), 'v0.2.0') |
||
785 | |||
786 | # 2. valid version - 0.23.0 |
||
787 | runner = self._get_mock_runner_obj(pack='test_content_version', sandbox=False) |
||
788 | runner.entry_point = PRINT_VERSION_ACTION |
||
789 | runner.runner_parameters = {'content_version': 'v0.3.0'} |
||
790 | runner.pre_run() |
||
791 | |||
792 | (status, output, _) = runner.run({}) |
||
793 | |||
794 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
795 | self.assertEqual(output['result'], 'v0.3.0') |
||
796 | self.assertEqual(output['stdout'].strip(), 'v0.3.0') |
||
797 | |||
798 | # 3. invalid version = 0.30.0 |
||
799 | runner = self._get_mock_runner_obj(pack='test_content_version', sandbox=False) |
||
800 | runner.entry_point = PRINT_VERSION_ACTION |
||
801 | runner.runner_parameters = {'content_version': 'v0.30.0'} |
||
802 | |||
803 | expected_msg = (r'Failed to create git worktree for pack "test_content_version": ' |
||
804 | 'Invalid content_version ' |
||
805 | '"v0.30.0" provided. Make sure that git repository is up ' |
||
806 | 'to date and contains that revision.') |
||
807 | self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run) |
||
808 | |||
809 | @mock.patch('python_runner.python_runner.get_sandbox_virtualenv_path') |
||
810 | @mock.patch('st2common.util.green.shell.subprocess.Popen') |
||
811 | def test_content_version_contains_common_libs_config_enabled(self, mock_popen, |
||
812 | mock_get_sandbox_virtualenv_path): |
||
813 | # Verify that the common libs path correctly reflects directory in git worktree |
||
814 | mock_get_sandbox_virtualenv_path.return_value = None |
||
815 | |||
816 | mock_process = mock.Mock() |
||
817 | mock_process.communicate.return_value = ('', '') |
||
818 | mock_popen.return_value = mock_process |
||
819 | |||
820 | runner = self._get_mock_runner_obj(pack='test_content_version', sandbox=False) |
||
821 | runner._enable_common_pack_libs = True |
||
822 | runner.auth_token = mock.Mock() |
||
823 | runner.auth_token.token = 'ponies' |
||
824 | runner.runner_parameters = {'content_version': 'v0.3.0'} |
||
825 | runner.entry_point = PRINT_VERSION_ACTION |
||
826 | runner.pre_run() |
||
827 | (_, _, _) = runner.run({'row_index': 4}) |
||
828 | |||
829 | _, call_kwargs = mock_popen.call_args |
||
830 | actual_env = call_kwargs['env'] |
||
831 | pack_common_lib_path = os.path.join(runner.git_worktree_path, 'lib') |
||
832 | self.assertTrue('PYTHONPATH' in actual_env) |
||
833 | self.assertTrue(pack_common_lib_path in actual_env['PYTHONPATH']) |
||
834 | |||
835 | @mock.patch('python_runner.python_runner.get_sandbox_virtualenv_path') |
||
836 | def test_content_version_success_local_modules_work_fine(self, |
||
837 | mock_get_sandbox_virtualenv_path): |
||
838 | # Verify that local module import correctly use git worktree directory |
||
839 | mock_get_sandbox_virtualenv_path.return_value = None |
||
840 | |||
841 | runner = self._get_mock_runner_obj(pack='test_content_version', sandbox=False) |
||
842 | runner.entry_point = PRINT_VERSION_LOCAL_MODULE_ACTION |
||
843 | runner.runner_parameters = {'content_version': 'v0.2.0'} |
||
844 | runner.pre_run() |
||
845 | |||
846 | (status, output, _) = runner.run({}) |
||
847 | |||
848 | self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED) |
||
849 | self.assertEqual(output['result'], 'v0.2.0') |
||
850 | |||
851 | # Verify local_module has been correctly loaded from git work tree directory |
||
852 | expected_stdout = ("<module '?local_module'? from '?%s/actions/local_module.py'?>.*" % |
||
853 | runner.git_worktree_path) |
||
854 | self.assertRegexpMatches(output['stdout'].strip(), expected_stdout) |
||
855 | |||
856 | @mock.patch('st2common.runners.base.run_command') |
||
857 | def test_content_version_old_git_version(self, mock_run_command): |
||
858 | mock_stdout = '' |
||
859 | mock_stderr = ''' |
||
860 | git: 'worktree' is not a git command. See 'git --help'. |
||
861 | ''' |
||
862 | mock_stderr = six.text_type(mock_stderr) |
||
863 | mock_run_command.return_value = 1, mock_stdout, mock_stderr, False |
||
864 | |||
865 | runner = self._get_mock_runner_obj() |
||
866 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
867 | runner.runner_parameters = {'content_version': 'v0.10.0'} |
||
868 | |||
869 | expected_msg = (r'Failed to create git worktree for pack "core": Installed git version ' |
||
870 | 'doesn\'t support git worktree command. To be able to utilize this ' |
||
871 | 'functionality you need to use git >= 2.5.0.') |
||
872 | self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run) |
||
873 | |||
874 | @mock.patch('st2common.runners.base.run_command') |
||
875 | def test_content_version_pack_repo_not_git_repository(self, mock_run_command): |
||
876 | mock_stdout = '' |
||
877 | mock_stderr = ''' |
||
878 | fatal: Not a git repository (or any parent up to mount point /home) |
||
879 | Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set). |
||
880 | ''' |
||
881 | mock_stderr = six.text_type(mock_stderr) |
||
882 | mock_run_command.return_value = 1, mock_stdout, mock_stderr, False |
||
883 | |||
884 | runner = self._get_mock_runner_obj() |
||
885 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
886 | runner.runner_parameters = {'content_version': 'v0.10.0'} |
||
887 | |||
888 | expected_msg = (r'Failed to create git worktree for pack "core": Pack directory ' |
||
889 | '".*" is not a ' |
||
890 | 'git repository. To utilize this functionality, pack directory needs to ' |
||
891 | 'be a git repository.') |
||
892 | self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run) |
||
893 | |||
894 | @mock.patch('st2common.runners.base.run_command') |
||
895 | def test_content_version_invalid_git_revision(self, mock_run_command): |
||
896 | mock_stdout = '' |
||
897 | mock_stderr = ''' |
||
898 | fatal: invalid reference: vinvalid |
||
899 | ''' |
||
900 | mock_stderr = six.text_type(mock_stderr) |
||
901 | mock_run_command.return_value = 1, mock_stdout, mock_stderr, False |
||
902 | |||
903 | runner = self._get_mock_runner_obj() |
||
904 | runner.entry_point = PASCAL_ROW_ACTION_PATH |
||
905 | runner.runner_parameters = {'content_version': 'vinvalid'} |
||
906 | |||
907 | expected_msg = (r'Failed to create git worktree for pack "core": Invalid content_version ' |
||
908 | '"vinvalid" provided. Make sure that git repository is up ' |
||
909 | 'to date and contains that revision.') |
||
910 | self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run) |
||
911 | |||
912 | def test_missing_config_item_user_friendly_error(self): |
||
913 | runner = self._get_mock_runner_obj() |
||
914 | runner.entry_point = PRINT_CONFIG_ITEM_ACTION |
||
915 | runner.pre_run() |
||
916 | (status, output, _) = runner.run({}) |
||
917 | |||
918 | self.assertEqual(status, LIVEACTION_STATUS_FAILED) |
||
919 | self.assertTrue(output is not None) |
||
920 | self.assertTrue('{}' in output['stdout']) |
||
921 | self.assertTrue('default_value' in output['stdout']) |
||
922 | self.assertTrue('Config for pack "core" is missing key "key"' in output['stderr']) |
||
923 | self.assertTrue('make sure you run "st2ctl reload --register-configs"' in output['stderr']) |
||
924 | |||
925 | def _get_mock_runner_obj(self, pack=None, sandbox=None): |
||
926 | runner = python_runner.get_runner() |
||
927 | runner.execution = MOCK_EXECUTION |
||
928 | runner.action = self._get_mock_action_obj() |
||
929 | runner.runner_parameters = {} |
||
930 | |||
931 | if pack: |
||
932 | runner.action.pack = pack |
||
933 | |||
934 | if sandbox is not None: |
||
935 | runner._sandbox = sandbox |
||
936 | |||
937 | return runner |
||
938 | |||
939 | @mock.patch('st2actions.container.base.ActionExecution.get', mock.Mock()) |
||
940 | def _get_mock_runner_obj_from_container(self, pack, user, sandbox=None): |
||
941 | container = RunnerContainer() |
||
942 | |||
943 | runnertype_db = mock.Mock() |
||
944 | runnertype_db.runner_package = 'python_runner' |
||
945 | runnertype_db.runner_module = 'python_runner' |
||
946 | action_db = mock.Mock() |
||
947 | action_db.pack = pack |
||
948 | action_db.entry_point = 'foo.py' |
||
949 | liveaction_db = mock.Mock() |
||
950 | liveaction_db.id = '123' |
||
951 | liveaction_db.context = {'user': user} |
||
952 | runner = container._get_runner(runner_type_db=runnertype_db, action_db=action_db, |
||
953 | liveaction_db=liveaction_db) |
||
954 | runner.execution = MOCK_EXECUTION |
||
955 | runner.action = action_db |
||
956 | runner.runner_parameters = {} |
||
957 | |||
958 | if sandbox is not None: |
||
959 | runner._sandbox = sandbox |
||
960 | |||
961 | return runner |
||
962 | |||
963 | def _get_mock_action_obj(self): |
||
964 | """ |
||
965 | Return mock action object. |
||
966 | |||
967 | Pack gets set to the system pack so the action doesn't require a separate virtualenv. |
||
968 | """ |
||
969 | action = mock.Mock() |
||
970 | action.ref = 'dummy.action' |
||
971 | action.pack = SYSTEM_PACK_NAME |
||
972 | action.entry_point = 'foo.py' |
||
973 | action.runner_type = { |
||
974 | 'name': 'python-script' |
||
975 | } |
||
976 | return action |
||
977 |
Prefixing a member variable
_
is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class: