Merge pull request #2576 from coreos-inc/full-db-tests-tox

Reenable full database testing locally and in concourse
This commit is contained in:
josephschorr 2017-04-27 18:09:15 -04:00 committed by GitHub
commit 8b148bf1d4
51 changed files with 278 additions and 318 deletions

View file

@ -1,25 +1,151 @@
import os
from cachetools import lru_cache
import pytest
import shutil
from flask import Flask, jsonify
from flask_login import LoginManager
from peewee import SqliteDatabase
from peewee import SqliteDatabase, savepoint, InternalError
from app import app as application
from data import model
from data.database import (close_db_filter, db)
from data.database import close_db_filter, db, configure
from data.model.user import LoginWrappedDBUser
from endpoints.api import api_bp
from endpoints.appr import appr_bp
from endpoints.web import web
from initdb import initialize_database, populate_database
from path_converters import APIRepositoryPathConverter, RegexConverter, RepositoryPathConverter
from test.testconfig import FakeTransaction
@pytest.fixture(scope="session")
@lru_cache(maxsize=1) # Important! pytest is calling this multiple times (despite it being session)
def init_db_path(tmpdir_factory):
""" Creates a new database and appropriate configuration. Note that the initial database
is created *once* per session. In the non-full-db-test case, the database_uri fixture
makes a copy of the SQLite database file on disk and passes a new copy to each test.
"""
if os.environ.get('TEST_DATABASE_URI'):
return _init_db_path_real_db(os.environ.get('TEST_DATABASE_URI'))
return _init_db_path_sqlite(tmpdir_factory)
def _init_db_path_real_db(db_uri):
""" Initializes a real database for testing by populating it from scratch. Note that this does
*not* add the tables (merely data). Callers must have migrated the database before calling
the test suite.
"""
configure({
"DB_URI": db_uri,
"DB_CONNECTION_ARGS": {
'threadlocals': True,
'autorollback': True,
},
"DB_TRANSACTION_FACTORY": _create_transaction,
})
populate_database()
return db_uri
def _init_db_path_sqlite(tmpdir_factory):
""" Initializes a SQLite database for testing by populating it from scratch and placing it into
a temp directory file.
"""
sqlitedbfile = str(tmpdir_factory.mktemp("data").join("test.db"))
sqlitedb = 'sqlite:///{0}'.format(sqlitedbfile)
conf = {"TESTING": True,
"DEBUG": True,
"DB_URI": sqlitedb}
os.environ['DB_URI'] = str(sqlitedb)
db.initialize(SqliteDatabase(sqlitedbfile))
application.config.update(conf)
application.config.update({"DB_URI": sqlitedb})
initialize_database()
db.obj.execute_sql('PRAGMA foreign_keys = ON;')
populate_database()
close_db_filter(None)
return str(sqlitedbfile)
@pytest.fixture()
def app(appconfig):
""" Used by pytest-flask plugin to inject app by test for client See test_security by name injection of client. """
def database_uri(monkeypatch, init_db_path, sqlitedb_file):
""" Returns the database URI to use for testing. In the SQLite case, a new, distinct copy of
the SQLite database is created by copying the initialized database file (sqlitedb_file)
on a per-test basis. In the non-SQLite case, a reference to the existing database URI is
returned.
"""
if os.environ.get('TEST_DATABASE_URI'):
db_uri = os.environ['TEST_DATABASE_URI']
monkeypatch.setenv("DB_URI", db_uri)
return db_uri
# Copy the golden database file to a new path.
shutil.copy2(init_db_path, sqlitedb_file)
# Monkeypatch the DB_URI.
db_path = 'sqlite:///{0}'.format(sqlitedb_file)
monkeypatch.setenv("DB_URI", db_path)
return db_path
@pytest.fixture()
def sqlitedb_file(tmpdir):
""" Returns the path at which the initialized, golden SQLite database file will be placed. """
test_db_file = tmpdir.mkdir("quaydb").join("test.db")
return str(test_db_file)
def _create_transaction(db):
return FakeTransaction()
@pytest.fixture()
def appconfig(database_uri):
""" Returns application configuration for testing that references the proper database URI. """
conf = {
"TESTING": True,
"DEBUG": True,
"DB_URI": database_uri,
"SECRET_KEY": 'superdupersecret!!!1',
"DB_CONNECTION_ARGS": {
'threadlocals': True,
'autorollback': True,
},
"DB_TRANSACTION_FACTORY": _create_transaction,
}
return conf
@pytest.fixture()
def initialized_db(appconfig):
""" Configures the database for the database found in the appconfig. """
# Configure the database.
configure(appconfig)
# If under a test *real* database, setup a savepoint.
under_test_real_database = bool(os.environ.get('TEST_DATABASE_URI'))
if under_test_real_database:
test_savepoint = savepoint(db)
test_savepoint.__enter__()
yield # Run the test.
try:
test_savepoint.__exit__(None, None, None)
except InternalError:
# If postgres fails with an exception (like IntegrityError) mid-transaction, it terminates
# it immediately, so when we go to remove the savepoint, it complains. We can safely ignore
# this case.
pass
else:
yield
@pytest.fixture()
def app(appconfig, initialized_db):
""" Used by pytest-flask plugin to inject a custom app instance for testing. """
app = Flask(__name__)
login_manager = LoginManager(app)
@ -36,58 +162,10 @@ def app(appconfig):
app.url_map.converters['regex'] = RegexConverter
app.url_map.converters['apirepopath'] = APIRepositoryPathConverter
app.url_map.converters['repopath'] = RepositoryPathConverter
app.register_blueprint(api_bp, url_prefix='/api')
app.register_blueprint(appr_bp, url_prefix='/cnr')
app.register_blueprint(web, url_prefix='/')
app.config.update(appconfig)
return app
@pytest.fixture(scope="session")
def init_db_path(tmpdir_factory):
""" Creates a new db and appropriate configuration. Used for parameter by name injection. """
sqlitedb_file = str(tmpdir_factory.mktemp("data").join("test.db"))
sqlitedb = 'sqlite:///{0}'.format(sqlitedb_file)
conf = {"TESTING": True,
"DEBUG": True,
"DB_URI": sqlitedb}
os.environ['TEST_DATABASE_URI'] = str(sqlitedb)
os.environ['DB_URI'] = str(sqlitedb)
db.initialize(SqliteDatabase(sqlitedb_file))
application.config.update(conf)
application.config.update({"DB_URI": sqlitedb})
initialize_database()
db.obj.execute_sql('PRAGMA foreign_keys = ON;')
populate_database()
close_db_filter(None)
return str(sqlitedb_file)
@pytest.fixture()
def database_uri(monkeypatch, init_db_path, sqlitedb_file):
""" Creates the db uri. Used for parameter by name injection. """
shutil.copy2(init_db_path, sqlitedb_file)
db.initialize(SqliteDatabase(sqlitedb_file))
db_path = 'sqlite:///{0}'.format(sqlitedb_file)
monkeypatch.setenv("DB_URI", db_path)
return db_path
@pytest.fixture()
def sqlitedb_file(tmpdir):
""" Makes file for db. Used for parameter by name injection. """
test_db_file = tmpdir.mkdir("quaydb").join("test.db")
return str(test_db_file)
@pytest.fixture()
def appconfig(database_uri):
""" Makes conf with database_uri. Used for parameter by name injection """
conf = {
"TESTING": True,
"DEBUG": True,
"DB_URI": database_uri,
"SECRET_KEY": 'superdupersecret!!!1',
}
return conf

View file

@ -13,8 +13,8 @@ up_mysql() {
}
down_mysql() {
docker kill mysql
docker rm -v mysql
docker kill mysql || true
docker rm -v mysql || true
}
up_postgres() {
@ -30,8 +30,8 @@ up_postgres() {
}
down_postgres() {
docker kill postgres
docker rm -v postgres
docker kill postgres || true
docker rm -v postgres || true
}
run_tests() {
@ -39,7 +39,7 @@ run_tests() {
PYTHONPATH=. TEST_DATABASE_URI=$1 TEST=true alembic upgrade head
# Run the full test suite.
SKIP_DB_SCHEMA=true TEST_DATABASE_URI=$1 TEST=true python -m unittest discover -f
PYTHONPATH=. SKIP_DB_SCHEMA=true TEST_DATABASE_URI=$1 TEST=true py.test ${2:-.} --ignore=endpoints/appr/test/
}
CIP=${CONTAINERIP-'127.0.0.1'}
@ -48,20 +48,22 @@ echo "> Using container IP address $CIP"
# NOTE: MySQL is currently broken on setup.
# Test (and generate, if requested) via MySQL.
echo '> Starting MySQL'
down_mysql
up_mysql
echo '> Running Full Test Suite (mysql)'
set +e
run_tests "mysql+pymysql://root:password@$CIP/genschema"
run_tests "mysql+pymysql://root:password@$CIP/genschema" $1
set -e
down_mysql
# Test via Postgres.
echo '> Starting Postgres'
down_postgres
up_postgres
echo '> Running Full Test Suite (postgres)'
set +e
run_tests "postgresql://postgres@$CIP/genschema"
run_tests "postgresql://postgres@$CIP/genschema" $1
set -e
down_postgres

View file

@ -859,15 +859,17 @@ class TestDeleteNamespace(ApiTestCase):
def test_deletenamespaces(self):
self.login(ADMIN_ACCESS_USER)
# Try to first delete the user. Since they are the sole admin of two orgs, it should fail.
# Try to first delete the user. Since they are the sole admin of three orgs, it should fail.
with check_transitive_modifications():
self.deleteResponse(User, expected_code=400)
# Delete the two orgs, checking in between.
# Delete the three orgs, checking in between.
with check_transitive_modifications():
self.deleteEmptyResponse(Organization, params=dict(orgname=ORGANIZATION), expected_code=204)
self.deleteResponse(User, expected_code=400) # Should still fail.
self.deleteEmptyResponse(Organization, params=dict(orgname='library'), expected_code=204)
self.deleteResponse(User, expected_code=400) # Should still fail.
self.deleteEmptyResponse(Organization, params=dict(orgname='titi'), expected_code=204)
# Add some queue items for the user.
notification_queue.put([ADMIN_ACCESS_USER, 'somerepo', 'somename'], '{}')
@ -1007,7 +1009,7 @@ class TestConductSearch(ApiTestCase):
json = self.getJsonResponse(ConductSearch,
params=dict(query='owners'))
self.assertEquals(3, len(json['results']))
self.assertEquals(4, len(json['results']))
self.assertEquals(json['results'][0]['kind'], 'team')
self.assertEquals(json['results'][0]['name'], 'owners')

View file

@ -23,15 +23,10 @@ class TestRepoModel(unittest.TestCase):
self.ctx.__exit__(True, None, None)
def test_popular_repo_list(self):
# Our repository action count table should have 1 event for the only public
# repo.
onlypublic = model.repository.list_popular_public_repos(0, timedelta(weeks=1))
self.assertEquals(len(onlypublic), 1)
self.assertEquals(onlypublic[0], (PUBLIC_USERNAME, PUBLIC_REPONAME))
self.assertEquals(len(model.repository.list_popular_public_repos(1, timedelta(weeks=1))), 1)
self.assertEquals(len(model.repository.list_popular_public_repos(50, timedelta(weeks=1))), 0)
if __name__ == '__main__':
unittest.main()