6
0
mirror of https://github.com/FirebirdSQL/firebird-qa.git synced 2025-01-22 21:43:06 +01:00

Added/Updated tests\functional\tabloid\test_6cfc9b5d.py: Checked on 6.0.0.396, 5.0.1.1440 (both SS/CS). See notes.

This commit is contained in:
pavel-zotov 2024-07-21 00:01:46 +03:00
parent f5b7ac0ec1
commit e98d03db83

View File

@ -0,0 +1,410 @@
#coding:utf-8
"""
ID: issue-6cfc9b5d
ISSUE: https://github.com/FirebirdSQL/firebird/commit/6cfc9b5d7dc343295968cc4545c6fa184966cfc9
TITLE: Scrollable cursors. Automated test with randomly generated mission for positioning.
DESCRIPTION:
Test verifies commit "Fixed the remaining issues with scrollable cursor re-positioning" (06.12.2021 07:47).
We generate data (see usage of table 'TEST' and SP sp_generate_test_data), and this data (see TEST.BINARY_DATA)
can be one of following type:
* NULL in every record;
* be compressible with MAX ratio (string like 'AAA....AAA');
* be absolutely incompressible (UUID-based values).
Number of rows in the TEST table is defined by settings N_ROWS
Number of operations for change cursor position is defined by settings FETCH_OPERATIONS_TO_PERFORM
Then we perform miscelaneous operations that change position of scrollable cursor (on random basis).
These operations are stored in the table 'job_for_cursor_movement', see call of SP sp_make_job_for_cursor_movement.
Outcome of that: randomly generated operations are stored in the table 'move_operations'.
Result of positioning (value of obtained ID) is compared with expected one.
These actions are performed for all possible combinations of the following criteria:
* data compressibility (see 'a_suitable_for_compression')
* connection protocol ('local' or 'remote')
* value of WireCrypt parameter (Enabled or DIsabled)
NOTES:
[20.07.2024] pzotov
1. Values of TEST.ID must start with 1 rather rthan 0.
2. Custom driver-config object must be used for DPB because two values of WireCrypt parameter must be checked:
Enabled and Disabled (see 'w_crypt').
Also, we verify result for two protocols that are used for connection: local and remote (INET).
Checked on 6.0.0.396, 5.0.1.1440 (both SS/CS).
"""
import pytest
from firebird.qa import *
from firebird.driver import driver_config, connect, NetProtocol
import time
#########################
### S E T T I N G S ###
#########################
N_ROWS = 20
N_WIDTH = 32765
FETCH_OPERATIONS_TO_PERFORM = 100
#-------------------------
init_script = f"""
create or alter procedure sp_generate_test_data as begin end;
create or alter procedure sp_make_job_for_cursor_movement as begin end;
create or alter view v_fetch_expected_data as select 1 x from rdb$database;
create or alter view v_fetch_job as select 1 x from rdb$database;
recreate table move_operations(nm varchar(10));
insert into move_operations(nm) values('first');
insert into move_operations(nm) values('last');
insert into move_operations(nm) values('next');
insert into move_operations(nm) values('prior');
insert into move_operations(nm) values('absolute');
insert into move_operations(nm) values('relative');
commit;
recreate table job_for_cursor_movement(
rn int primary key
,nm varchar(10)
,arg_n int
,cnt int
,id_min int
,id_max int
,bof int
,eof int
,id_expected int
-- ,constraint job_valid check( nm in ('absolute', 'relative') and arg_n is not null )
)
;
---------------------
recreate table test( id int primary key, binary_data varchar( {N_WIDTH} ) character set octets );
comment on table test is q'#Textual column width was substitued from .py script, see variable 'N_WIDTH' there.#'
;
commit;
---------------------
create or alter view v_fetch_job as
with
i as (
select
count(*)+1 as cnt
,min(id) as id_min
,max(id) as id_max
from test
)
,a as (
select
f.nm
,iif(f.nm in ('absolute', 'relative'), 1, null) as def_sign
,i.cnt
,i.id_min
,i.id_max
,iif(f.nm = 'absolute', 0, -i.cnt) as rnd_min
,iif(f.nm = 'absolute', i.cnt, 2*i.cnt) as rnd_max
from move_operations f
cross join i
)
select
row_number()over(order by rand()) as rn
,a.nm
,a.def_sign * cast(a.rnd_min + rand() * a.rnd_max as int) as arg_n
,a.cnt
,a.id_min
,a.id_max
from a
cross join(select rand() as x from rdb$types a, rdb$types b rows 1000) b
;
----------------------------
create or alter view v_fetch_expected_data as
with recursive
r as (
select
rn
,cast(null as int) as id_expect_bak
,nm
,arg_n
,case nm
when 'first' then id_min
when 'last' then id_max
when 'prior' then bof
when 'next' then id_min
when 'relative' then iif( id_min + arg_n - 1 > id_max, eof, iif( id_min + arg_n <= id_min, bof, id_min + arg_n - 1 ) )
when 'absolute' then -- iif( arg_n > id_max, eof, iif( id_min + arg_n <=0, bof, id_min + arg_n ) )
case
when b.arg_n > b.id_max then b.eof
when b.arg_n > 0 then b.id_min + (b.arg_n-1)
when b.arg_n = 0 or b.arg_n < -b.cnt then b.bof
else -- b.arg_n between -b.cnt and -1
b.id_max + b.arg_n - 1
end
end as id_expected
,cnt
,id_min
,id_max
,bof
,eof
from job_for_cursor_movement b
where rn = 1
UNION ALL
select
x.rn
,r.id_expected as id_expect_bak
,x.nm
,x.arg_n
,case x.nm
when 'first' then r.id_min
when 'last' then r.id_max
when 'prior' then iif(r.id_expected = r.eof, r.id_max, iif( r.id_expected <= r.id_min, r.bof, r.id_expected - 1 ) )
when 'next' then iif( r.id_expected = r.bof, r.id_min, iif( r.id_expected >= r.id_max, r.eof, r.id_expected + 1 ) )
when 'relative' then -- iif( r.id_expected + x.arg_n > r.id_max, r.eof, iif( r.id_expected + x.arg_n < r.id_min, r.bof, r.id_expected + x.arg_n ) )
case
when r.id_expected = r.bof then iif(x.arg_n <= 0, r.bof, iif(x.arg_n >= r.cnt, r.eof, x.arg_n))
when r.id_expected = r.eof then iif(x.arg_n >= 0, r.eof, iif(abs(x.arg_n) >= r.cnt, r.bof, r.id_max + 1 - abs(x.arg_n)))
else iif( r.id_expected + x.arg_n > r.id_max, r.eof, iif( r.id_expected + x.arg_n < r.id_min, r.bof, r.id_expected + x.arg_n ) )
end
when 'absolute' then
case
when x.arg_n > r.id_max then r.eof
when x.arg_n > 0 then r.id_min + (x.arg_n-1)
when x.arg_n = 0 or x.arg_n < -r.cnt then r.bof
else -- x.arg_n between -r.cnt and -1
r.id_max + x.arg_n - 1
end
end as id_expected
,r.cnt
,r.id_min
,r.id_max
,r.bof
,r.eof
from r
join job_for_cursor_movement x on r.rn + 1 = x.rn
--where x.rn <= 3
)
select * from r
;
-----------------------------
set term ^;
create or alter procedure sp_make_job_for_cursor_movement(a_rows_to_add int = 20) as
declare cnt int;
declare bof int = -2147483648;
declare eof int = 2147483647;
begin
delete from job_for_cursor_movement;
select count(*) from test into cnt;
insert into job_for_cursor_movement(
rn,
nm,
arg_n,
cnt,
id_min,
id_max,
bof,
eof
)
select
rn,
nm,
arg_n,
cnt,
id_min,
id_max,
:bof,
:eof
from v_fetch_job
rows :a_rows_to_add
;
merge into job_for_cursor_movement t
using (select rn, id_expected from v_fetch_expected_data) s on t.rn = s.rn
when MATCHED then update set t.id_expected = s.id_expected
;
end
^
create or alter procedure sp_generate_test_data(a_suitable_for_compression smallint, a_rows int) as
declare i int = 1;
declare dummy int;
declare s varchar({N_WIDTH});
declare uuid_text varchar({N_WIDTH}) character set octets;
declare uuid_addi varchar(16) character set octets;
begin
delete from test;
select count(*) from test into dummy;
s = iif(a_suitable_for_compression = 0, null, lpad('', {N_WIDTH}, 'A'));
if (a_suitable_for_compression in (0,1) ) then
begin
while( i <= a_rows ) do
begin
insert into test(id, binary_data) values(:i, :s);
i = i + 1;
end
end
else
begin
-- generate NON-compressible data
while (i <= a_rows) do
begin
uuid_text = '';
uuid_addi = '';
while ( 1=1 ) do
begin
uuid_addi = gen_uuid();
if ( octet_length(uuid_text) < {N_WIDTH} - octet_length(uuid_addi) ) then
uuid_text = uuid_text || trim(uuid_addi);
else
begin
uuid_text = uuid_text || left(uuid_addi, {N_WIDTH} - octet_length(uuid_text));
leave;
end
end
insert into test(id,binary_data) values( :i, :uuid_text);
--execute statement ('insert into test(id,binary_data) values(?, ?)') ( :i, :uuid_text);
i = i + 1;
end
end
end
^
set term ;^
commit;
"""
db = db_factory(init = init_script, charset='win1251')
act = python_act('db')
#----------------------------
def print_row(row, cur = None):
if row:
print(f"{row[0]}")
if cur and (cur.is_bof() or cur.is_eof()):
print('### STRANGE BOF/EOR WHILE SOME DATA CAN BE SEEN ###')
else:
msg = '*** NO_DATA***'
if cur:
msg += ' BOF=%r EOF=%r' % ( cur.is_bof(), cur.is_eof() )
print(msg)
#----------------------------
@pytest.mark.scroll_cur
@pytest.mark.version('>=5.0.0')
def test_1(act: Action, capsys):
for a_suitable_for_compression in (0,1,2):
move_ops = {}
with act.db.connect() as con:
cur = con.cursor()
cur.callproc('sp_generate_test_data', (a_suitable_for_compression, N_ROWS))
con.commit()
cur.callproc('sp_make_job_for_cursor_movement', (FETCH_OPERATIONS_TO_PERFORM,))
con.commit()
cur.execute('select rn, nm, arg_n, id_expected from job_for_cursor_movement order by rn')
for r in cur:
move_ops[ r[0] ] = (r[1], '' if r[2] == None else r[2], r[3])
# result:
# table 'test' contains <N_ROWS> rows with data that has character appropriated to a_suitable_for_compression value
# table 'job_for_cursor_movement' contains <FETCH_OPERATIONS_TO_PERFORM> rows with randomly generated 'jobs' for cursor positioning.
# column job_for_cursor_movement.id_expected has EXPECTED values for ID after each position operation against table 'test'; we will compare ID with it.
# Example of move_ops:
# 1 : oper = fetch_last , id_expected: 19
# 2 : oper = fetch_first , id_expected: 0
# 3 : oper = fetch_relative(5) , id_expected: 5
# 4 : oper = fetch_relative(16) , id_expected: 2147483647
# ...
# 96 : oper = fetch_absolute(2) , id_expected: 1
# 97 : oper = fetch_prior , id_expected: 0
# 98 : oper = fetch_relative(5) , id_expected: 5
# 99 : oper = fetch_relative(-9) , id_expected: -2147483648
# 100 : oper = fetch_absolute(10) , id_expected: 9
srv_cfg = driver_config.register_server(name = f'test_6cfc9b5d_srv_{a_suitable_for_compression}', config = '')
for protocol_name in ('local', 'remote'):
for w_crypt in ('Enabled', 'Disabled'):
PASSED_MSG = f'{a_suitable_for_compression=}, {protocol_name=}, {w_crypt=}: PASSED.'
db_cfg_name = f'test_90129c6d_wcrypt_{a_suitable_for_compression}_{w_crypt}_{protocol_name}'
db_cfg_object = driver_config.register_database(name = db_cfg_name)
db_cfg_object.server.value = srv_cfg.name
db_cfg_object.protocol.value = None if protocol_name == 'local' else NetProtocol.INET
db_cfg_object.database.value = str(act.db.db_path)
db_cfg_object.config.value = f"""
WireCrypt = w_crypt
"""
details_lst = []
chk_retcode = 0
with connect(db_cfg_name, user = act.db.user, password = act.db.password) as con:
cur = con.cursor()
cur.open("select id, binary_data from test order by id")
for k,v in sorted(move_ops.items()):
crow = None
chk_val = None
f_kind = v[0].strip()
if f_kind == 'prior':
crow = cur.fetch_prior()
elif f_kind == 'next':
crow = cur.fetch_next()
elif f_kind == 'first':
crow = cur.fetch_first()
elif f_kind == 'last':
crow = cur.fetch_last()
elif f_kind == 'relative':
crow = cur.fetch_relative( v[1] )
elif f_kind == 'absolute':
crow = cur.fetch_absolute( v[1] )
wrong_found_msg = ''
if crow:
chk_val = crow[0]
if cur.is_bof() or cur.is_eof():
wrong_found_msg += ('One of BOF/EOF is true (BOF=%r, EOF=%r), but cursor returns data: %d' % (cur.is_bof(), cur.is_eof(), crow[0]))
else:
if cur.is_bof():
chk_val = -2147483648
elif cur.is_eof():
chk_val = 2147483647
else:
wrong_found_msg += '!! No data from cursor but also neither BOF nor EOF.'
msg = ''.join(
( '%5d ' % k
,'+++ passed +++' if wrong_found_msg == '' and v[2] == chk_val else '## MISMATCH ##'
,f'fetch oper: {f_kind},{v[1]}; id_expected: {v[2]}'
,f'; id_actual: {chk_val};'
,'BOF' if cur.is_bof() else ''
,'EOF' if cur.is_eof() else ''
)
)
details_lst.append(msg)
if not v[2] == chk_val or wrong_found_msg:
chk_retcode = 1
print(f'{a_suitable_for_compression=}, {protocol_name=}, {w_crypt=}: FAILED.')
for x in details_lst:
print(x)
break
if chk_retcode == 0:
print(PASSED_MSG)
else:
print('###############')
print('### F A I L ###')
print('###############')
act.expected_stdout = PASSED_MSG
act.stdout = capsys.readouterr().out
assert act.clean_stdout == act.clean_expected_stdout
act.reset()