tests and refactor of nose driver
This commit is contained in:
@@ -14,30 +14,27 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo.config import cfg
|
||||
cfg.CONF(project='testing_adapter')
|
||||
|
||||
from core.common import logger
|
||||
|
||||
|
||||
cfg.CONF(project='testing_adapter')
|
||||
logger.setup()
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
from gevent import wsgi
|
||||
from core.wsgi import app
|
||||
from core.wsgi import config
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Parse OpenStack config file and command line options, then
|
||||
# configure logging.
|
||||
# Build the WSGI app
|
||||
|
||||
|
||||
|
||||
root = app.setup_app()
|
||||
# Create the WSGI server and start it
|
||||
host, port = config.server.values()
|
||||
srv = wsgi.WSGIServer((host, int(port)), root)
|
||||
log.info('Starting server in PID %s' % os.getpid())
|
||||
|
||||
20
core/api.py
20
core/api.py
@@ -1,16 +1,20 @@
|
||||
import os
|
||||
from core.storage import get_storage
|
||||
from core.transport import get_transport
|
||||
import logging
|
||||
import yaml
|
||||
from stevedore import extension
|
||||
import io
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PLUGINS_NAMESPACE = 'plugins'
|
||||
|
||||
|
||||
def parse_commands_file():
|
||||
current_directory = os.path.dirname(os.path.realpath(__file__))
|
||||
commands_path = os.path.join(current_directory, 'commands.yaml')
|
||||
with open(commands_path, 'r') as f:
|
||||
with io.open(commands_path, 'r') as f:
|
||||
return yaml.load(f)
|
||||
|
||||
|
||||
@@ -21,13 +25,15 @@ class API(object):
|
||||
self._commands = parse_commands_file()
|
||||
log.info('Parsed commands %s' % self._commands)
|
||||
self._storage = get_storage()
|
||||
self._transport_manager = extension.ExtensionManager(
|
||||
PLUGINS_NAMESPACE, invoke_on_load=True)
|
||||
|
||||
def run(self, test_run_name, conf):
|
||||
log.info('Looking for %s in %s' % (test_run_name, self._commands))
|
||||
command = self._commands.get(test_run_name, {})
|
||||
transport = get_transport(command['driver'])
|
||||
transport = self._transport_manager[command['driver']]
|
||||
test_run = self._storage.add_test_run(test_run_name)
|
||||
transport.run(test_run['id'], conf, **command)
|
||||
transport.obj.run(test_run['id'], conf, **command)
|
||||
return test_run
|
||||
|
||||
def get_info(self, test_run_name, test_run_id):
|
||||
@@ -35,6 +41,6 @@ class API(object):
|
||||
|
||||
def kill(self, test_run_name, test_run_id):
|
||||
log.info('Looking for %s in %s' % (test_run_name, self._commands))
|
||||
commands_keys = self._commands.get(test_run_name, {})
|
||||
transport = get_transport(commands_keys['driver'])
|
||||
return transport.kill(test_run_id)
|
||||
command = self._commands.get(test_run_name, {})
|
||||
transport = self._transport_manager[command['driver']]
|
||||
return transport.obj.kill(test_run_id)
|
||||
|
||||
@@ -11,4 +11,10 @@ five_min:
|
||||
driver: nose
|
||||
tests:
|
||||
test_path: /home/dshulyak/projects/ostf-tests
|
||||
driver: nose
|
||||
cliff:
|
||||
test_path: /home/dshulyak/projects/cliff
|
||||
driver: nose
|
||||
wait:
|
||||
test_path: /home/dshulyak/test_demo
|
||||
driver: nose
|
||||
@@ -1,16 +0,0 @@
|
||||
from core.transport import nose_adapter
|
||||
from stevedore import driver
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PLUGINS_NAMESPACE = 'plugins'
|
||||
|
||||
|
||||
def get_transport(driver_name):
|
||||
log.info('GET TRANSPORT FOR - %s' % driver_name)
|
||||
# TODO remove nose hardcode from driver manager
|
||||
mgr = driver.DriverManager(PLUGINS_NAMESPACE,
|
||||
driver_name,
|
||||
invoke_on_load=True)
|
||||
return mgr.driver
|
||||
@@ -7,11 +7,9 @@ from time import time
|
||||
import sys
|
||||
from StringIO import StringIO
|
||||
import logging
|
||||
from oslo.config import cfg
|
||||
import io
|
||||
|
||||
|
||||
|
||||
TESTS_PROCESS = {}
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -20,7 +18,7 @@ log = logging.getLogger(__name__)
|
||||
class StoragePlugin(plugins.Plugin):
|
||||
|
||||
enabled = True
|
||||
name = 'redis'
|
||||
name = 'storage'
|
||||
score = 15000
|
||||
|
||||
def __init__(self, test_run_id):
|
||||
@@ -83,13 +81,13 @@ class StoragePlugin(plugins.Plugin):
|
||||
|
||||
def beforeTest(self, test):
|
||||
self._start_time = time()
|
||||
self._start_capture()
|
||||
# self._start_capture()
|
||||
pass
|
||||
|
||||
def afterTest(self, test):
|
||||
self._end_capture()
|
||||
self._current_stdout = None
|
||||
self._current_stderr = None
|
||||
# def afterTest(self, test):
|
||||
# self._end_capture()
|
||||
# self._current_stdout = None
|
||||
# self._current_stderr = None
|
||||
|
||||
def startContext(self, context):
|
||||
self._start_capture()
|
||||
@@ -115,51 +113,56 @@ class StoragePlugin(plugins.Plugin):
|
||||
return time() - self._start_time
|
||||
|
||||
|
||||
g_pool = pool.Pool(10)
|
||||
|
||||
|
||||
|
||||
class NoseDriver(object):
|
||||
|
||||
def run(self, test_run, conf, **kwargs):
|
||||
def __init__(self):
|
||||
log.info('NoseDriver initialized')
|
||||
self._pool = pool.Pool(10)
|
||||
self._named_threads = {}
|
||||
|
||||
def run(self, test_run_id, conf, **kwargs):
|
||||
if 'config_path' in kwargs:
|
||||
self.prepare_config(conf, kwargs['config_path'])
|
||||
argv_add = []
|
||||
if 'argv' in kwargs:
|
||||
argv_add = [kwargs['argv']]
|
||||
log.info('Additional args: %s' % argv_add)
|
||||
gev = g_pool.spawn(self._run_tests, test_run, kwargs['test_path'],
|
||||
argv_add)
|
||||
TESTS_PROCESS[test_run] = gev
|
||||
gev = self._pool.spawn(
|
||||
self._run_tests, test_run_id, kwargs['test_path'], argv_add)
|
||||
self._named_threads[test_run_id] = gev
|
||||
|
||||
def _run_tests(self, test_run, test_path, argv_add):
|
||||
def _run_tests(self, test_run_id, test_path, argv_add):
|
||||
try:
|
||||
log.info('Nose Driver spawn green thread for TEST RUN: %s\n'
|
||||
'TEST PATH: %s\n'
|
||||
'ARGS: %s' % (test_run, test_path, argv_add))
|
||||
'ARGS: %s' % (test_run_id, test_path, argv_add))
|
||||
main(defaultTest=test_path,
|
||||
addplugins=[StoragePlugin(test_run)],
|
||||
addplugins=[StoragePlugin(test_run_id)],
|
||||
exit=True,
|
||||
argv=['tests']+argv_add)
|
||||
finally:
|
||||
log.info('Close green thread TEST_RUN: %s' % test_run)
|
||||
del TESTS_PROCESS[test_run]
|
||||
#To close thread we need to catch any exception
|
||||
except BaseException, e:
|
||||
log.info('Close green thread TEST_RUN: %s\n'
|
||||
'Thread closed with exception: %s' % (test_run_id,
|
||||
e.message))
|
||||
del self._named_threads[test_run_id]
|
||||
raise gevent.GreenletExit
|
||||
|
||||
def kill(self, test_run):
|
||||
log.info('Trying to stop process %s\n'
|
||||
'%s' % (test_run, TESTS_PROCESS))
|
||||
if int(test_run) in TESTS_PROCESS:
|
||||
'%s' % (test_run, self._named_threads))
|
||||
if int(test_run) in self._named_threads:
|
||||
log.info('Kill green thread: %s' % test_run)
|
||||
TESTS_PROCESS[int(test_run)].kill()
|
||||
self._named_threads[int(test_run)].kill()
|
||||
return True
|
||||
return False
|
||||
|
||||
def prepare_config(self, conf, testing_config_path):
|
||||
conf_path = os.path.abspath(testing_config_path)
|
||||
with io.open(conf_path, 'w', encoding='utf-8') as f:
|
||||
for key, value in conf.iteritems():
|
||||
f.write(u'%s = %s\n' % (key, value))
|
||||
|
||||
conf_path = os.path.abspath(testing_config_path)
|
||||
with io.open(conf_path, 'w', encoding='utf-8') as f:
|
||||
for key, value in conf.iteritems():
|
||||
f.write(u'%s = %s\n' % (key, value))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import pecan
|
||||
from core.wsgi import config
|
||||
from core.wsgi import hooks
|
||||
from oslo.config import cfg
|
||||
|
||||
|
||||
def get_pecan_config():
|
||||
|
||||
@@ -8,16 +8,3 @@ class APIHook(pecan.hooks.PecanHook):
|
||||
|
||||
def before(self, state):
|
||||
state.request.api = self.rest_api
|
||||
|
||||
|
||||
# class StorageHook(hooks.PecanHook):
|
||||
#
|
||||
# def before(self, state):
|
||||
# state.request.storage = get_storage(conf)
|
||||
#
|
||||
#
|
||||
# class TransportHook(hooks.PecanHook):
|
||||
#
|
||||
# def before(self, state):
|
||||
# state.request.transport = get_transport(conf)
|
||||
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
import unittest
|
||||
from mock import patch, MagicMock
|
||||
from core.api import API, parse_commands_file
|
||||
|
||||
import io
|
||||
|
||||
TEST_RUN_NAME = 'tests'
|
||||
TEST_RUN_ID = 1
|
||||
TEST_ID = 'simple.TestSimple'
|
||||
CONF = {'config': True}
|
||||
TEST_COMMANDS = {'tests': {
|
||||
'driver': 'nose',
|
||||
'test_path': '/home/tests'
|
||||
}}
|
||||
|
||||
|
||||
class TestApi(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.transport = MagicMock()
|
||||
self.storage = MagicMock()
|
||||
|
||||
@patch('core.api.get_storage')
|
||||
def test_init_api(self, get_storage_mock):
|
||||
get_storage_mock.return_value = 'TEST STORAGE'
|
||||
api = API()
|
||||
self.assertEqual(api._storage, 'TEST STORAGE')
|
||||
|
||||
def setUp(self):
|
||||
self.transport = MagicMock()
|
||||
self.storage = MagicMock()
|
||||
|
||||
@patch('core.api.get_storage')
|
||||
@patch('core.api.get_transport')
|
||||
def test_run(self, get_transport_mock, get_storage_mock):
|
||||
@patch('core.api.parse_commands_file')
|
||||
def test_run(self, commands_mock, get_storage_mock):
|
||||
commands_mock.return_value = TEST_COMMANDS
|
||||
get_storage_mock.return_value = self.storage
|
||||
get_transport_mock.return_value = self.transport
|
||||
self.storage.add_test_run.return_value = {'type': TEST_RUN_NAME, 'id':TEST_RUN_ID}
|
||||
self.storage.add_test_run.return_value = {'type': TEST_RUN_NAME,
|
||||
'id': TEST_RUN_ID}
|
||||
api = API()
|
||||
res = api.run(TEST_RUN_NAME, CONF)
|
||||
with patch.object(api, '_transport_manager') as transport_manager_mock:
|
||||
transport_manager_mock.__getitem__.side_effect = \
|
||||
lambda test_run: self.transport
|
||||
res = api.run(TEST_RUN_NAME, CONF)
|
||||
self.transport.obj.run.assert_called_once_with(
|
||||
TEST_RUN_ID, CONF, driver='nose', test_path='/home/tests')
|
||||
self.storage.add_test_run.assert_called_once_with(TEST_RUN_NAME)
|
||||
self.transport.run.assert_called_once_with(TEST_RUN_ID, CONF)
|
||||
self.assertEqual(res, {'type': TEST_RUN_NAME, 'id': TEST_RUN_ID})
|
||||
|
||||
@patch('core.api.get_storage')
|
||||
@@ -40,15 +49,41 @@ class TestApi(unittest.TestCase):
|
||||
res = api.get_info(TEST_RUN_NAME, TEST_RUN_ID)
|
||||
self.storage.get_test_results.assert_called_once_with(TEST_RUN_ID)
|
||||
|
||||
def test_parse_commands_file(self):
|
||||
@patch('core.api.io.open')
|
||||
def test_parse_commands_file(self, file_mock):
|
||||
yaml = u"""
|
||||
fuel_smoke:
|
||||
test_path: /root/ostf-tests/fuel/tests
|
||||
driver: nose
|
||||
argv: smoke
|
||||
fuel_sanity:
|
||||
test_path: /root/ostf-tests/fuel/tests
|
||||
driver: nose
|
||||
argv: sanity
|
||||
"""
|
||||
file_mock.return_value = io.StringIO(yaml)
|
||||
res = parse_commands_file()
|
||||
expected = {'fuel_smoke': {
|
||||
'argv': '-A "type == ["fuel", "smoke"]"',
|
||||
'driver': 'nose',
|
||||
'test_path': '/root/ostf/ostf-tests'},
|
||||
'tests': {'driver': 'nose',
|
||||
'test_path': '/home/dshulyak/projects/ostf-tests'}
|
||||
expected = {
|
||||
'fuel_smoke': {
|
||||
'test_path': '/root/ostf-tests/fuel/tests',
|
||||
'driver': 'nose',
|
||||
'argv': 'smoke'},
|
||||
'fuel_sanity': {
|
||||
'test_path': '/root/ostf-tests/fuel/tests',
|
||||
'driver': 'nose',
|
||||
'argv': 'sanity'}
|
||||
}
|
||||
self.assertEqual(res, expected)
|
||||
|
||||
@patch('core.api.parse_commands_file')
|
||||
def test_kill_test_run(self, commands_mock):
|
||||
commands_mock.return_value = TEST_COMMANDS
|
||||
self.transport.obj.kill.return_value = True
|
||||
api = API()
|
||||
with patch.object(api, '_transport_manager') as transport_manager_mock:
|
||||
transport_manager_mock.__getitem__.side_effect = \
|
||||
lambda test_run: self.transport
|
||||
res = api.kill(TEST_RUN_NAME, TEST_RUN_ID)
|
||||
self.transport.obj.kill.assert_called_once_with(TEST_RUN_ID)
|
||||
self.assertTrue(res)
|
||||
|
||||
|
||||
@@ -1,35 +1,100 @@
|
||||
import unittest
|
||||
from core.transport import nose_adapter, get_transport
|
||||
from oslo.config import cfg
|
||||
from core.transport import nose_adapter
|
||||
import io
|
||||
from mock import patch
|
||||
from mock import patch, MagicMock
|
||||
import gevent
|
||||
|
||||
|
||||
TEST_RUN_ID = 1
|
||||
CONF = {'keys': 'values'}
|
||||
|
||||
|
||||
def raise_system_exit(*args, **kwargs):
|
||||
raise SystemExit
|
||||
|
||||
def raise_greenlet_exit(*args, **kwargs):
|
||||
raise gevent.GreenletExit
|
||||
|
||||
class DummyStringIO(io.StringIO):
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
|
||||
@patch('core.transport.nose_adapter.pool')
|
||||
class TestNoseAdapters(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.nose_driver = nose_adapter.NoseDriver()
|
||||
self.pool_mock = MagicMock()
|
||||
self.thread_mock = MagicMock()
|
||||
|
||||
def test_get_nose_transport(self):
|
||||
driver = get_transport('nose')
|
||||
self.assertIsInstance(driver, nose_adapter.NoseDriver)
|
||||
|
||||
@patch('core.transport.nose_adapter.io.open')
|
||||
def test_create_tempest_conf(self, io_mock):
|
||||
"""Test verified
|
||||
"""
|
||||
def test_prepare_config_conf(self, io_mock, pool_module):
|
||||
pool_module.Pool.return_value = self.pool_mock
|
||||
nose_driver = nose_adapter.NoseDriver()
|
||||
string_io = DummyStringIO()
|
||||
io_mock.return_value = string_io
|
||||
conf = {'param1': 'test',
|
||||
'param2': 'test'}
|
||||
conf_path = '/etc/config.conf'
|
||||
res = self.nose_driver.prepare_config(conf, conf_path)
|
||||
res = nose_driver.prepare_config(conf, conf_path)
|
||||
self.assertEqual(string_io.getvalue(),
|
||||
u'param2 = test\nparam1 = test\n')
|
||||
|
||||
def test_run_with_config_path_with_argv(self, pool_module):
|
||||
pool_module.Pool.return_value = self.pool_mock
|
||||
nose_driver = nose_adapter.NoseDriver()
|
||||
with patch.object(nose_driver, 'prepare_config')\
|
||||
as prepare_config_mock:
|
||||
res = nose_driver.run(
|
||||
TEST_RUN_ID, CONF, config_path='/etc/test.conf', argv='sanity',
|
||||
test_path='/home/tests')
|
||||
prepare_config_mock.assert_called_once_with(CONF, '/etc/test.conf')
|
||||
self.pool_mock.spawn.assert_called_once_with(
|
||||
nose_driver._run_tests, TEST_RUN_ID, '/home/tests', ['sanity']
|
||||
)
|
||||
self.assertIn(1, nose_driver._named_threads)
|
||||
|
||||
def test_kill_test_run_success(self, pool_module):
|
||||
pool_module.Pool.return_value = self.pool_mock
|
||||
nose_driver = nose_adapter.NoseDriver()
|
||||
nose_driver._named_threads[TEST_RUN_ID] = self.thread_mock
|
||||
|
||||
res = nose_driver.kill(TEST_RUN_ID)
|
||||
self.thread_mock.kill.assert_called_once_with()
|
||||
self.assertTrue(res)
|
||||
|
||||
def test_kill_test_run_fail(self, pool_module):
|
||||
pool_module.Pool.return_value = self.pool_mock
|
||||
nose_driver = nose_adapter.NoseDriver()
|
||||
|
||||
res = nose_driver.kill(2)
|
||||
self.assertFalse(res)
|
||||
|
||||
@patch('core.transport.nose_adapter.main')
|
||||
def test_run_tests_raise_system_exit(
|
||||
self, nose_test_program_mock, pool_module):
|
||||
pool_module.Pool.return_value = self.pool_mock
|
||||
nose_driver = nose_adapter.NoseDriver()
|
||||
nose_driver._named_threads[TEST_RUN_ID] = 'VALUE'
|
||||
nose_test_program_mock.side_effect = raise_system_exit
|
||||
|
||||
self.assertRaises(gevent.GreenletExit, nose_driver._run_tests,
|
||||
TEST_RUN_ID, '/home/tests', ['sanity'])
|
||||
|
||||
@patch('core.transport.nose_adapter.main')
|
||||
def test_run_tests_raise_greelet_exit(
|
||||
self, nose_test_program_mock, pool_module):
|
||||
pool_module.Pool.return_value = self.pool_mock
|
||||
nose_driver = nose_adapter.NoseDriver()
|
||||
nose_driver._named_threads[TEST_RUN_ID] = 'VALUE'
|
||||
nose_test_program_mock.side_effect = raise_greenlet_exit
|
||||
|
||||
self.assertRaises(gevent.GreenletExit, nose_driver._run_tests,
|
||||
TEST_RUN_ID, '/home/tests', ['sanity'])
|
||||
|
||||
|
||||
class TestNoseStoragePlugin(unittest.TestCase):
|
||||
|
||||
|
||||
def test
|
||||
Reference in New Issue
Block a user