tests and refactor of nose driver

This commit is contained in:
Dmitry Shulyak
2013-06-26 13:20:42 +03:00
parent f6b5952f86
commit 0ff400a05c
9 changed files with 188 additions and 106 deletions

View File

@@ -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())

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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():

View File

@@ -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)

View File

@@ -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)

View File

@@ -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