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

201 lines
8.0 KiB
Python

#coding:utf-8
"""
ID: issue-8077
ISSUE: https://github.com/FirebirdSQL/firebird/issues/8077
TITLE: Error "Too many recursion levels" does not stop execuition of code that uses ON DISCONNECT trigger (FB 4.x+)
DESCRIPTION:
Test creates DB-triggers 'ON CONNECT' and 'ON DISCONNECT', plus table 'LOG' for logging actions performed by DB-triggers.
Trigger 'ON DISCONNECT' makes EDS with new role thus creating new connection which will then be immediately finished and,
in turn, fire again this trigger. Eventually this must cause ISQL to terminate and firebird.log must contain errors.
Difference between old and new firebird.log must contain exactly two errors about detahc problem
(Error at disconnect: / Execute statement error at attach : / 335544830 : Too many recursion levels of EXECUTE STATEMENT)
NOTES:
[27.05.2024] pzotov
Time of ISQL execution is limited by MAX_WAIT_FOR_ISQL_TERMINATE seconds. Currently it is ~6s for SS and ~18s for CS.
[08.06.2024] pzotov
Added threshold in order to prevent infinite recursion in case of regression.
Otherwise this test can cause collapse of test machine because of infinite launch of firebird processes (in case of Classic).
See notes in the code below, variable 'STOP_RECURSIVE_ES_AFTER_ITER'.
Checked on snapshot 5.x that was not yet fixed.
Checked on 6.0.0.362, 5.0.1.1408, 4.0.5.3103 (all SS/CS).
"""
import re
from difflib import unified_diff
import pytest
import time
from pathlib import Path
import subprocess
import locale
import firebird.driver
from firebird.qa import *
db = db_factory(do_not_drop = True)
#db = db_factory()
substitutions = [
('^((?!(E|e)rror|exception|statement|recursion|source|rdb_trg|trigger|ext_pool_active|isql_outcome).)*$', '')
,('Execute statement error.*', 'Execute statement error')
,('Firebird::.*', 'Firebird::')
,('line(:)?\\s+\\d+.*', '')
,('[ \t]+', ' ')
]
act = python_act('db', substitutions = substitutions)
tmp_sql = temp_file('tmp_8077.sql')
tmp_log = temp_file('tmp_8077.log')
MAX_WAIT_FOR_ISQL_BEGIN_WORK=0.5
MAX_WAIT_FOR_ISQL_TERMINATE=30
# ### NOTE ###
# We have to define generator and increment it on each DB-level trigger invocation in order to prevent infinite loop
# if its value will be greater than some threshold (in case if regression will occur and engine for some reason will
# not able to detect too deep recursion).
# Value of this generator will be compared with threshold ('STOP_RECURSIVE_ES_AFTER_ITER') and no further recursive
# calls will be executed if generator exceeds this threshold.
# But value of threshold STOP_RECURSIVE_ES_AFTER_ITER must NOT be too small.
# Otherwise following message will be MESSED from output:
# Error at disconnect:
# Execute statement error
# 335544830 : Too many recursion levels of EXECUTE STATEMENT
# Data source : Firebird::
# At trigger 'TRG_DETACH'
# Currently last value of this sequence (after exception raising) is 99 for both SS and CS.
#
STOP_RECURSIVE_ES_AFTER_ITER = 101
#--------------------------------------------------------------------
@pytest.mark.es_eds
@pytest.mark.version('>=4.0.5')
def test_1(act: Action, tmp_sql: Path, tmp_log: Path, capsys):
test_sql = f"""
set list on;
set bail on;
set term ^;
create or alter trigger trg_detach on disconnect as begin end
^
create or alter trigger trg_attach on connect as begin end
^
create sequence g_attach
^
create sequence g_detach
^
create sequence g_diff
^
create exception exc_too_deep_recursion 'Execution terminated by @1: recursion depth exceeded the threshold @2 by @3'
^
recreate table log(id int primary key, att bigint default current_connection, event_name varchar(6) )
^
create or alter trigger trg_attach on connect as
declare v_pool_size int;
begin
if ( gen_id(g_attach,0) <= {STOP_RECURSIVE_ES_AFTER_ITER} ) then
in autonomous transaction do
insert into log(id, event_name) values (-gen_id(g_attach,1), 'attach');
else
exception exc_too_deep_recursion using ('trg_attach', {STOP_RECURSIVE_ES_AFTER_ITER}, gen_id(g_attach,1) - {STOP_RECURSIVE_ES_AFTER_ITER} - 1)
;
end
^
create or alter trigger trg_detach on disconnect as
begin
if ( gen_id(g_detach,0) <= {STOP_RECURSIVE_ES_AFTER_ITER} ) then
execute statement ('insert into log(id, event_name) values(?, ?)') (gen_id(g_detach,1), 'detach')
with autonomous transaction
on external 'localhost:' || rdb$get_context('SYSTEM', 'DB_NAME')
as user '{act.db.user}' password '{act.db.password}' role 'R' || replace(uuid_to_char(gen_uuid()),'-','')
;
else
exception exc_too_deep_recursion using ('trg_detach', {STOP_RECURSIVE_ES_AFTER_ITER}, gen_id(g_detach,1) - {STOP_RECURSIVE_ES_AFTER_ITER} - 1)
;
end
^
set term ;^
commit;
select
rdb$trigger_name as "rdb_trg_name"
,rdb$trigger_type as "rdb_trg_type"
from rdb$triggers
where rdb$system_flag is distinct from 1
order by rdb$trigger_name;
rollback;
connect '{act.db.dsn}' user {act.db.user} password '{act.db.password}';
quit;
"""
with open(tmp_sql, 'w') as f:
f.write(test_sql)
# Get Firebird log before test
# ----------------------------
fb_log_init = act.get_firebird_log()
failed_finish_isql_msg = ''
with act.db.connect() as con:
with open(tmp_log, 'w') as f:
try:
p_handed_isql = subprocess.Popen( [act.vars['isql'], '-i', str(tmp_sql),
'-user', act.db.user,
'-password', act.db.password, act.db.dsn],
stdout = f,
stderr = subprocess.STDOUT
)
for i in range(MAX_WAIT_FOR_ISQL_TERMINATE):
time.sleep(1)
# Check if child process has terminated.
# Set and return returncode attribute. Otherwise, returns None.
if p_handed_isql.poll() is not None:
break
finally:
p_handed_isql.terminate()
if p_handed_isql.poll() is None:
failed_finish_isql_msg = f'isql_outcome: process WAS NOT terminated in {MAX_WAIT_FOR_ISQL_TERMINATE} second. Probably MAX_WAIT_FOR_ISQL_TERMINATE value must be increased.'
# Get Firebird log after test
# ----------------------------
fb_log_curr = act.get_firebird_log()
with open(tmp_log, 'a') as f:
if failed_finish_isql_msg:
f.write(failed_finish_isql_msg+'\n')
for line in unified_diff(fb_log_init, fb_log_curr):
if line.startswith('+'):
f.write(line[1:] + '\n')
expected_stdout = f"""
rdb_trg_name TRG_ATTACH
rdb_trg_type 8192
rdb_trg_name TRG_DETACH
rdb_trg_type 8193
Error at disconnect:
Execute statement error at attach :
335544830 : Too many recursion levels of EXECUTE STATEMENT
Data source : Firebird::
At trigger 'TRG_DETACH'
Error at disconnect:
Execute statement error at attach :
335544830 : Too many recursion levels of EXECUTE STATEMENT
Data source : Firebird::
At trigger 'TRG_DETACH'
"""
with open(tmp_log, 'r') as f:
for line in f:
print(line)
act.expected_stdout = expected_stdout
act.stdout = capsys.readouterr().out
assert act.clean_stdout == act.clean_expected_stdout
act.reset()