Passed
Push — develop ( 9f382f...4cae1d )
by
unknown
07:20 queued 04:09
created

_get_mock_runner_obj_from_container()   A

Complexity

Conditions 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 19
rs 9.4285
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
import os
17
import re
18
19
import mock
20
from oslo_config import cfg
21
22
import python_runner
23
from st2actions.container.base import RunnerContainer
24
from st2common.runners.python_action_wrapper import PythonActionWrapper
25
from st2common.runners.base_action import Action
26
from st2common.runners.utils import get_action_class_instance
27
from st2common.services import config as config_service
28
from st2common.constants.action import ACTION_OUTPUT_RESULT_DELIMITER
29
from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED, LIVEACTION_STATUS_FAILED
30
from st2common.constants.action import LIVEACTION_STATUS_TIMED_OUT
31
from st2common.constants.pack import SYSTEM_PACK_NAME
32
from st2common.persistence.execution import ActionExecutionOutput
33
from st2tests.base import RunnerTestCase
34
from st2tests.base import CleanDbTestCase
35
from st2tests.base import blocking_eventlet_spawn
36
from st2tests.base import make_mock_stream_readline
37
import st2tests.base as tests_base
38
39
40
PASCAL_ROW_ACTION_PATH = os.path.join(tests_base.get_resources_path(), 'packs',
41
                                      'pythonactions/actions/pascal_row.py')
42
TEST_ACTION_PATH = os.path.join(tests_base.get_resources_path(), 'packs',
43
                                'pythonactions/actions/test.py')
44
PATHS_ACTION_PATH = os.path.join(tests_base.get_resources_path(), 'packs',
45
                                'pythonactions/actions/python_paths.py')
46
ACTION_1_PATH = os.path.join(tests_base.get_fixtures_path(),
47
                             'packs/dummy_pack_9/actions/list_repos_doesnt_exist.py')
48
ACTION_2_PATH = os.path.join(tests_base.get_fixtures_path(),
49
                             'packs/dummy_pack_9/actions/invalid_syntax.py')
50
NON_SIMPLE_TYPE_ACTION = os.path.join(tests_base.get_resources_path(), 'packs',
51
                                      'pythonactions/actions/non_simple_type.py')
52
53
# Note: runner inherits parent args which doesn't work with tests since test pass additional
54
# unrecognized args
55
mock_sys = mock.Mock()
56
mock_sys.argv = []
57
58
MOCK_EXECUTION = mock.Mock()
59
MOCK_EXECUTION.id = '598dbf0c0640fd54bffc688b'
60
61
62
@mock.patch('python_runner.sys', mock_sys)
63
class PythonRunnerTestCase(RunnerTestCase, CleanDbTestCase):
64
    register_packs = True
65
    register_pack_configs = True
66
67
    def test_runner_creation(self):
68
        runner = python_runner.get_runner()
69
        self.assertTrue(runner is not None, 'Creation failed. No instance.')
70
        self.assertEqual(type(runner), python_runner.PythonRunner, 'Creation failed. No instance.')
71
72
    def test_action_returns_non_serializable_result(self):
73
        # Actions returns non-simple type which can't be serialized, verify result is simple str()
74
        # representation of the result
75
        runner = self._get_mock_runner_obj()
76
        runner.entry_point = NON_SIMPLE_TYPE_ACTION
77
        runner.pre_run()
78
        (status, output, _) = runner.run({})
79
80
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
81
        self.assertTrue(output is not None)
82
83
        expected_result_re = (r"\[{'a': '1'}, {'h': 3, 'c': 2}, {'e': "
84
                              "<non_simple_type.Test object at .*?>}\]")
0 ignored issues
show
Bug introduced by
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 r or R 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.

Loading history...
85
        match = re.match(expected_result_re, output['result'])
86
        self.assertTrue(match)
87
88
    def test_simple_action_with_result_no_status(self):
89
        runner = self._get_mock_runner_obj()
90
        runner.entry_point = PASCAL_ROW_ACTION_PATH
91
        runner.pre_run()
92
        (status, output, _) = runner.run({'row_index': 5})
93
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
94
        self.assertTrue(output is not None)
95
        self.assertEqual(output['result'], [1, 5, 10, 10, 5, 1])
96
97
    def test_simple_action_with_result_as_None_no_status(self):
98
        runner = self._get_mock_runner_obj()
99
        runner.entry_point = PASCAL_ROW_ACTION_PATH
100
        runner.pre_run()
101
        (status, output, _) = runner.run({'row_index': 'b'})
102
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
103
        self.assertTrue(output is not None)
104
        self.assertEqual(output['exit_code'], 0)
105
        self.assertEqual(output['result'], None)
106
107
    def test_simple_action_timeout(self):
108
        timeout = 0
109
        runner = self._get_mock_runner_obj()
110
        runner.runner_parameters = {python_runner.RUNNER_TIMEOUT: timeout}
111
        runner.entry_point = PASCAL_ROW_ACTION_PATH
112
        runner.pre_run()
113
        (status, output, _) = runner.run({'row_index': 4})
114
        self.assertEqual(status, LIVEACTION_STATUS_TIMED_OUT)
115
        self.assertTrue(output is not None)
116
        self.assertEqual(output['result'], 'None')
117
        self.assertEqual(output['error'], 'Action failed to complete in 0 seconds')
118
        self.assertEqual(output['exit_code'], -9)
119
120
    def test_simple_action_with_status_succeeded(self):
121
        runner = self._get_mock_runner_obj()
122
        runner.entry_point = PASCAL_ROW_ACTION_PATH
123
        runner.pre_run()
124
        (status, output, _) = runner.run({'row_index': 4})
125
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
126
        self.assertTrue(output is not None)
127
        self.assertEqual(output['result'], [1, 4, 6, 4, 1])
128
129
    def test_simple_action_with_status_failed(self):
130
        runner = self._get_mock_runner_obj()
131
        runner.entry_point = PASCAL_ROW_ACTION_PATH
132
        runner.pre_run()
133
        (status, output, _) = runner.run({'row_index': 'a'})
134
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
135
        self.assertTrue(output is not None)
136
        self.assertEqual(output['result'], "This is suppose to fail don't worry!!")
137
138
    def test_simple_action_with_status_complex_type_returned_for_result(self):
139
        # Result containing a complex type shouldn't break the returning a tuple with status
140
        # behavior
141
        runner = self._get_mock_runner_obj()
142
        runner.entry_point = PASCAL_ROW_ACTION_PATH
143
        runner.pre_run()
144
        (status, output, _) = runner.run({'row_index': 'complex_type'})
145
146
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
147
        self.assertTrue(output is not None)
148
        self.assertTrue('<pascal_row.PascalRowAction object at' in output['result'])
149
150
    def test_simple_action_with_status_failed_result_none(self):
151
        runner = self._get_mock_runner_obj()
152
        runner.entry_point = PASCAL_ROW_ACTION_PATH
153
        runner.pre_run()
154
        (status, output, _) = runner.run({'row_index': 'c'})
155
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
156
        self.assertTrue(output is not None)
157
        self.assertEqual(output['result'], None)
158
159
    def test_exception_in_simple_action_with_invalid_status(self):
160
        runner = self._get_mock_runner_obj()
161
        runner.entry_point = PASCAL_ROW_ACTION_PATH
162
        runner.pre_run()
163
        self.assertRaises(ValueError,
164
                          runner.run, action_parameters={'row_index': 'd'})
165
166
    def test_simple_action_no_status_backward_compatibility(self):
167
        runner = self._get_mock_runner_obj()
168
        runner.entry_point = PASCAL_ROW_ACTION_PATH
169
        runner.pre_run()
170
        (status, output, _) = runner.run({'row_index': 'e'})
171
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
172
        self.assertTrue(output is not None)
173
        self.assertEqual(output['result'], [1, 2])
174
175
    def test_simple_action_config_value_provided_overriden_in_datastore(self):
176
        pack = 'dummy_pack_5'
177
        user = 'joe'
178
179
        # No values provided in the datastore
180
        runner = self._get_mock_runner_obj_from_container(pack=pack, user=user)
181
182
        self.assertEqual(runner._config['api_key'], 'some_api_key')  # static value
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
183
        self.assertEqual(runner._config['regions'], ['us-west-1'])  # static value
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
184
        self.assertEqual(runner._config['api_secret'], None)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
185
        self.assertEqual(runner._config['private_key_path'], None)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
186
187
        # api_secret overriden in the datastore (user scoped value)
188
        config_service.set_datastore_value_for_config_key(pack_name='dummy_pack_5',
189
                                                          key_name='api_secret',
190
                                                          user='joe',
191
                                                          value='foosecret',
192
                                                          secret=True)
193
194
        # private_key_path overriden in the datastore (global / non-user scoped value)
195
        config_service.set_datastore_value_for_config_key(pack_name='dummy_pack_5',
196
                                                          key_name='private_key_path',
197
                                                          value='foopath')
198
199
        runner = self._get_mock_runner_obj_from_container(pack=pack, user=user)
200
        self.assertEqual(runner._config['api_key'], 'some_api_key')  # static value
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
201
        self.assertEqual(runner._config['regions'], ['us-west-1'])  # static value
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
202
        self.assertEqual(runner._config['api_secret'], 'foosecret')
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
203
        self.assertEqual(runner._config['private_key_path'], 'foopath')
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _config was declared protected and should not be accessed from this context.

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:

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...
204
205
    def test_simple_action_fail(self):
206
        runner = self._get_mock_runner_obj()
207
        runner.entry_point = PASCAL_ROW_ACTION_PATH
208
        runner.pre_run()
209
        (status, result, _) = runner.run({'row_index': '4'})
210
        self.assertTrue(result is not None)
211
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
212
213
    def test_simple_action_no_file(self):
214
        runner = self._get_mock_runner_obj()
215
        runner.entry_point = 'foo.py'
216
        runner.pre_run()
217
        (status, result, _) = runner.run({})
218
        self.assertTrue(result is not None)
219
        self.assertEqual(status, LIVEACTION_STATUS_FAILED)
220
221
    def test_simple_action_no_entry_point(self):
222
        runner = self._get_mock_runner_obj()
223
        runner.entry_point = ''
224
225
        expected_msg = 'Action .*? is missing entry_point attribute'
226
        self.assertRaisesRegexp(Exception, expected_msg, runner.run, {})
227
228
    @mock.patch('st2common.util.green.shell.subprocess.Popen')
229
    def test_action_with_user_supplied_env_vars(self, mock_popen):
230
        env_vars = {'key1': 'val1', 'key2': 'val2', 'PYTHONPATH': 'foobar'}
231
232
        mock_process = mock.Mock()
233
        mock_process.communicate.return_value = ('', '')
234
        mock_popen.return_value = mock_process
235
236
        runner = self._get_mock_runner_obj()
237
        runner.runner_parameters = {'env': env_vars}
238
        runner.entry_point = PASCAL_ROW_ACTION_PATH
239
        runner.pre_run()
240
        (_, _, _) = runner.run({'row_index': 4})
241
242
        _, call_kwargs = mock_popen.call_args
243
        actual_env = call_kwargs['env']
244
245
        for key, value in env_vars.items():
246
            # Verify that a blacklsited PYTHONPATH has been filtered out
247
            if key == 'PYTHONPATH':
248
                self.assertTrue(actual_env[key] != value)
249
            else:
250
                self.assertEqual(actual_env[key], value)
251
252
    @mock.patch('st2common.util.green.shell.subprocess.Popen')
253
    @mock.patch('st2common.util.green.shell.eventlet.spawn')
254
    def test_action_stdout_and_stderr_is_not_stored_in_db_by_default(self, mock_spawn, mock_popen):
255
        # Feature should be disabled by default
256
        values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER}
257
258
        # Note: We need to mock spawn function so we can test everything in single event loop
259
        # iteration
260
        mock_spawn.side_effect = blocking_eventlet_spawn
261
262
        # No output to stdout and no result (implicit None)
263
        mock_stdout = [
264
            'pre result line 1\n',
265
            '%(delimiter)sTrue%(delimiter)s' % values,
266
            'post result line 1'
267
        ]
268
        mock_stderr = [
269
            'stderr line 1\n',
270
            'stderr line 2\n',
271
            'stderr line 3\n'
272
        ]
273
274
        mock_process = mock.Mock()
275
        mock_process.returncode = 0
276
        mock_popen.return_value = mock_process
277
        mock_process.stdout.closed = False
278
        mock_process.stderr.closed = False
279
        mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout,
280
                                                                 stop_counter=3)
281
        mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr,
282
                                                                 stop_counter=3)
283
284
        runner = self._get_mock_runner_obj()
285
        runner.entry_point = PASCAL_ROW_ACTION_PATH
286
        runner.pre_run()
287
        (_, output, _) = runner.run({'row_index': 4})
288
289
        self.assertEqual(output['stdout'], 'pre result line 1\npost result line 1')
290
        self.assertEqual(output['stderr'], 'stderr line 1\nstderr line 2\nstderr line 3\n')
291
        self.assertEqual(output['result'], 'True')
292
        self.assertEqual(output['exit_code'], 0)
293
294
        output_dbs = ActionExecutionOutput.get_all()
295
        self.assertEqual(len(output_dbs), 0)
296
297
        # False is a default behavior so end result should be the same
298
        cfg.CONF.set_override(name='stream_output', group='actionrunner', override=False)
299
300
        mock_process = mock.Mock()
301
        mock_process.returncode = 0
302
        mock_popen.return_value = mock_process
303
        mock_process.stdout.closed = False
304
        mock_process.stderr.closed = False
305
        mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout,
306
                                                                 stop_counter=3)
307
        mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr,
308
                                                                 stop_counter=3)
309
310
        runner.pre_run()
311
        (_, output, _) = runner.run({'row_index': 4})
312
313
        self.assertEqual(output['stdout'], 'pre result line 1\npost result line 1')
314
        self.assertEqual(output['stderr'], 'stderr line 1\nstderr line 2\nstderr line 3\n')
315
        self.assertEqual(output['result'], 'True')
316
        self.assertEqual(output['exit_code'], 0)
317
318
        output_dbs = ActionExecutionOutput.get_all()
319
        self.assertEqual(len(output_dbs), 0)
320
321
    @mock.patch('st2common.util.green.shell.subprocess.Popen')
322
    @mock.patch('st2common.util.green.shell.eventlet.spawn')
323
    def test_action_stdout_and_stderr_is_stored_in_the_db(self, mock_spawn, mock_popen):
324
        # Feature is enabled
325
        cfg.CONF.set_override(name='stream_output', group='actionrunner', override=True)
326
327
        values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER}
328
329
        # Note: We need to mock spawn function so we can test everything in single event loop
330
        # iteration
331
        mock_spawn.side_effect = blocking_eventlet_spawn
332
333
        # No output to stdout and no result (implicit None)
334
        mock_stdout = [
335
            'pre result line 1\n',
336
            'pre result line 2\n',
337
            '%(delimiter)sTrue%(delimiter)s' % values,
338
            'post result line 1'
339
        ]
340
        mock_stderr = [
341
            'stderr line 1\n',
342
            'stderr line 2\n',
343
            'stderr line 3\n'
344
        ]
345
346
        mock_process = mock.Mock()
347
        mock_process.returncode = 0
348
        mock_popen.return_value = mock_process
349
        mock_process.stdout.closed = False
350
        mock_process.stderr.closed = False
351
        mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout,
352
                                                                 stop_counter=4)
353
        mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr,
354
                                                                 stop_counter=3)
355
356
        runner = self._get_mock_runner_obj()
357
        runner.entry_point = PASCAL_ROW_ACTION_PATH
358
        runner.pre_run()
359
        (_, output, _) = runner.run({'row_index': 4})
360
361
        self.assertEqual(output['stdout'],
362
                         'pre result line 1\npre result line 2\npost result line 1')
363
        self.assertEqual(output['stderr'], 'stderr line 1\nstderr line 2\nstderr line 3\n')
364
        self.assertEqual(output['result'], 'True')
365
        self.assertEqual(output['exit_code'], 0)
366
367
        # Verify stdout and stderr lines have been correctly stored in the db
368
        # Note - result delimiter should not be stored in the db
369
        output_dbs = ActionExecutionOutput.query(output_type='stdout')
370
        self.assertEqual(len(output_dbs), 3)
371
        self.assertEqual(output_dbs[0].runner_ref, 'python-script')
372
        self.assertEqual(output_dbs[0].data, mock_stdout[0])
373
        self.assertEqual(output_dbs[1].data, mock_stdout[1])
374
        self.assertEqual(output_dbs[2].data, mock_stdout[3])
375
376
        output_dbs = ActionExecutionOutput.query(output_type='stderr')
377
        self.assertEqual(len(output_dbs), 3)
378
        self.assertEqual(output_dbs[0].runner_ref, 'python-script')
379
        self.assertEqual(output_dbs[0].data, mock_stderr[0])
380
        self.assertEqual(output_dbs[1].data, mock_stderr[1])
381
        self.assertEqual(output_dbs[2].data, mock_stderr[2])
382
383
    @mock.patch('st2common.util.green.shell.subprocess.Popen')
384
    def test_stdout_interception_and_parsing(self, mock_popen):
385
        values = {'delimiter': ACTION_OUTPUT_RESULT_DELIMITER}
386
387
        # No output to stdout and no result (implicit None)
388
        mock_stdout = ['%(delimiter)sNone%(delimiter)s' % values]
389
        mock_stderr = ['foo stderr']
390
391
        mock_process = mock.Mock()
392
        mock_process.returncode = 0
393
        mock_popen.return_value = mock_process
394
        mock_process.stdout.closed = False
395
        mock_process.stderr.closed = False
396
        mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout)
397
        mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr)
398
399
        runner = self._get_mock_runner_obj()
400
        runner.entry_point = PASCAL_ROW_ACTION_PATH
401
        runner.pre_run()
402
        (_, output, _) = runner.run({'row_index': 4})
403
404
        self.assertEqual(output['stdout'], '')
405
        self.assertEqual(output['stderr'], mock_stderr[0])
406
        self.assertEqual(output['result'], 'None')
407
        self.assertEqual(output['exit_code'], 0)
408
409
        # Output to stdout, no result (implicit None), return_code 1 and status failed
410
        mock_stdout = ['pre result%(delimiter)sNone%(delimiter)spost result' % values]
411
        mock_stderr = ['foo stderr']
412
413
        mock_process = mock.Mock()
414
        mock_process.returncode = 1
415
        mock_popen.return_value = mock_process
416
        mock_process.stdout.closed = False
417
        mock_process.stderr.closed = False
418
        mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout)
419
        mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr)
420
421
        runner = self._get_mock_runner_obj()
422
        runner.entry_point = PASCAL_ROW_ACTION_PATH
423
        runner.pre_run()
424
        (status, output, _) = runner.run({'row_index': 4})
425
426
        self.assertEqual(output['stdout'], 'pre resultpost result')
427
        self.assertEqual(output['stderr'], mock_stderr[0])
428
        self.assertEqual(output['result'], 'None')
429
        self.assertEqual(output['exit_code'], 1)
430
        self.assertEqual(status, 'failed')
431
432
        # Output to stdout, no result (implicit None), return_code 1 and status succeeded
433
        mock_stdout = ['pre result%(delimiter)sNone%(delimiter)spost result' % values]
434
        mock_stderr = ['foo stderr']
435
436
        mock_process = mock.Mock()
437
        mock_process.returncode = 0
438
        mock_popen.return_value = mock_process
439
        mock_process.stdout.closed = False
440
        mock_process.stderr.closed = False
441
        mock_process.stdout.readline = make_mock_stream_readline(mock_process.stdout, mock_stdout)
442
        mock_process.stderr.readline = make_mock_stream_readline(mock_process.stderr, mock_stderr)
443
444
        runner = self._get_mock_runner_obj()
445
        runner.entry_point = PASCAL_ROW_ACTION_PATH
446
        runner.pre_run()
447
        (status, output, _) = runner.run({'row_index': 4})
448
449
        self.assertEqual(output['stdout'], 'pre resultpost result')
450
        self.assertEqual(output['stderr'], mock_stderr[0])
451
        self.assertEqual(output['result'], 'None')
452
        self.assertEqual(output['exit_code'], 0)
453
        self.assertEqual(status, 'succeeded')
454
455
    @mock.patch('st2common.util.green.shell.subprocess.Popen')
456
    def test_common_st2_env_vars_are_available_to_the_action(self, mock_popen):
457
        mock_process = mock.Mock()
458
        mock_process.communicate.return_value = ('', '')
459
        mock_popen.return_value = mock_process
460
461
        runner = self._get_mock_runner_obj()
462
        runner.auth_token = mock.Mock()
463
        runner.auth_token.token = 'ponies'
464
        runner.entry_point = PASCAL_ROW_ACTION_PATH
465
        runner.pre_run()
466
        (_, _, _) = runner.run({'row_index': 4})
467
468
        _, call_kwargs = mock_popen.call_args
469
        actual_env = call_kwargs['env']
470
        self.assertCommonSt2EnvVarsAvailableInEnv(env=actual_env)
471
472
    def test_action_class_instantiation_action_service_argument(self):
473
        class Action1(Action):
474
            # Constructor not overriden so no issue here
475
            pass
0 ignored issues
show
Unused Code introduced by
Unnecessary pass statement
Loading history...
476
477
            def run(self):
478
                pass
479
480
        class Action2(Action):
481
            # Constructor overriden, but takes action_service argument
482
            def __init__(self, config, action_service=None):
483
                super(Action2, self).__init__(config=config,
484
                                              action_service=action_service)
485
486
            def run(self):
487
                pass
488
489
        class Action3(Action):
490
            # Constructor overriden, but doesn't take to action service
491
            def __init__(self, config):
492
                super(Action3, self).__init__(config=config)
493
494
            def run(self):
495
                pass
496
497
        config = {'a': 1, 'b': 2}
498
        action_service = 'ActionService!'
499
500
        action1 = get_action_class_instance(action_cls=Action1, config=config,
501
                                            action_service=action_service)
502
        self.assertEqual(action1.config, config)
503
        self.assertEqual(action1.action_service, action_service)
504
505
        action2 = get_action_class_instance(action_cls=Action2, config=config,
506
                                            action_service=action_service)
507
        self.assertEqual(action2.config, config)
508
        self.assertEqual(action2.action_service, action_service)
509
510
        action3 = get_action_class_instance(action_cls=Action3, config=config,
511
                                            action_service=action_service)
512
        self.assertEqual(action3.config, config)
513
        self.assertEqual(action3.action_service, action_service)
514
515
    def test_action_with_same_module_name_as_module_in_stdlib(self):
516
        runner = self._get_mock_runner_obj()
517
        runner.entry_point = TEST_ACTION_PATH
518
        runner.pre_run()
519
        (status, output, _) = runner.run({})
520
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
521
        self.assertTrue(output is not None)
522
        self.assertEqual(output['result'], 'test action')
523
524
    def test_python_action_wrapper_script_doesnt_get_added_to_sys_path(self):
525
        # Validate that the directory where python_action_wrapper.py script is located
526
        # (st2common/runners) doesn't get added to sys.path
527
        runner = self._get_mock_runner_obj()
528
        runner.entry_point = PATHS_ACTION_PATH
529
        runner.pre_run()
530
        (status, output, _) = runner.run({})
531
532
        self.assertEqual(status, LIVEACTION_STATUS_SUCCEEDED)
533
        self.assertTrue(output is not None)
534
535
        lines = output['stdout'].split('\n')
536
        process_sys_path = lines[0]
537
        process_pythonpath = lines[1]
538
539
        assert 'sys.path' in process_sys_path
540
        assert 'PYTHONPATH' in process_pythonpath
541
542
        wrapper_script_path = 'st2common/runners'
543
544
        assertion_msg = 'Found python wrapper script path in subprocess path'
545
        self.assertTrue(wrapper_script_path not in process_sys_path, assertion_msg)
546
        self.assertTrue(wrapper_script_path not in process_pythonpath, assertion_msg)
547
548
    def test_python_action_wrapper_action_script_file_doesnt_exist_friendly_error(self):
549
        # File in a directory which is not a Python package
550
        wrapper = PythonActionWrapper(pack='dummy_pack_5', file_path='/tmp/doesnt.exist',
551
                                      user='joe')
552
553
        expected_msg = 'File "/tmp/doesnt.exist" has no action class or the file doesn\'t exist.'
554
        self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _get_action_instance was declared protected and should not be accessed from this context.

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:

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...
555
556
        # File in a directory which is a Python package
557
        wrapper = PythonActionWrapper(pack='dummy_pack_5', file_path=ACTION_1_PATH,
558
                                      user='joe')
559
560
        expected_msg = ('Failed to load action class from file ".*?list_repos_doesnt_exist.py" '
561
                       '\(action file most likely doesn\'t exist or contains invalid syntax\): '
0 ignored issues
show
Bug introduced by
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 r or R 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.

Loading history...
Bug introduced by
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 r or R 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.

Loading history...
562
                       '\[Errno 2\] No such file or directory')
0 ignored issues
show
Bug introduced by
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 r or R 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.

Loading history...
Bug introduced by
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 r or R 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.

Loading history...
563
        self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _get_action_instance was declared protected and should not be accessed from this context.

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:

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...
564
565
    def test_python_action_wrapper_action_script_file_contains_invalid_syntax_friendly_error(self):
566
        wrapper = PythonActionWrapper(pack='dummy_pack_5', file_path=ACTION_2_PATH,
567
                                      user='joe')
568
        expected_msg = ('Failed to load action class from file ".*?invalid_syntax.py" '
569
                       '\(action file most likely doesn\'t exist or contains invalid syntax\): '
0 ignored issues
show
Bug introduced by
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 r or R 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.

Loading history...
Bug introduced by
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 r or R 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.

Loading history...
570
                       'No module named invalid')
571
        self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _get_action_instance was declared protected and should not be accessed from this context.

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:

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...
572
573
    def _get_mock_runner_obj(self):
574
        runner = python_runner.get_runner()
575
        runner.execution = MOCK_EXECUTION
576
        runner.action = self._get_mock_action_obj()
577
        runner.runner_parameters = {}
578
579
        return runner
580
581
    @mock.patch('st2actions.container.base.ActionExecution.get', mock.Mock())
582
    def _get_mock_runner_obj_from_container(self, pack, user):
583
        container = RunnerContainer()
584
585
        runnertype_db = mock.Mock()
586
        runnertype_db.runner_module = 'python_runner'
587
        action_db = mock.Mock()
588
        action_db.pack = pack
589
        action_db.entry_point = 'foo.py'
590
        liveaction_db = mock.Mock()
591
        liveaction_db.id = '123'
592
        liveaction_db.context = {'user': user}
593
        runner = container._get_runner(runnertype_db=runnertype_db, action_db=action_db,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _get_runner was declared protected and should not be accessed from this context.

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:

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...
594
                                       liveaction_db=liveaction_db)
595
        runner.execution = MOCK_EXECUTION
596
        runner.action = self._get_mock_action_obj()
597
        runner.runner_parameters = {}
598
599
        return runner
600
601
    def _get_mock_action_obj(self):
602
        """
603
        Return mock action object.
604
605
        Pack gets set to the system pack so the action doesn't require a separate virtualenv.
606
        """
607
        action = mock.Mock()
608
        action.ref = 'dummy.action'
609
        action.pack = SYSTEM_PACK_NAME
610
        action.entry_point = 'foo.py'
611
        action.runner_type = {
612
            'name': 'python-script'
613
        }
614
        return action
615