Improve database creation for testing

Create MySQL database before starting tests and cleanup after that.
MySQL database is able to work in standalone mode where establish
connection using a Unix domain socket.
This patch makes it possible to perform functional tests with `tox -e py27-func-mysql`.
If an enviroment variable REFSTACK_TEST_MYSQL_URL is set then tests will run with it
and a database will not be created.
Update test requirements.

Change-Id: I0cdba19701bbf06109e78f78f275038caeecd881
This commit is contained in:
Vladislav Kuzmin 2015-02-17 15:01:00 +03:00
parent 8ea0a1ace9
commit 16affe2d12
8 changed files with 130 additions and 83 deletions

View File

@ -1,4 +1,4 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./refstack -s ./refstack/tests/unit $LISTOPT $IDOPTION
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./refstack -s ./refstack/tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -15,7 +15,7 @@ import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
# commands auto generated by Alembic - please adjust!
op.create_table(
'test',
sa.Column('updated_at', sa.DateTime()),
@ -56,12 +56,12 @@ def upgrade():
sa.UniqueConstraint('test_id', 'name'),
sa.UniqueConstraint('test_id', 'uid')
)
### end Alembic commands ###
# end Alembic commands
def downgrade():
### commands auto generated by Alembic - please adjust! ###
# commands auto generated by Alembic - please adjust!
op.drop_table('results')
op.drop_table('meta')
op.drop_table('test')
### end Alembic commands ###
# end Alembic commands

View File

@ -29,9 +29,7 @@ CONF = cfg.CONF
_FACADE = None
_DEFAULT_SQL_CONNECTION = 'sqlite://'
db_options.set_defaults(cfg.CONF,
connection=_DEFAULT_SQL_CONNECTION)
db_options.set_defaults(cfg.CONF)
def _create_facade_lazily():

View File

@ -14,26 +14,28 @@
# under the License.
"""Base classes for API tests."""
import inspect
import os
import alembic
import alembic.config
from oslo_config import cfg
import sqlalchemy as sa
import sqlalchemy.exc
from unittest import TestCase
from webtest import TestApp
from oslo_config import fixture as config_fixture
from oslotest import base
import pecan.testing
from sqlalchemy.engine import reflection
from sqlalchemy import create_engine
from sqlalchemy.schema import (
MetaData,
Table,
DropTable,
ForeignKeyConstraint,
DropConstraint,
)
from testtools import testcase
import refstack
from refstack.api import app
CONF = cfg.CONF
from refstack.db import migration
class FunctionalTest(TestCase):
class FunctionalTest(base.BaseTestCase):
"""Functional test case.
"""Base class for functional test case.
Used for functional tests where you need to test your.
literal application and its integration with the framework.
@ -41,60 +43,75 @@ class FunctionalTest(TestCase):
def setUp(self):
"""Test setup."""
class TestConfig(object):
app = {
super(FunctionalTest, self).setUp()
# Skip integration/functional tests
# if database has not been created
self.connection = os.environ.get("REFSTACK_TEST_MYSQL_URL")
if self.connection is None:
raise testcase.TestSkipped("Database connection url was not found")
self.config = {
'app': {
'root': 'refstack.api.controllers.root.RootController',
'modules': ['refstack.api'],
'static_root': '%(confdir)s/public',
'template_path': '%(confdir)s/${package}/templates',
}
}
self.config_fixture = config_fixture.Config()
self.CONF = self.useFixture(self.config_fixture).conf
self.CONF.set_override('connection',
self.connection,
'database')
test_config = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'refstack.test.conf'
)
os.environ['REFSTACK_OSLO_CONFIG'] = test_config
self.project_path = os.path.abspath(
os.path.join(inspect.getabsfile(refstack), '..', '..'))
self.app = TestApp(app.setup_app(TestConfig()))
self.prepare_test_db()
self.migrate_test_db()
self.app = pecan.testing.load_test_app(self.config)
self.drop_all_tables_and_constraints()
migration.upgrade('head')
def tearDown(self):
"""Test teardown."""
super(FunctionalTest, self).tearDown()
pecan.set_config({}, overwrite=True)
self.app.reset()
def prepare_test_db(self):
"""Create/clear test database."""
db_url = CONF.database.connection
db_name = db_url.split('/')[-1]
short_db_url = '/'.join(db_url.split('/')[0:-1])
try:
engine = sa.create_engine(db_url)
conn = engine.connect()
conn.execute('commit')
conn.execute('drop database %s' % db_name)
conn.close()
except sqlalchemy.exc.OperationalError:
pass
finally:
engine = sa.create_engine('/'.join((short_db_url, 'mysql')))
conn = engine.connect()
conn.execute('commit')
conn.execute('create database %s' % db_name)
conn.close()
def drop_all_tables_and_constraints(self):
"""Drop tables and cyclical constraints between tables"""
engine = create_engine(self.connection)
conn = engine.connect()
trans = conn.begin()
def migrate_test_db(self):
"""Apply migrations to test database."""
alembic_cfg = alembic.config.Config()
alembic_cfg.set_main_option(
"script_location",
os.path.join(self.project_path, 'refstack', 'db',
'migrations', 'alembic')
)
alembic_cfg.set_main_option("sqlalchemy.url",
CONF.database.connection)
alembic.command.upgrade(alembic_cfg, 'head')
inspector = reflection.Inspector.from_engine(engine)
metadata = MetaData()
tbs = []
all_fks = []
try:
for table_name in inspector.get_table_names():
fks = []
for fk in inspector.get_foreign_keys(table_name):
if not fk['name']:
continue
fks.append(
ForeignKeyConstraint((), (), name=fk['name']))
t = Table(table_name, metadata, *fks)
tbs.append(t)
all_fks.extend(fks)
for fkc in all_fks:
conn.execute(DropConstraint(fkc))
for table in tbs:
conn.execute(DropTable(table))
trans.commit()
trans.close()
conn.close()
except:
trans.rollback()
conn.close()
raise
def get_json(self, url, headers=None, extra_environ=None,
status=None, expect_errors=False, **params):

View File

@ -1,2 +0,0 @@
[DEFAULT]
sql_connection = mysql://root:passw0rd@127.0.0.1/refstack

37
setup-mysql-tests.sh Executable file
View File

@ -0,0 +1,37 @@
#!/bin/bash -x
wait_for_line () {
while read line
do
echo "$line" | grep -q "$1" && break
done < "$2"
# Read the fifo for ever otherwise process would block
cat "$2" >/dev/null &
}
# If test DB url is provided, run tests with it
if [[ "$REFSTACK_TEST_MYSQL_URL" ]]
then
$*
exit $?
fi
# Else setup mysql base for tests.
# Start MySQL process for tests
MYSQL_DATA=`mktemp -d /tmp/refstack-mysql-XXXXX`
mkfifo ${MYSQL_DATA}/out
# On systems like Fedora here's where mysqld can be found
PATH=$PATH:/usr/libexec
mysqld --no-defaults --datadir=${MYSQL_DATA} --pid-file=${MYSQL_DATA}/mysql.pid --socket=${MYSQL_DATA}/mysql.socket --skip-networking --skip-grant-tables &> ${MYSQL_DATA}/out &
# Wait for MySQL to start listening to connections
wait_for_line "mysqld: ready for connections." ${MYSQL_DATA}/out
export REFSTACK_TEST_MYSQL_URL="mysql://root@localhost/test?unix_socket=${MYSQL_DATA}/mysql.socket&charset=utf8"
mysql --no-defaults -S ${MYSQL_DATA}/mysql.socket -e 'CREATE DATABASE test;'
# Yield execution to venv command
$*
# Cleanup after tests
ret=$?
kill $(jobs -p)
rm -rf "${MYSQL_DATA}"
exit $ret

View File

@ -1,7 +1,8 @@
pep8==1.4.5
pyflakes>=0.7.2,<0.7.4
flake8==2.0
pep8==1.5.7
pyflakes==0.8.1
flake8==2.2.4
oslotest>=1.2.0 # Apache-2.0
python-subunit>=0.0.18
testrepository>=0.0.18
testtools>=0.9.34
mysqlclient
mysqlclient

18
tox.ini
View File

@ -18,17 +18,13 @@ deps = -r{toxinidir}/requirements.txt
commands = python setup.py testr --testr-args='{posargs}'
distribute = false
[testenv:func]
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python -m unittest discover ./refstack/tests/api
distribute = false
[testenv:py27-func-mysql]
basepython = python2.7
# Integration/functional tests
# must not be run in parallel (--concurrency=1),
# because each of these tests
# require cleanup of database
commands = {toxinidir}/setup-mysql-tests.sh python setup.py testr --slowest --testr-args='{posargs:--concurrency=1}'
[testenv:pep8]
commands =