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 .*?>}\]") |
||
0 ignored issues
–
show
|
|||
108 | else: |
||
109 | expected_result_re = (r"\[{'a': '1'}, {'c': 2, 'h': 3}, {'e': " |
||
110 | "<non_simple_type.Test object at .*?>}\]") |
||
0 ignored issues
–
show
A suspicious escape sequence
\] was found. Did you maybe forget to add an r prefix?
Escape sequences in Python are generally interpreted according to rules similar
to standard C. Only if strings are prefixed with The escape sequence that was used indicates that you might have intended to write a regular expression. Learn more about the available escape sequences. in the Python documentation.
Loading history...
|
|||
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 |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
210 | self.assertEqual(runner._config['regions'], ['us-west-1']) # static value |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
211 | self.assertEqual(runner._config['api_secret'], None) |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
212 | self.assertEqual(runner._config['private_key_path'], None) |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
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 |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
228 | self.assertEqual(runner._config['regions'], ['us-west-1']) # static value |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
229 | self.assertEqual(runner._config['api_secret'], 'foosecret') |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
230 | self.assertEqual(runner._config['private_key_path'], 'foopath') |
||
0 ignored issues
–
show
It seems like
_config 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...
|
|||
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 |
||
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 |
||
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 |
||
0 ignored issues
–
show
|
|||
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) |
||
0 ignored issues
–
show
It seems like
_get_action_instance 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...
|
|||
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\): ' |
||
0 ignored issues
–
show
A suspicious escape sequence
\( was found. Did you maybe forget to add an r prefix?
Escape sequences in Python are generally interpreted according to rules similar
to standard C. Only if strings are prefixed with The escape sequence that was used indicates that you might have intended to write a regular expression. Learn more about the available escape sequences. in the Python documentation.
Loading history...
A suspicious escape sequence
\) was found. Did you maybe forget to add an r prefix?
Escape sequences in Python are generally interpreted according to rules similar
to standard C. Only if strings are prefixed with The escape sequence that was used indicates that you might have intended to write a regular expression. Learn more about the available escape sequences. in the Python documentation.
Loading history...
|
|||
629 | '\[Errno 2\] No such file or directory') |
||
0 ignored issues
–
show
A suspicious escape sequence
\[ was found. Did you maybe forget to add an r prefix?
Escape sequences in Python are generally interpreted according to rules similar
to standard C. Only if strings are prefixed with The escape sequence that was used indicates that you might have intended to write a regular expression. Learn more about the available escape sequences. in the Python documentation.
Loading history...
A suspicious escape sequence
\] was found. Did you maybe forget to add an r prefix?
Escape sequences in Python are generally interpreted according to rules similar
to standard C. Only if strings are prefixed with The escape sequence that was used indicates that you might have intended to write a regular expression. Learn more about the available escape sequences. in the Python documentation.
Loading history...
|
|||
630 | self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance) |
||
0 ignored issues
–
show
It seems like
_get_action_instance 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...
|
|||
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\): ' |
||
0 ignored issues
–
show
A suspicious escape sequence
\( was found. Did you maybe forget to add an r prefix?
Escape sequences in Python are generally interpreted according to rules similar
to standard C. Only if strings are prefixed with The escape sequence that was used indicates that you might have intended to write a regular expression. Learn more about the available escape sequences. in the Python documentation.
Loading history...
A suspicious escape sequence
\) was found. Did you maybe forget to add an r prefix?
Escape sequences in Python are generally interpreted according to rules similar
to standard C. Only if strings are prefixed with The escape sequence that was used indicates that you might have intended to write a regular expression. Learn more about the available escape sequences. in the Python documentation.
Loading history...
|
|||
637 | 'No module named \'?invalid\'?') |
||
638 | self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance) |
||
0 ignored issues
–
show
It seems like
_get_action_instance 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...
|
|||
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 |
||
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...
|
|||
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 |
||
0 ignored issues
–
show
It seems like
_sandbox 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...
|
|||
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, |
||
0 ignored issues
–
show
It seems like
_get_runner 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...
|
|||
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 |
||
0 ignored issues
–
show
It seems like
_sandbox 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...
|
|||
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 |
Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with
r
orR
are they interpreted as regular expressions.The escape sequence that was used indicates that you might have intended to write a regular expression.
Learn more about the available escape sequences. in the Python documentation.