GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — kale/submit-debug-info ( c0cb8c...f2d693 )
by
unknown
08:15
created

st2tests.BaseActionTestCase   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 23
Duplicated Lines 0 %
Metric Value
wmc 2
dl 0
loc 23
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
A get_action_instance() 0 9 1
A setUp() 0 6 1
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
try:
17
    import simplejson as json
18
except ImportError:
19
    import json
20
21
import os
22
import os.path
23
import sys
24
import shutil
25
import logging
26
27
import six
28
import eventlet
29
import psutil
30
from oslo_config import cfg
31
from unittest2 import TestCase
32
33
from st2common.exceptions.db import StackStormDBObjectConflictError
34
from st2common.models.db import db_setup, db_teardown, db_ensure_indexes
35
from st2common.bootstrap.base import ResourceRegistrar
36
from st2common.content.utils import get_packs_base_paths
37
import st2common.models.db.rule as rule_model
38
import st2common.models.db.rule_enforcement as rule_enforcement_model
39
import st2common.models.db.sensor as sensor_model
40
import st2common.models.db.trigger as trigger_model
41
import st2common.models.db.action as action_model
42
import st2common.models.db.keyvalue as keyvalue_model
43
import st2common.models.db.runner as runner_model
44
import st2common.models.db.execution as execution_model
45
import st2common.models.db.executionstate as executionstate_model
46
import st2common.models.db.liveaction as liveaction_model
47
import st2common.models.db.actionalias as actionalias_model
48
import st2common.models.db.policy as policy_model
49
from st2actions.runners.utils import get_action_class_instance
50
51
import st2tests.config
52
from st2tests.mocks.sensor import MockSensorWrapper
53
from st2tests.mocks.sensor import MockSensorService
54
from st2tests.mocks.action import MockActionWrapper
55
from st2tests.mocks.action import MockActionService
56
57
58
__all__ = [
59
    'EventletTestCase',
60
    'DbTestCase',
61
    'DbModelTestCase',
62
    'CleanDbTestCase',
63
    'CleanFilesTestCase',
64
    'IntegrationTestCase',
65
66
    'BaseSensorTestCase',
67
    'BaseActionTestCase'
68
]
69
70
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
71
72
ALL_MODELS = []
73
ALL_MODELS.extend(rule_model.MODELS)
74
ALL_MODELS.extend(sensor_model.MODELS)
75
ALL_MODELS.extend(trigger_model.MODELS)
76
ALL_MODELS.extend(action_model.MODELS)
77
ALL_MODELS.extend(keyvalue_model.MODELS)
78
ALL_MODELS.extend(runner_model.MODELS)
79
ALL_MODELS.extend(execution_model.MODELS)
80
ALL_MODELS.extend(executionstate_model.MODELS)
81
ALL_MODELS.extend(liveaction_model.MODELS)
82
ALL_MODELS.extend(actionalias_model.MODELS)
83
ALL_MODELS.extend(policy_model.MODELS)
84
ALL_MODELS.extend(rule_enforcement_model.MODELS)
85
86
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
87
TESTS_CONFIG_PATH = os.path.join(BASE_DIR, '../conf/st2.conf')
88
89
90
class BaseTestCase(TestCase):
91
92
    @classmethod
93
    def _register_packs(self):
0 ignored issues
show
Coding Style Best Practice introduced by
The first argument of the class method _register_packs should be named cls.
Loading history...
94
        """
95
        Register all the packs inside the fixtures directory.
96
        """
97
        registrar = ResourceRegistrar(use_pack_cache=False)
98
        registrar.register_packs(base_dirs=get_packs_base_paths())
99
100
101
class EventletTestCase(TestCase):
102
    """
103
    Base test class which performs eventlet monkey patching before the tests run
104
    and un-patching after the tests have finished running.
105
    """
106
107
    @classmethod
108
    def setUpClass(cls):
109
        eventlet.monkey_patch(
110
            os=True,
111
            select=True,
112
            socket=True,
113
            thread=False if '--use-debugger' in sys.argv else True,
114
            time=True
115
        )
116
117
    @classmethod
118
    def tearDownClass(cls):
119
        eventlet.monkey_patch(
120
            os=False,
121
            select=False,
122
            socket=False,
123
            thread=False,
124
            time=False
125
        )
126
127
128
class BaseDbTestCase(BaseTestCase):
129
130
    # Set to True to enable printing of all the log messages to the console
131
    DISPLAY_LOG_MESSAGES = False
132
133
    @classmethod
134
    def setUpClass(cls):
135
        st2tests.config.parse_args()
136
137
        if cls.DISPLAY_LOG_MESSAGES:
138
            config_path = os.path.join(BASE_DIR, '../conf/logging.conf')
139
            logging.config.fileConfig(config_path,
140
                                      disable_existing_loggers=False)
141
142
    @classmethod
143
    def _establish_connection_and_re_create_db(cls):
144
        username = cfg.CONF.database.username if hasattr(cfg.CONF.database, 'username') else None
145
        password = cfg.CONF.database.password if hasattr(cfg.CONF.database, 'password') else None
146
        cls.db_connection = db_setup(
147
            cfg.CONF.database.db_name, cfg.CONF.database.host, cfg.CONF.database.port,
148
            username=username, password=password, ensure_indexes=False)
149
        cls._drop_collections()
150
        cls.db_connection.drop_database(cfg.CONF.database.db_name)
151
152
        # Explicity ensure indexes after we re-create the DB otherwise ensure_indexes could failure
153
        # inside db_setup if test inserted invalid data
154
        db_ensure_indexes()
155
156
    @classmethod
157
    def _drop_db(cls):
158
        cls._drop_collections()
159
        if cls.db_connection is not None:
160
            cls.db_connection.drop_database(cfg.CONF.database.db_name)
161
        db_teardown()
162
        cls.db_connection = None
163
164
    @classmethod
165
    def _drop_collections(cls):
166
        # XXX: Explicitly drop all the collection. Otherwise, artifacts are left over in
167
        # subsequent tests.
168
        # See: https://github.com/MongoEngine/mongoengine/issues/566
169
        # See: https://github.com/MongoEngine/mongoengine/issues/565
170
        global ALL_MODELS
0 ignored issues
show
Unused Code introduced by
The variable ALL_MODELS was imported from global scope, but was never written to.
Loading history...
171
        for model in ALL_MODELS:
172
            model.drop_collection()
173
174
175
class DbTestCase(BaseDbTestCase):
176
    """
177
    This class drops and re-creates the database once per TestCase run.
178
179
    This means database is only dropped once before all the tests from this class run. This means
180
    data is persited between different tests in this class.
181
    """
182
183
    db_connection = None
184
    current_result = None
185
    register_packs = False
186
187
    @classmethod
188
    def setUpClass(cls):
189
        BaseDbTestCase.setUpClass()
190
        cls._establish_connection_and_re_create_db()
191
192
        if cls.register_packs:
193
            cls._register_packs()
194
195
    @classmethod
196
    def tearDownClass(cls):
197
        drop_db = True
198
199
        if cls.current_result.errors or cls.current_result.failures:
200
            # Don't drop DB on test failure
201
            drop_db = False
202
203
        if drop_db:
204
            cls._drop_db()
205
206
    def run(self, result=None):
207
        # Remember result for use in tearDown and tearDownClass
208
        self.current_result = result
209
        self.__class__.current_result = result
210
        super(DbTestCase, self).run(result=result)
211
212
213
class DbModelTestCase(DbTestCase):
214
    access_type = None
215
216
    @classmethod
217
    def setUpClass(cls):
218
        super(DbModelTestCase, cls).setUpClass()
219
        cls.db_type = cls.access_type.impl.model
220
221
    def _assert_fields_equal(self, a, b, exclude=None):
222
        exclude = exclude or []
223
        fields = {k: v for k, v in six.iteritems(self.db_type._fields) if k not in exclude}
224
225
        assert_funcs = {
226
            'mongoengine.fields.DictField': self.assertDictEqual,
227
            'mongoengine.fields.ListField': self.assertListEqual,
228
            'mongoengine.fields.SortedListField': self.assertListEqual
229
        }
230
231
        for k, v in six.iteritems(fields):
232
            assert_func = assert_funcs.get(str(v), self.assertEqual)
233
            assert_func(getattr(a, k, None), getattr(b, k, None))
234
235
    def _assert_values_equal(self, a, values=None):
236
        values = values or {}
237
238
        assert_funcs = {
239
            'dict': self.assertDictEqual,
240
            'list': self.assertListEqual
241
        }
242
243
        for k, v in six.iteritems(values):
244
            assert_func = assert_funcs.get(type(v).__name__, self.assertEqual)
245
            assert_func(getattr(a, k, None), v)
246
247
    def _assert_crud(self, instance, defaults=None, updates=None):
248
        # Assert instance is not already in the database.
249
        self.assertIsNone(getattr(instance, 'id', None))
250
251
        # Assert default values are assigned.
252
        self._assert_values_equal(instance, values=defaults)
253
254
        # Assert instance is created in the datbaase.
255
        saved = self.access_type.add_or_update(instance)
256
        self.assertIsNotNone(saved.id)
257
        self._assert_fields_equal(instance, saved, exclude=['id'])
258
        retrieved = self.access_type.get_by_id(saved.id)
259
        self._assert_fields_equal(saved, retrieved)
260
261
        # Assert instance is updated in the database.
262
        for k, v in six.iteritems(updates or {}):
263
            setattr(instance, k, v)
264
265
        updated = self.access_type.add_or_update(instance)
266
        self._assert_fields_equal(instance, updated)
267
268
        # Assert instance is deleted from the database.
269
        retrieved = self.access_type.get_by_id(instance.id)
270
        retrieved.delete()
271
        self.assertRaises(ValueError, self.access_type.get_by_id, instance.id)
272
273
    def _assert_unique_key_constraint(self, instance):
274
        # Assert instance is not already in the database.
275
        self.assertIsNone(getattr(instance, 'id', None))
276
277
        # Assert instance is created in the datbaase.
278
        saved = self.access_type.add_or_update(instance)
279
        self.assertIsNotNone(saved.id)
280
281
        # Assert exception is thrown if try to create same instance again.
282
        delattr(instance, 'id')
283
        self.assertRaises(StackStormDBObjectConflictError,
284
                          self.access_type.add_or_update,
285
                          instance)
286
287
288
class CleanDbTestCase(BaseDbTestCase):
289
    """
290
    Class which ensures database is re-created before running each test method.
291
292
    This means each test inside this class is self-sustained and starts with a clean (empty)
293
    database.
294
    """
295
296
    def setUp(self):
297
        self._establish_connection_and_re_create_db()
298
299
300
class CleanFilesTestCase(TestCase):
301
    """
302
    Base test class which deletes specified files and directories on setUp and `tearDown.
303
    """
304
    to_delete_files = []
305
    to_delete_directories = []
306
307
    def setUp(self):
308
        super(CleanFilesTestCase, self).setUp()
309
        self._delete_files()
310
311
    def tearDown(self):
312
        super(CleanFilesTestCase, self).tearDown()
313
        self._delete_files()
314
315
    def _delete_files(self):
316
        for file_path in self.to_delete_files:
317
            if not os.path.isfile(file_path):
318
                continue
319
320
            try:
321
                os.remove(file_path)
322
            except Exception:
323
                pass
324
325
        for file_path in self.to_delete_directories:
326
            if not os.path.isdir(file_path):
327
                continue
328
329
            try:
330
                shutil.rmtree(file_path)
331
            except Exception:
332
                pass
333
334
335
class IntegrationTestCase(TestCase):
336
    """
337
    Base test class for integration tests to inherit from.
338
339
    It includes various utility functions and assert methods for working with processes.
340
    """
341
342
    # Set to True to print process stdout and stderr in tearDown after killing the processes
343
    # which are still alive
344
    print_stdout_stderr_on_teardown = False
345
346
    processes = {}
347
348
    def tearDown(self):
349
        super(IntegrationTestCase, self).tearDown()
350
351
        # Make sure we kill all the processes on teardown so they don't linger around if an
352
        # exception was thrown.
353
        for pid, process in self.processes.items():
354
355
            try:
356
                process.kill()
357
            except OSError:
358
                # Process already exited or similar
359
                pass
360
361
            if self.print_stdout_stderr_on_teardown:
362
                try:
363
                    stdout = process.stdout.read()
364
                except:
365
                    stdout = None
366
367
                try:
368
                    stderr = process.stderr.read()
369
                except:
370
                    stderr = None
371
372
                print('Process "%s"' % (process.pid))
373
                print('Stdout: %s' % (stdout))
374
                print('Stderr: %s' % (stderr))
375
376
    def add_process(self, process):
377
        """
378
        Add a process to the local data structure to make sure it will get killed and cleaned up on
379
        tearDown.
380
        """
381
        self.processes[process.pid] = process
382
383
    def remove_process(self, process):
384
        """
385
        Remove process from a local data structure.
386
        """
387
        if process.pid in self.processes:
388
            del self.processes[process.pid]
389
390
    def assertProcessIsRunning(self, process):
391
        """
392
        Assert that a long running process provided Process object as returned by subprocess.Popen
393
        has succesfuly started and is running.
394
        """
395
        return_code = process.poll()
396
397
        if return_code is not None:
398
            stdout = process.stdout.read()
399
            stderr = process.stderr.read()
400
            msg = ('Process exited with code=%s.\nStdout:\n%s\n\nStderr:\n%s' %
401
                   (return_code, stdout, stderr))
402
            self.fail(msg)
403
404
    def assertProcessExited(self, proc):
405
        try:
406
            status = proc.status()
407
        except psutil.NoSuchProcess:
408
            status = 'exited'
409
410
        if status not in ['exited', 'zombie']:
411
            self.fail('Process with pid "%s" is still running' % (proc.pid))
412
413
414
class BaseSensorTestCase(TestCase):
415
    """
416
    Base class for sensor tests.
417
418
    This class provides some utility methods for verifying that a trigger has
419
    been dispatched, etc.
420
    """
421
422
    sensor_cls = None
423
424
    def setUp(self):
425
        super(BaseSensorTestCase, self).setUp()
426
427
        class_name = self.sensor_cls.__name__
428
        sensor_wrapper = MockSensorWrapper(pack='tests', class_name=class_name)
429
        self.sensor_service = MockSensorService(sensor_wrapper=sensor_wrapper)
430
431
    def get_sensor_instance(self, config=None, poll_interval=None):
432
        """
433
        Retrieve instance of the sensor class.
434
        """
435
        kwargs = {
436
            'sensor_service': self.sensor_service
437
        }
438
439
        if config:
440
            kwargs['config'] = config
441
442
        if poll_interval is not None:
443
            kwargs['poll_interval'] = poll_interval
444
445
        instance = self.sensor_cls(**kwargs)  # pylint: disable=not-callable
446
        return instance
447
448
    def get_dispatched_triggers(self):
449
        return self.sensor_service.dispatched_triggers
450
451
    def get_last_dispatched_trigger(self):
452
        return self.sensor_service.dispatched_triggers[-1]
453
454
    def assertTriggerDispatched(self, trigger, payload=None, trace_context=None):
455
        """
456
        Assert that the trigger with the provided values has been dispatched.
457
458
        :param trigger: Name of the trigger.
459
        :type trigger: ``str``
460
461
        :param paylod: Trigger payload (optional). If not provided, only trigger name is matched.
462
        type: payload: ``object``
463
464
        :param trace_context: Trigger trace context (optional). If not provided, only trigger name
465
                              is matched.
466
        type: payload: ``object``
467
        """
468
        dispatched_triggers = self.get_dispatched_triggers()
469
        for item in dispatched_triggers:
470
            trigger_matches = (item['trigger'] == trigger)
471
472
            if payload:
473
                payload_matches = (item['payload'] == payload)
474
            else:
475
                payload_matches = True
476
477
            if trace_context:
478
                trace_context_matches = (item['trace_context'] == trace_context)
479
            else:
480
                trace_context_matches = True
481
482
            if trigger_matches and payload_matches and trace_context_matches:
483
                return True
484
485
        msg = 'Trigger "%s" hasn\'t been dispatched' % (trigger)
486
        raise AssertionError(msg)
487
488
489
class BaseActionTestCase(TestCase):
490
    """
491
    Base class for action tests.
492
    """
493
494
    action_cls = None
495
496
    def setUp(self):
497
        super(BaseActionTestCase, self).setUp()
498
499
        class_name = self.action_cls.__name__
500
        action_wrapper = MockActionWrapper(pack='tests', class_name=class_name)
501
        self.action_service = MockActionService(action_wrapper=action_wrapper)
502
503
    def get_action_instance(self, config=None):
504
        """
505
        Retrieve instance of the action class.
506
        """
507
        # pylint: disable=not-callable
508
        instance = get_action_class_instance(action_cls=self.action_cls,
509
                                             config=config,
510
                                             action_service=self.action_service)
511
        return instance
512
513
514
class FakeResponse(object):
515
516
    def __init__(self, text, status_code, reason):
517
        self.text = text
518
        self.status_code = status_code
519
        self.reason = reason
520
521
    def json(self):
522
        return json.loads(self.text)
523
524
    def raise_for_status(self):
525
        raise Exception(self.reason)
526
527
528
def get_fixtures_path():
529
    return os.path.join(os.path.dirname(__file__), 'fixtures')
530
531
532
def get_resources_path():
533
    return os.path.join(os.path.dirname(__file__), 'resources')
534