Change fulldbtests to use py.test

This commit is contained in:
Joseph Schorr 2017-04-24 15:52:50 -04:00
parent 6ba7ed4cd6
commit a1a4b68306
3 changed files with 79 additions and 15 deletions

View file

@ -20,7 +20,7 @@ from data.database import (db, all_models, beta_classes, Role, TeamRole, Visibil
ExternalNotificationEvent, ExternalNotificationMethod, NotificationKind, ExternalNotificationEvent, ExternalNotificationMethod, NotificationKind,
QuayRegion, QuayService, UserRegion, OAuthAuthorizationCode, QuayRegion, QuayService, UserRegion, OAuthAuthorizationCode,
ServiceKeyApprovalType, MediaType, LabelSourceType, UserPromptKind, ServiceKeyApprovalType, MediaType, LabelSourceType, UserPromptKind,
RepositoryKind, TagKind, BlobPlacementLocation) RepositoryKind, TagKind, BlobPlacementLocation, User)
from data import model from data import model
from data.queue import WorkQueue from data.queue import WorkQueue
from app import app, storage as store, tf from app import app, storage as store, tf
@ -440,6 +440,15 @@ def wipe_database():
def populate_database(minimal=False, with_storage=False): def populate_database(minimal=False, with_storage=False):
logger.debug('Populating the DB with test data.') logger.debug('Populating the DB with test data.')
# Check if the data already exists. If so, we skip. This can happen between calls from the
# "old style" tests and the new py.test's.
try:
User.get(username='devtable')
logger.debug('DB already populated')
return
except User.DoesNotExist:
pass
# Note: databases set up with "real" schema (via Alembic) will not have these types # Note: databases set up with "real" schema (via Alembic) will not have these types
# type, so we it here it necessary. # type, so we it here it necessary.
try: try:

View file

@ -1,10 +1,12 @@
import os import os
from cachetools import lru_cache
import pytest import pytest
import shutil import shutil
from flask import Flask, jsonify from flask import Flask, jsonify
from flask_login import LoginManager from flask_login import LoginManager
from peewee import SqliteDatabase from peewee import SqliteDatabase, savepoint, InternalError
from app import app as application from app import app as application
from data import model from data import model
@ -14,30 +16,56 @@ from endpoints.api import api_bp
from endpoints.web import web from endpoints.web import web
from initdb import initialize_database, populate_database from initdb import initialize_database, populate_database
from path_converters import APIRepositoryPathConverter, RegexConverter, RepositoryPathConverter from path_converters import APIRepositoryPathConverter, RegexConverter, RepositoryPathConverter
from test.testconfig import FakeTransaction from test.testconfig import FakeTransaction
@pytest.fixture(scope="session") @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): def init_db_path(tmpdir_factory):
""" Creates a new database and appropriate configuration. Note that the initial database """ 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 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. makes a copy of the SQLite database file on disk and passes a new copy to each test.
""" """
sqlitedb_file = str(tmpdir_factory.mktemp("data").join("test.db")) if os.environ.get('TEST_DATABASE_URI'):
sqlitedb = 'sqlite:///{0}'.format(sqlitedb_file) 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, conf = {"TESTING": True,
"DEBUG": True, "DEBUG": True,
"DB_URI": sqlitedb} "DB_URI": sqlitedb}
os.environ['TEST_DATABASE_URI'] = str(sqlitedb)
os.environ['DB_URI'] = str(sqlitedb) os.environ['DB_URI'] = str(sqlitedb)
db.initialize(SqliteDatabase(sqlitedb_file)) db.initialize(SqliteDatabase(sqlitedbfile))
application.config.update(conf) application.config.update(conf)
application.config.update({"DB_URI": sqlitedb}) application.config.update({"DB_URI": sqlitedb})
initialize_database() initialize_database()
populate_database() populate_database()
close_db_filter(None) close_db_filter(None)
return str(sqlitedb_file) return str(sqlitedbfile)
@pytest.fixture() @pytest.fixture()
@ -47,6 +75,11 @@ def database_uri(monkeypatch, init_db_path, sqlitedb_file):
on a per-test basis. In the non-SQLite case, a reference to the existing database URI is on a per-test basis. In the non-SQLite case, a reference to the existing database URI is
returned. 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. # Copy the golden database file to a new path.
shutil.copy2(init_db_path, sqlitedb_file) shutil.copy2(init_db_path, sqlitedb_file)
@ -84,8 +117,28 @@ def appconfig(database_uri):
@pytest.fixture() @pytest.fixture()
def initialized_db(appconfig): def initialized_db(appconfig):
""" Configures the database for the database found in the appconfig. """ """ Configures the database for the database found in the appconfig. """
# Configure the database.
configure(appconfig) 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() @pytest.fixture()
def app(appconfig, initialized_db): def app(appconfig, initialized_db):
""" Used by pytest-flask plugin to inject a custom app instance for testing. """ """ Used by pytest-flask plugin to inject a custom app instance for testing. """

View file

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