mirror of
https://github.com/zulip/zulip.git
synced 2025-11-21 15:09:34 +00:00
Optimize checks of test database state by moving into Python.
Previously, the generate-fixtures shell script by called into Django multiple times in order to check whether the database was in a reasonable state. Since there's a lot of overhead to starting up Django, this resulted in `test-backend` and `test-js-with-casper` being quite slow to run a single small test (2.8s or so) even on my very fast laptop. We fix this is by moving the checks into a new Python library, so that we can avoid paying the Django startup overhead 3 times unnecessarily. The result saves about 1.2s (~40%) from the time required to run a single backend test. Fixes #1221.
This commit is contained in:
@@ -11,6 +11,7 @@ try:
|
|||||||
# outside a Zulip virtualenv.
|
# outside a Zulip virtualenv.
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
import requests
|
import requests
|
||||||
|
import django
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print("ImportError: {}".format(e))
|
print("ImportError: {}".format(e))
|
||||||
print("You need to run the Zulip tests inside a Zulip dev environment.")
|
print("You need to run the Zulip tests inside a Zulip dev environment.")
|
||||||
@@ -39,6 +40,13 @@ parser.add_option('--remote-debug',
|
|||||||
default=False)
|
default=False)
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
||||||
|
from zerver.lib.test_fixtures import is_template_database_current
|
||||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'zproject.test_settings'
|
||||||
|
django.setup()
|
||||||
|
os.environ['PYTHONUNBUFFERED'] = 'y'
|
||||||
|
|
||||||
os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
|
os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..'))
|
||||||
|
|
||||||
subprocess.check_call('tools/setup/generate-test-credentials')
|
subprocess.check_call('tools/setup/generate-test-credentials')
|
||||||
@@ -72,7 +80,11 @@ def run_tests(realms_have_subdomains, files):
|
|||||||
file = os.path.join(os.path.dirname(__file__), '../frontend_tests/casper_tests', file)
|
file = os.path.join(os.path.dirname(__file__), '../frontend_tests/casper_tests', file)
|
||||||
test_files.append(os.path.abspath(file))
|
test_files.append(os.path.abspath(file))
|
||||||
|
|
||||||
subprocess.check_call('tools/setup/generate-fixtures')
|
generate_fixtures_command = ['tools/setup/generate-fixtures']
|
||||||
|
if not is_template_database_current():
|
||||||
|
generate_fixtures_command.append('--force')
|
||||||
|
|
||||||
|
subprocess.check_call(generate_fixtures_command)
|
||||||
|
|
||||||
remote_debug = ""
|
remote_debug = ""
|
||||||
if options.remote_debug:
|
if options.remote_debug:
|
||||||
|
|||||||
@@ -2,16 +2,10 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
function migration_status {
|
function migration_status {
|
||||||
./manage.py migrate --list --settings=zproject.test_settings | sed 's/*/ /' > "$1"
|
./manage.py get_migration_status --settings=zproject.test_settings > $1
|
||||||
}
|
}
|
||||||
|
|
||||||
template_grep_error_code=$(echo "SELECT 1 from pg_database WHERE datname='zulip_test_template';" | python manage.py dbshell --settings=zproject.test_settings | grep -q "1 row"; echo $?)
|
if [ "$1" != "--force" ]; then
|
||||||
|
|
||||||
if [ "$template_grep_error_code" == "0" ]; then
|
|
||||||
migration_status var/available-migrations
|
|
||||||
if [ -e var/migration-status ] &&
|
|
||||||
cmp -s var/available-migrations var/migration-status &&
|
|
||||||
[ "$1" != "--force" ]; then
|
|
||||||
"$(dirname "$0")/../../scripts/setup/terminate-psql-sessions" zulip zulip_test zulip_test_base zulip_test_template
|
"$(dirname "$0")/../../scripts/setup/terminate-psql-sessions" zulip zulip_test zulip_test_base zulip_test_template
|
||||||
psql -h localhost postgres zulip_test << EOF
|
psql -h localhost postgres zulip_test << EOF
|
||||||
DROP DATABASE IF EXISTS zulip_test;
|
DROP DATABASE IF EXISTS zulip_test;
|
||||||
@@ -19,7 +13,6 @@ CREATE DATABASE zulip_test TEMPLATE zulip_test_template;
|
|||||||
EOF
|
EOF
|
||||||
sh "$(dirname "$0")/../../scripts/setup/flush-memcached"
|
sh "$(dirname "$0")/../../scripts/setup/flush-memcached"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p zerver/fixtures
|
mkdir -p zerver/fixtures
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ if __name__ == "__main__":
|
|||||||
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
os.chdir(os.path.dirname(TOOLS_DIR))
|
os.chdir(os.path.dirname(TOOLS_DIR))
|
||||||
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
||||||
|
from zerver.lib.test_fixtures import is_template_database_current
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'zproject.test_settings'
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'zproject.test_settings'
|
||||||
# "-u" uses unbuffered IO, which is important when wrapping it in subprocess
|
# "-u" uses unbuffered IO, which is important when wrapping it in subprocess
|
||||||
os.environ['PYTHONUNBUFFERED'] = 'y'
|
os.environ['PYTHONUNBUFFERED'] = 'y'
|
||||||
@@ -136,7 +137,11 @@ if __name__ == "__main__":
|
|||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
if options.generate_fixtures:
|
if options.generate_fixtures:
|
||||||
subprocess.call(os.path.join(TOOLS_DIR, 'setup', 'generate-fixtures'))
|
generate_fixtures_command = [os.path.join(TOOLS_DIR, 'setup', 'generate-fixtures')]
|
||||||
|
if not is_template_database_current():
|
||||||
|
generate_fixtures_command.append('--force')
|
||||||
|
|
||||||
|
subprocess.call(generate_fixtures_command)
|
||||||
|
|
||||||
TestRunner = get_runner(settings)
|
TestRunner = get_runner(settings)
|
||||||
test_runner = TestRunner()
|
test_runner = TestRunner()
|
||||||
|
|||||||
69
zerver/lib/test_fixtures.py
Normal file
69
zerver/lib/test_fixtures.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from typing import Any, Optional
|
||||||
|
from importlib import import_module
|
||||||
|
from six import text_type
|
||||||
|
from six.moves import cStringIO as StringIO
|
||||||
|
|
||||||
|
from django.db import connections, DEFAULT_DB_ALIAS
|
||||||
|
from django.apps import apps
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.utils.module_loading import module_has_submodule
|
||||||
|
|
||||||
|
def database_exists(database_name, **options):
|
||||||
|
# type: (text_type, **Any) -> bool
|
||||||
|
db = options.get('database', DEFAULT_DB_ALIAS)
|
||||||
|
connection = connections[db]
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute("SELECT 1 from pg_database WHERE datname='{}';".format(database_name))
|
||||||
|
return_value = bool(cursor.fetchone())
|
||||||
|
connections.close_all()
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
def get_migration_status(**options):
|
||||||
|
# type: (**Any) -> str
|
||||||
|
verbosity = options.get('verbosity', 1)
|
||||||
|
|
||||||
|
for app_config in apps.get_app_configs():
|
||||||
|
if module_has_submodule(app_config.module, "management"):
|
||||||
|
import_module('.management', app_config.name)
|
||||||
|
|
||||||
|
app_labels = [options['app_label']] if options.get('app_label') else None
|
||||||
|
db = options.get('database', DEFAULT_DB_ALIAS)
|
||||||
|
out = StringIO()
|
||||||
|
call_command(
|
||||||
|
'showmigrations',
|
||||||
|
'--list',
|
||||||
|
app_labels=app_labels,
|
||||||
|
database=db,
|
||||||
|
no_color=options.get('no_color', False),
|
||||||
|
settings=options.get('settings', os.environ['DJANGO_SETTINGS_MODULE']),
|
||||||
|
stdout=out,
|
||||||
|
traceback=options.get('traceback', True),
|
||||||
|
verbosity=verbosity,
|
||||||
|
)
|
||||||
|
connections.close_all()
|
||||||
|
out.seek(0)
|
||||||
|
output = out.read()
|
||||||
|
return re.sub('\x1b\[(1|0)m', '', output)
|
||||||
|
|
||||||
|
def are_migrations_the_same(migration_file, **options):
|
||||||
|
# type: (text_type, **Any) -> bool
|
||||||
|
if not os.path.exists(migration_file):
|
||||||
|
return False
|
||||||
|
|
||||||
|
with open(migration_file) as f:
|
||||||
|
migration_content = f.read()
|
||||||
|
return migration_content == get_migration_status(**options)
|
||||||
|
|
||||||
|
def is_template_database_current(
|
||||||
|
database_name='zulip_test_template',
|
||||||
|
migration_status='var/migration-status',
|
||||||
|
settings='zproject.test_settings'
|
||||||
|
):
|
||||||
|
# type: (Optional[text_type], Optional[text_type], Optional[text_type]) -> bool
|
||||||
|
if database_exists(database_name):
|
||||||
|
return are_migrations_the_same(migration_status, settings=settings)
|
||||||
|
return False
|
||||||
24
zerver/management/commands/get_migration_status.py
Normal file
24
zerver/management/commands/get_migration_status.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import argparse
|
||||||
|
from typing import Any
|
||||||
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from zerver.lib.test_fixtures import get_migration_status
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Get status of migrations."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
# type: (argparse.ArgumentParser) -> None
|
||||||
|
parser.add_argument('app_label', nargs='?',
|
||||||
|
help='App label of an application to synchronize the state.')
|
||||||
|
|
||||||
|
parser.add_argument('--database', action='store', dest='database',
|
||||||
|
default=DEFAULT_DB_ALIAS, help='Nominates a database to synchronize. '
|
||||||
|
'Defaults to the "default" database.')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# type: (*Any, **Any) -> None
|
||||||
|
self.stdout.write(get_migration_status(**options))
|
||||||
Reference in New Issue
Block a user