6
0
mirror of https://github.com/FirebirdSQL/firebird-qa.git synced 2025-01-22 13:33:07 +01:00

Release 0.18.0; Added cache for empty databases

This commit is contained in:
Pavel Císař 2023-02-14 16:20:50 +01:00
parent 562b7de322
commit d648f29359
3 changed files with 92 additions and 11 deletions

View File

@ -4,6 +4,18 @@ Changelog
.. currentmodule:: firebird.qa.plugin .. currentmodule:: firebird.qa.plugin
Version 0.18.0
==============
* Added cache for empty databases. This works transparently and does not require any
special configuration. Databases are stored in `dbcache` subdirectory (created automatically)
for combination of ODS + page size + SQL dialect + character set.
Files in `dbcache` directory could be removed as needed (including whole directory)
to fore creation of new database.
Cache is enabled by default. Use new --disable-db-cache option to disable it.
Version 0.17.3 Version 0.17.3
============== ==============

View File

@ -37,7 +37,7 @@
""" """
from __future__ import annotations from __future__ import annotations
from typing import List, Dict, Union, Optional, Tuple, Sequence from typing import List, Dict, Union, Optional, Tuple, Sequence, Set
import sys import sys
import locale import locale
import os import os
@ -97,6 +97,47 @@ def log_session_context(record_testsuite_property):
record_testsuite_property('architecture', _vars_['arch']) record_testsuite_property('architecture', _vars_['arch'])
record_testsuite_property('mode', _vars_['server-arch']) record_testsuite_property('mode', _vars_['server-arch'])
class DbCache:
"""Cache for empty databases.
"""
def __init__(self):
self.cache: Path = _vars_['dbcache']
self.databases: Dict[str, Path] = {}
for db in self.cache.glob('**/*.fdb'):
self.databases[db.stem] = db
def get_db(self, page_size: Optional[int]=None, sql_dialect: Optional[int]=None,
charset: Optional[str] = None) -> Optional[Path]:
"""Returns path to cached database or None if database is not in cache.
Arguments:
page_size: Database page size.
sql_dialect: Database SQL dialect.
charset: Database character set.
"""
return self.databases.get(f"db-{_vars_['ods'][0]}.{_vars_['ods'][1]}-{page_size}-{sql_dialect}-{charset}")
def store_db(self, src_path: Path, page_size: Optional[int]=None,
sql_dialect: Optional[int]=None, charset: Optional[str] = None) -> None:
"""Copy database to cache.
Arguments:
page_size: Database page size.
sql_dialect: Database SQL dialect.
charset: Database character set.
"""
db_name = f"db-{_vars_['ods'][0]}.{_vars_['ods'][1]}-{page_size}-{sql_dialect}-{charset}.fdb"
db_path = self.cache / db_name
shutil.copyfile(src_path, db_path)
# Fix permissions
if platform.system != 'Windows':
os.chmod(db_path, 33206)
self.databases[db_name] = db_path
@pytest.fixture(scope='session', autouse=True)
def db_cache() -> DbCache:
"""Database cache for empty databases.
"""
return DbCache()
class ExecutionError(Exception): class ExecutionError(Exception):
"""Exception used to indicate errors when external QA tools (like isql, gstat etc.) are executed. """Exception used to indicate errors when external QA tools (like isql, gstat etc.) are executed.
""" """
@ -114,6 +155,8 @@ def pytest_addoption(parser, pluginmanager):
grp.addoption('--protocol', grp.addoption('--protocol',
choices=[i.name.lower() for i in NetProtocol], choices=[i.name.lower() for i in NetProtocol],
help="Network protocol used for database attachments") help="Network protocol used for database attachments")
grp.addoption('--disable-db-cache', action='store_true', default=False,
help="Disable cache for empty databases")
grp.addoption('--runslow', action='store_true', default=False, help="Run slow tests") grp.addoption('--runslow', action='store_true', default=False, help="Run slow tests")
grp.addoption('--save-output', action='store_true', default=False, help="Save test std[out|err] output to files") grp.addoption('--save-output', action='store_true', default=False, help="Save test std[out|err] output to files")
grp.addoption('--skip-deselected', choices=[SKIP_PLATFORM, SKIP_VERSION, SKIP_ANY], grp.addoption('--skip-deselected', choices=[SKIP_PLATFORM, SKIP_VERSION, SKIP_ANY],
@ -130,6 +173,7 @@ def pytest_report_header(config):
f" encodings: sys:{sys.getdefaultencoding()} locale:{locale.getpreferredencoding()} filesystem:{sys.getfilesystemencoding()}", f" encodings: sys:{sys.getdefaultencoding()} locale:{locale.getpreferredencoding()} filesystem:{sys.getfilesystemencoding()}",
"Firebird:", "Firebird:",
f" configuration: {_vars_['driver-config']}", f" configuration: {_vars_['driver-config']}",
f" ODS: {_vars_['ods'][0]}.{_vars_['ods'][1]}",
f" server: {_vars_['server']} [v{_vars_['version']}, {_vars_['server-arch']}, {_vars_['arch']}]", f" server: {_vars_['server']} [v{_vars_['version']}, {_vars_['server-arch']}, {_vars_['arch']}]",
f" home: {_vars_['home-dir']}", f" home: {_vars_['home-dir']}",
f" bin: {_vars_['bin-dir']}", f" bin: {_vars_['bin-dir']}",
@ -285,9 +329,15 @@ def pytest_configure(config):
driver_config.register_database('pytest') driver_config.register_database('pytest')
# #
_vars_['driver-config'] = config.getoption('driver_config') _vars_['driver-config'] = config.getoption('driver_config')
_vars_['dbcache-disabled'] = config.getoption('disable_db_cache')
_vars_['basetemp'] = config.getoption('basetemp') _vars_['basetemp'] = config.getoption('basetemp')
_vars_['runslow'] = config.getoption('runslow') _vars_['runslow'] = config.getoption('runslow')
_vars_['root'] = config.rootpath _vars_['root'] = config.rootpath
path: Path = config.rootpath / 'dbcache'
path.mkdir(exist_ok=True)
if platform.system != 'Windows':
path.chmod(16895)
_vars_['dbcache'] = path if path.is_dir() else config.rootpath
path = config.rootpath / 'databases' path = config.rootpath / 'databases'
_vars_['databases'] = path if path.is_dir() else config.rootpath _vars_['databases'] = path if path.is_dir() else config.rootpath
path = config.rootpath / 'backups' path = config.rootpath / 'backups'
@ -347,6 +397,7 @@ def pytest_configure(config):
# Server architecture [CS,SS,SC] and sample directory # Server architecture [CS,SS,SC] and sample directory
_vars_['sample_dir'] = None _vars_['sample_dir'] = None
with connect('employee') as con1, connect('employee') as con2: with connect('employee') as con1, connect('employee') as con2:
_vars_['ods'] = (con1.info.ods_version, con1.info.ods_minor_version)
db_path = Path(con1.info.name) db_path = Path(con1.info.name)
_vars_['sample_dir'] = db_path.parent _vars_['sample_dir'] = db_path.parent
sql = f""" sql = f"""
@ -584,7 +635,8 @@ class Database:
"""Returns `firebird-driver`_ configuration for test database. """Returns `firebird-driver`_ configuration for test database.
""" """
return driver_config.get_database(self.config_name) return driver_config.get_database(self.config_name)
def create(self, page_size: Optional[int]=None, sql_dialect: Optional[int]=None) -> None: def create(self, page_size: Optional[int]=None, sql_dialect: Optional[int]=None,
cache: DbCache=None) -> None:
"""Create the test database. """Create the test database.
Arguments: Arguments:
@ -598,11 +650,28 @@ class Database:
""" """
__tracebackhide__ = True __tracebackhide__ = True
self._make_config(page_size=page_size, sql_dialect=sql_dialect, charset=self.charset) # Path means database file, str means alias
charset = self.charset use_cache: bool = isinstance(self.db_path, Path) and cache is not None and not _vars_['dbcache-disabled']
print(f"Creating db: {self.dsn} [{page_size=}, {sql_dialect=}, {charset=}, user={self.user}, password={self.password}]") src_path: Path = None
with create_database(self.config_name): if use_cache:
pass src_path = cache.get_db(page_size=page_size, sql_dialect=sql_dialect, charset=self.charset)
if src_path:
charset = self.charset
print(f"Cached db: {src_path.name} [{page_size=}, {sql_dialect=}, {charset=}")
shutil.copyfile(src_path, self.db_path)
# Fix permissions
if platform.system != 'Windows':
os.chmod(self.db_path, 33206)
else:
self._make_config(page_size=page_size, sql_dialect=sql_dialect, charset=self.charset)
charset = self.charset
print(f"Creating db: {self.dsn} [{page_size=}, {sql_dialect=}, {charset=}, user={self.user}, password={self.password}]")
with create_database(self.config_name):
pass
if use_cache:
cache.store_db(self.db_path, page_size=page_size, sql_dialect=sql_dialect,
charset=self.charset)
def restore(self, backup: str) -> None: def restore(self, backup: str) -> None:
"""Create the test database from backup. """Create the test database from backup.
@ -644,7 +713,7 @@ class Database:
""" """
__tracebackhide__ = True __tracebackhide__ = True
src_path = _vars_['databases'] / filename src_path = _vars_['databases'] / filename
#print(f"Copying db: {self.db_path} from {src_path}") print(f"Copying db: {self.db_path} from {src_path}")
shutil.copyfile(src_path, self.db_path) shutil.copyfile(src_path, self.db_path)
# Fix permissions # Fix permissions
if platform.system != 'Windows': if platform.system != 'Windows':
@ -794,12 +863,12 @@ def db_factory(*, filename: str='test.fdb', init: Optional[str]=None,
""" """
@pytest.fixture @pytest.fixture
def database_fixture(request: pytest.FixtureRequest, db_path) -> Database: def database_fixture(request: pytest.FixtureRequest, db_path, db_cache) -> Database:
db = Database(db_path, filename, user, password, charset, debug=str(request.module), db = Database(db_path, filename, user, password, charset, debug=str(request.module),
config_name=config_name, utf8filename=utf8filename) config_name=config_name, utf8filename=utf8filename)
if not do_not_create: if not do_not_create:
if from_backup is None and copy_of is None: if from_backup is None and copy_of is None:
db.create(page_size, sql_dialect) db.create(page_size, sql_dialect, db_cache)
elif from_backup is not None: elif from_backup is not None:
db.restore(from_backup) db.restore(from_backup)
elif copy_of is not None: elif copy_of is not None:

View File

@ -5,7 +5,7 @@ all-files=True
[metadata] [metadata]
name = firebird-qa name = firebird-qa
version = 0.17.3 version = 0.18.0
description = pytest plugin for Firebird QA description = pytest plugin for Firebird QA
long_description = file: README.rst long_description = file: README.rst
long_description_content_type = text/x-rst; charset=UTF-8 long_description_content_type = text/x-rst; charset=UTF-8