Skip to content

Commit ffd47c8

Browse files
committed
Changelog; hatch config; doc and test adjustments
1 parent b227525 commit ffd47c8

File tree

5 files changed

+239
-20
lines changed

5 files changed

+239
-20
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7-
## [2.0.0] - Unreleased
7+
## [2.0.0] - 2025-04-30
88

99
### Changed
1010

pyproject.toml

-18
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,6 @@ dependencies = [
5858
[tool.hatch.envs.hatch-test]
5959
extra-args = ["--host=localhost"]
6060

61-
[tool.hatch.envs.test]
62-
dependencies = [
63-
"coverage[toml]>=6.5",
64-
"pytest",
65-
]
66-
[tool.hatch.envs.test.scripts]
67-
test = "pytest {args:tests}"
68-
test-cov = "coverage run -m pytest {args:tests}"
69-
cov-report = [
70-
"- coverage combine",
71-
"coverage report",
72-
]
73-
cov = [
74-
"test-cov",
75-
"cov-report",
76-
]
77-
version = "python --version"
78-
7961
[[tool.hatch.envs.hatch-test.matrix]]
8062
python = ["3.11", "3.12", "3.13"]
8163

src/firebird/lib/logmsgs.py

+2
Original file line numberDiff line numberDiff line change
@@ -994,12 +994,14 @@ def identify_msg(msg: str) -> tuple[MsgDesc, dict[str, Any], bool] | None:
994994
995995
Returns:
996996
A tuple containing:
997+
997998
- The matched `.MsgDesc` instance.
998999
- A dictionary mapping parameter names (from placeholders like `{s:name}`)
9991000
to their extracted values (as strings or integers).
10001001
- A boolean flag: `True` if the optional part of the message format
10011002
(following 'OPTIONAL') was *not* present in the input `msg`,
10021003
`False` otherwise.
1004+
10031005
Returns `None` if the `msg` does not match any known `.MsgDesc` pattern.
10041006
"""
10051007
parts = msg.split()

tests/conftest.py

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# SPDX-FileCopyrightText: 2025-present The Firebird Projects <www.firebirdsql.org>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
#
5+
# PROGRAM/MODULE: firebird-base
6+
# FILE: tests/conftest.py
7+
# DESCRIPTION: Common fixtures
8+
# CREATED: 28.1.2025
9+
#
10+
# The contents of this file are subject to the MIT License
11+
#
12+
# Permission is hereby granted, free of charge, to any person obtaining a copy
13+
# of this software and associated documentation files (the "Software"), to deal
14+
# in the Software without restriction, including without limitation the rights
15+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
# copies of the Software, and to permit persons to whom the Software is
17+
# furnished to do so, subject to the following conditions:
18+
#
19+
# The above copyright notice and this permission notice shall be included in all
20+
# copies or substantial portions of the Software.
21+
#
22+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28+
# SOFTWARE.
29+
#
30+
# Copyright (c) 2025 Firebird Project (www.firebirdsql.org)
31+
# All Rights Reserved.
32+
#
33+
# Contributor(s): Pavel Císař (original code)
34+
# ______________________________________.
35+
36+
from __future__ import annotations
37+
38+
from pathlib import Path
39+
import platform
40+
from shutil import copyfile
41+
from configparser import ConfigParser
42+
43+
import pytest
44+
from packaging.specifiers import SpecifierSet
45+
from packaging.version import parse
46+
from firebird.base.config import EnvExtendedInterpolation
47+
from firebird.driver import driver_config, get_api, connect_server, connect
48+
from firebird.base.config import ConfigProto
49+
50+
_vars_: dict = {'client-lib': None,
51+
'firebird-config': None,
52+
'server': None,
53+
'host': None,
54+
'port': None,
55+
'user': 'SYSDBA',
56+
'password': 'masterkey',
57+
}
58+
59+
_platform: str = platform.system()
60+
61+
# Configuration
62+
63+
def pytest_addoption(parser, pluginmanager):
64+
"""Adds specific pytest command-line options.
65+
66+
.. seealso:: `pytest documentation <_pytest.hookspec.pytest_addoption>` for details.
67+
"""
68+
grp = parser.getgroup('firebird', "Firebird driver QA", 'general')
69+
grp.addoption('--host', help="Server host", default=None, required=False)
70+
grp.addoption('--port', help="Server port", default=None, required=False)
71+
grp.addoption('--client-lib', help="Firebird client library", default=None, required=False)
72+
grp.addoption('--server', help="Server configuration name", default='', required=False)
73+
grp.addoption('--driver-config', help="Firebird driver configuration filename", default=None)
74+
75+
@pytest.hookimpl(trylast=True)
76+
def pytest_configure(config):
77+
"""General configuration.
78+
79+
.. seealso:: `pytest documentation <_pytest.hookspec.pytest_configure>` for details.
80+
"""
81+
if config.getoption('help'):
82+
return
83+
# Base paths
84+
root_path: Path = Path(config.rootpath)
85+
_vars_['root'] = root_path
86+
path = config.rootpath / 'tests' / 'databases'
87+
_vars_['databases'] = path if path.is_dir() else config.rootpath / 'tests'
88+
path = config.rootpath / 'tests' / 'backups'
89+
_vars_['backups'] = path if path.is_dir() else config.rootpath / 'tests'
90+
path = config.rootpath / 'tests' / 'files'
91+
_vars_['files'] = path if path.is_dir() else config.rootpath / 'tests'
92+
# Driver configuration
93+
db_config = driver_config.register_database('pytest')
94+
if server := config.getoption('server'):
95+
db_config.server.value = server
96+
_vars_['server'] = server
97+
98+
config_path: Path = root_path / 'tests' / 'firebird-driver.conf'
99+
if cfg_path := config.getoption('driver_config'):
100+
config_path = Path(cfg_path)
101+
if config_path.is_file():
102+
driver_config.read(str(config_path))
103+
_vars_['firebird-config'] = config_path
104+
srv_conf = driver_config.get_server(_vars_['server'])
105+
_vars_['host'] = srv_conf.host.value
106+
_vars_['port'] = srv_conf.port.value
107+
_vars_['user'] = srv_conf.user.value
108+
_vars_['password'] = srv_conf.password.value
109+
# Handle server-specific "fb_client_library" configuration option
110+
#_vars_['client-lib'] = 'UNKNOWN'
111+
cfg = ConfigParser(interpolation=EnvExtendedInterpolation())
112+
cfg.read(str(config_path))
113+
if cfg.has_option(_vars_['server'], 'fb_client_library'):
114+
fbclient = Path(cfg.get(_vars_['server'], 'fb_client_library'))
115+
if not fbclient.is_file():
116+
pytest.exit(f"Client library '{fbclient}' not found!")
117+
driver_config.fb_client_library.value = str(fbclient)
118+
cfg.clear()
119+
else:
120+
# No configuration file, so we process 'host' and 'client-lib' options
121+
if client_lib := config.getoption('client_lib'):
122+
client_lib = Path(client_lib)
123+
if not client_lib.is_file():
124+
pytest.exit(f"Client library '{client_lib}' not found!")
125+
driver_config.fb_client_library.value = client_lib
126+
#
127+
if host := config.getoption('host'):
128+
_vars_['host'] = host
129+
_vars_['port'] = config.getoption('port')
130+
driver_config.server_defaults.host.value = config.getoption('host')
131+
driver_config.server_defaults.port.value = config.getoption('port')
132+
driver_config.server_defaults.user.value = 'SYSDBA'
133+
driver_config.server_defaults.password.value = 'masterkey'
134+
# THIS should load the driver API, do not connect db or server earlier!
135+
_vars_['client-lib'] = get_api().client_library_name
136+
# Information from server
137+
with connect_server('') as srv:
138+
version = parse(srv.info.version.replace('-dev', ''))
139+
_vars_['version'] = version
140+
_vars_['home-dir'] = Path(srv.info.home_directory)
141+
bindir = _vars_['home-dir'] / 'bin'
142+
if not bindir.exists():
143+
bindir = _vars_['home-dir']
144+
_vars_['bin-dir'] = bindir
145+
_vars_['lock-dir'] = Path(srv.info.lock_directory)
146+
_vars_['bin-dir'] = Path(bindir) if bindir else _vars_['home-dir']
147+
_vars_['security-db'] = Path(srv.info.security_database)
148+
_vars_['arch'] = srv.info.architecture
149+
# Create copy of test database
150+
if version in SpecifierSet('>=3.0, <4'):
151+
source_filename = 'fbtest30.fdb'
152+
elif version in SpecifierSet('>=4.0, <5'):
153+
source_filename = 'fbtest40.fdb'
154+
elif version in SpecifierSet('>=5.0, <6'):
155+
source_filename = 'fbtest50.fdb'
156+
else:
157+
pytest.exit(f"Unsupported Firebird version {version}")
158+
source_db_file: Path = _vars_['databases'] / source_filename
159+
if not source_db_file.is_file():
160+
pytest.exit(f"Source test database '{source_db_file}' not found!")
161+
_vars_['source_db'] = source_db_file
162+
163+
def pytest_report_header(config):
164+
"""Returns plugin-specific test session header.
165+
166+
.. seealso:: `pytest documentation <_pytest.hookspec.pytest_report_header>` for details.
167+
"""
168+
return ["Firebird:",
169+
f" configuration: {_vars_['firebird-config']}",
170+
f" server: {_vars_['server']} [v{_vars_['version']}, {_vars_['arch']}]",
171+
f" host: {_vars_['host']}",
172+
f" home: {_vars_['home-dir']}",
173+
f" bin: {_vars_['bin-dir']}",
174+
f" client library: {_vars_['client-lib']}",
175+
f" test database: {_vars_['source_db']}",
176+
]
177+
178+
@pytest.fixture(scope='session')
179+
def fb_vars():
180+
yield _vars_
181+
182+
@pytest.fixture(scope='session')
183+
def data_path(fb_vars):
184+
yield _vars_['files']
185+
186+
@pytest.fixture(scope='session')
187+
def tmp_dir(tmp_path_factory):
188+
path = tmp_path_factory.mktemp('db')
189+
if _platform != 'Windows':
190+
wdir = path
191+
while wdir is not wdir.parent:
192+
try:
193+
wdir.chmod(16895)
194+
except:
195+
pass
196+
wdir = wdir.parent
197+
yield path
198+
199+
@pytest.fixture(scope='session', autouse=True)
200+
def db_file(tmp_dir):
201+
test_db_filename: Path = tmp_dir / 'test-db.fdb'
202+
copyfile(_vars_['source_db'], test_db_filename)
203+
if _platform != 'Windows':
204+
test_db_filename.chmod(33206)
205+
driver_config.get_database('pytest').database.value = str(test_db_filename)
206+
return test_db_filename
207+
208+
@pytest.fixture(scope='session')
209+
def dsn(db_file):
210+
host = _vars_['host']
211+
port = _vars_['port']
212+
if host is None:
213+
result = str(db_file)
214+
else:
215+
result = f'{host}/{port}:{db_file}' if port else f'{host}:{db_file}'
216+
yield result
217+
218+
@pytest.fixture()
219+
def driver_cfg(tmp_path_factory):
220+
proto = ConfigProto()
221+
driver_config.save_proto(proto)
222+
yield driver_config
223+
driver_config.load_proto(proto)
224+
225+
@pytest.fixture
226+
def db_connection(driver_cfg):
227+
conn = connect('pytest', charset='UTF8')
228+
yield conn
229+
if not conn.is_closed():
230+
conn.close()
231+
232+
@pytest.fixture
233+
def server_connection(fb_vars):
234+
with connect_server(fb_vars['host'], user=fb_vars['user'], password=fb_vars['password']) as svc:
235+
yield svc

tests/test_gstat.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ def test_27_parse_bad_float_in_table():
606606
'MYTABLE (128)',
607607
' Average record length: abc',
608608
]
609-
with pytest.raises(Error, match="Unknown information \(line 3\)"): # Catches float() error
609+
with pytest.raises(Error, match="Unknown information"): # Catches float() error
610610
db.parse(lines)
611611

612612
def test_28_parse_bad_fill_range():

0 commit comments

Comments
 (0)