diff --git a/tests/bugs/core_2341_test.py b/tests/bugs/core_2341_test.py index 44334648..dea82003 100644 --- a/tests/bugs/core_2341_test.py +++ b/tests/bugs/core_2341_test.py @@ -2,14 +2,14 @@ # # id: bugs.core_2341 # title: Hidden variables conflict with output parameters, causing assertions, unexpected errors or possibly incorrect results -# decription: +# decription: # tracker_id: CORE-2341 # min_versions: [] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None @@ -23,7 +23,7 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # test_script_1 #--- # c = db_conn.cursor() -# +# # cmd = c.prep("""execute block (i varchar(10) = ?) returns (o varchar(10)) # as # begin @@ -34,7 +34,8 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # c.execute(cmd,['asd']) # printData(c) #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """O ---------- @@ -42,8 +43,21 @@ asd """ @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action, capsys): + with act_1.db.connect() as con: + c = con.cursor() + cmd = c.prepare("""execute block (i varchar(10) = ?) returns (o varchar(10)) + as + begin + o = coalesce(cast(o as date), current_date); + o = i; + suspend; + end""") + c.execute(cmd, ['asd']) + act_1.print_data(c) + # + act_1.expected_stdout = expected_stdout_1 + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2408_test.py b/tests/bugs/core_2408_test.py index e39c9d6d..084ea928 100644 --- a/tests/bugs/core_2408_test.py +++ b/tests/bugs/core_2408_test.py @@ -2,25 +2,26 @@ # # id: bugs.core_2408 # title: isql -ex puts default values of sp parameters before the NOT NULL and COLLATE flags -# decription: +# decription: # Quote from ticket: "make a procedure with NOT NULL and/or COLLATE flags *and* a default value on any parameter". # Test enchances this by checking not only procedure but also function and package. # Also, check is performed for table (I've encountered the same for TABLES definition in some old databases). -# +# # Algorithm is similar to test for core-5089: we create several DB objects which do have properties from ticket. # Then we extract metadata and save it into file as 'initial' text. # After this we drop all objects and make attempt to APPLY just extracted metadata script. It should perform without errors. # Finally, we extract metadata again and do COMPARISON of their current content and those which are stored 'initial' file. -# +# # Checked on: WI-V3.0.0.32328 (SS/CS/SC). -# +# # tracker_id: CORE-2408 # min_versions: ['3.0'] # versions: 3.0 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from difflib import unified_diff +from firebird.qa import db_factory, python_act, Action # version: 3.0 # resources: None @@ -61,7 +62,7 @@ init_script_1 = """ suspend; end ^ - + create or alter function fn_test( p1 varchar(20) character set utf8 not null collate nums_coll default 'foo' ,p2 dm_test default 'qwe' @@ -134,23 +135,23 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # import subprocess # import time # import difflib -# +# # #----------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # os.fsync(file_handle.fileno()) -# +# # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -158,22 +159,22 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # os.remove( f_names_list[i] ) # if os.path.isfile( f_names_list[i]): # print('ERROR: can not remove file ' + f_names_list[i]) -# +# # #------------------------------------------- -# +# # db_file=db_conn.database_name # db_conn.close() -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # f_extract_initial_meta_sql = open( os.path.join(context['temp_directory'],'tmp_meta_2408_init.sql'), 'w') # subprocess.call( [ context['isql_path'], dsn, "-x", "-ch", "utf8" ], # stdout = f_extract_initial_meta_sql, # stderr = subprocess.STDOUT # ) # flush_and_close( f_extract_initial_meta_sql ) -# +# # ddl_clear_all=''' # drop package pg_test; # drop function fn_test; @@ -184,85 +185,108 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # drop collation nums_coll; # commit; # ''' -# +# # f_meta_drop_all_sql = open( os.path.join(context['temp_directory'],'tmp_meta_2408_drop_all.sql'), 'w') # f_meta_drop_all_sql.write(ddl_clear_all) # flush_and_close( f_meta_drop_all_sql ) -# +# # f_meta_drop_all_log = open( os.path.join(context['temp_directory'],'tmp_meta_2408_drop_all.log'), 'w') # subprocess.call( [ context['isql_path'], dsn, "-i", f_meta_drop_all_sql.name, "-ch", "utf8" ], # stdout = f_meta_drop_all_log, # stderr = subprocess.STDOUT # ) # flush_and_close( f_meta_drop_all_log ) -# -# +# +# # f_apply_extracted_meta_log = open( os.path.join(context['temp_directory'],'tmp_meta_2408_apply.log'), 'w') # subprocess.call( [ context['isql_path'], dsn, "-i", f_extract_initial_meta_sql.name, "-ch", "utf8" ], # stdout = f_apply_extracted_meta_log, # stderr = subprocess.STDOUT # ) # flush_and_close( f_apply_extracted_meta_log ) -# +# # f_extract_current_meta_sql = open( os.path.join(context['temp_directory'],'tmp_meta_2408_last.sql'), 'w') # subprocess.call( [ context['isql_path'], dsn, "-x", "-ch", "utf8"], # stdout = f_extract_current_meta_sql, # stderr = subprocess.STDOUT # ) # flush_and_close( f_extract_current_meta_sql ) -# -# +# +# # f_diff_txt=open( os.path.join(context['temp_directory'],'tmp_2408_meta_diff.txt'), 'w') -# +# # f_old=[] # f_new=[] -# +# # f_old.append(f_extract_initial_meta_sql) # tmp_meta_2408_init.sql -- extracted metadata just after 'init_script' was done # f_new.append(f_extract_current_meta_sql) # tmp_meta_2408_last.sql -- extracted metadata after drop all object and applying 'tmp_meta_2408_init.sql' -# +# # for i in range(len(f_old)): # old_file=open(f_old[i].name,'r') # new_file=open(f_new[i].name,'r') -# +# # f_diff_txt.write( ''.join( difflib.unified_diff( old_file.readlines(), new_file.readlines() ) ) ) -# +# # old_file.close() # new_file.close() -# +# # flush_and_close( f_diff_txt ) -# -# +# +# # # Should be EMPTY: # ################## # with open( f_meta_drop_all_log.name, 'r') as f: # for line in f: # print( 'Error log of dropping existing objects: ' + f.line() ) -# +# # # Should be EMPTY: # ################## # with open( f_apply_extracted_meta_log.name, 'r') as f: # for line in f: # print( 'Error log of applying extracted metadata: ' + f.line() ) -# +# # # Should be EMPTY: # ################## # with open( f_diff_txt.name,'r') as f: # for line in f: # print( ' '.join(line.split()).upper() ) -# +# # ############################### # # Cleanup. # time.sleep(1) # cleanup( [ i.name for i in (f_extract_initial_meta_sql,f_extract_current_meta_sql,f_meta_drop_all_sql,f_meta_drop_all_log,f_apply_extracted_meta_log,f_diff_txt) ] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action): + # Extract metadata + act_1.isql(switches=['-x']) + initial_meta = act_1.stdout + # Clear all + ddl_clear_all = ''' + drop package pg_test; + drop function fn_test; + drop procedure sp_test; + drop table test; + drop domain dm_test; + drop collation name_coll; + drop collation nums_coll; + commit; + ''' + act_1.reset() + act_1.isql(switches=[], input=ddl_clear_all) + # Recreate metadata + act_1.reset() + act_1.isql(switches=[], input=initial_meta) + # Extract metadata again + act_1.reset() + act_1.isql(switches=['-x']) + current_meta = act_1.stdout + # Compare metadata + meta_diff = '\n'.join(unified_diff(initial_meta.splitlines(), current_meta.splitlines())) + assert meta_diff == '' diff --git a/tests/bugs/core_2420_test.py b/tests/bugs/core_2420_test.py index 532e6063..76c1b1e1 100644 --- a/tests/bugs/core_2420_test.py +++ b/tests/bugs/core_2420_test.py @@ -2,14 +2,15 @@ # # id: bugs.core_2420 # title: Parsing error in EXECUTE STATEMENT with named parameters -# decription: +# decription: # tracker_id: CORE-2420 # min_versions: [] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action +from firebird.driver import DatabaseError # version: 2.5 # resources: None @@ -50,16 +51,35 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # do exit; # end""") # print ('Execution OK') -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) -expected_stdout_1 = """Execution OK -""" +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + with act_1.db.connect() as con: + c = con.cursor() + # Test fails if next raises an exception + try: + c.execute("""execute block as + declare datedoc date; + declare cod int; + declare num int; + declare name int; + declare summa int; + begin + for execute statement ( + ' select s.cod,s.num, s.name,sum(g.summa) from sch s + left join getschdet(s.cod,:datedoc ,:datedoc,0,0,0,0,0,0,0,0,0,0,0,1,3) g on 1=1 + where s.num in (''50'',''51'') and s.udl<>''У'' and s.root=1 + and not exists (select s2.cod from sch s2 where s2.prevcod=s.cod) + group by 1,2,3') (datedoc := :datedoc) + into :cod, :num, :name, :summa + do exit; + end""") + except DatabaseError as exc: + pytest.fail(f"SQL execution failed with: {str(exc)}") + diff --git a/tests/bugs/core_2441_test.py b/tests/bugs/core_2441_test.py index b4a31c84..59ca9d79 100644 --- a/tests/bugs/core_2441_test.py +++ b/tests/bugs/core_2441_test.py @@ -2,14 +2,16 @@ # # id: bugs.core_2441 # title: Server crashes on UPDATE OR INSERT statement -# decription: +# decription: # tracker_id: CORE-2441 # min_versions: [] # versions: 2.1.3 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +import datetime +from firebird.qa import db_factory, python_act, Action +from firebird.driver import DatabaseError # version: 2.1.3 # resources: None @@ -31,14 +33,17 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # c.execute("""UPDATE OR INSERT INTO TABLE_TXT (FIELD1) # VALUES (CAST(? AS TIMESTAMP)) # MATCHING(FIELD1)""",[datetime.datetime(2011,5,1)]) -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=2.1.3') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + with act_1.db.connect() as con: + c = con.cursor() + c.execute("""UPDATE OR INSERT INTO TABLE_TXT (FIELD1) + VALUES (CAST(? AS TIMESTAMP)) + MATCHING(FIELD1)""", [datetime.datetime(2011,5,1)]) diff --git a/tests/bugs/core_2475_test.py b/tests/bugs/core_2475_test.py index a1fe028a..d5946942 100644 --- a/tests/bugs/core_2475_test.py +++ b/tests/bugs/core_2475_test.py @@ -4,7 +4,7 @@ # title: External table data not visible to other sessions in Classic # decription: In 2.1.2 SuperServer, any data written to external tables are visible to other sessions. # However in Classic, this data is not visible. It seems to be cached and written to file eventually, when this happens it becomes visible. -# +# # THIS TEST WILL END WITH ERROR IF EXTERNAL TABLE ACCESS IS NOT ALLOWED, WHICH IS BY DEFAULT. It's necessary to adjust firebird.conf. # tracker_id: CORE-2475 # min_versions: ['2.1.3'] @@ -12,16 +12,15 @@ # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from pathlib import Path +from firebird.qa import db_factory, python_act, Action, temp_file # version: 2.1.3 # resources: None substitutions_1 = [] -init_script_1 = """create table EXT1 external file '$(DATABASE_LOCATION)EXT1.TBL' -(PK INTEGER); -""" +init_script_1 = """""" db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) @@ -30,37 +29,45 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # # init # import os # ext_filename = '%sEXT1.TBL' % context[db_path_property] -# +# # # session A # c1 = db_conn.cursor() # c1.execute("insert into EXT1 (PK) values (1)") -# +# # db_conn.commit() -# +# # # session B # con2 = kdb.connect(dsn=dsn,user=user_name,password=user_password) # c2 = con2.cursor() # c2.execute('select * from EXT1') # printData(c2) -# +# # # cleanup # con2.close() # try: # os.remove(ext_filename) # except: # print("Error while removing external table file") -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) -expected_stdout_1 = """PK ------------ -1 -""" +act_1 = python_act('db_1', substitutions=substitutions_1) + +external_table = temp_file('EXT1.TBL') @pytest.mark.version('>=2.1.3') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action, external_table: Path): + # Create external table + act_1.isql(switches=[], + input=f"create table EXT1 external file '{str(external_table)}' (PK INTEGER); exit;") + # session A + with act_1.db.connect() as con: + c = con.cursor() + c.execute("insert into EXT1 (PK) values (1)") + con.commit() + # session B + with act_1.db.connect() as con: + c = con.cursor() + c.execute('select * from EXT1') + result = c.fetchall() + assert result == [(1, )] diff --git a/tests/bugs/core_2477_test.py b/tests/bugs/core_2477_test.py index e59d0ff1..b85fa7e8 100644 --- a/tests/bugs/core_2477_test.py +++ b/tests/bugs/core_2477_test.py @@ -2,32 +2,32 @@ # # id: bugs.core_2477 # title: mon$memory_usage: Sorting memory should be reported as owned by the statement -# decription: +# decription: # We create view that gathers monitoring info related to all needed levels of statistics (DB, attachment, transaction, call). # Then this view is "customized" in order to leave only interested info about activity that will be started by separate isql process. # Then we start ascynchronously ISQL and make it stay in some (small) pause. At this moment we make first "photo" of mon$ info and store # it in dict object 'map_beg'. # NB: we should NOT wait too long because when SORT starts it can very fast to fill whole space of TempCacheLimit and mon$memory* counters # will not change since this poitn at all (===> one mey to get zero difference between mon$ countyers in this case). -# +# # After small pause (in ISQL connect) will gone, ISQL starts to do "huge sort" by invoking query with 'SELECT DISTINCT FROM '. -# We wait about 1..2 second after this sort start and then make 2nd "photo" of monitoring counters and also store their values in another +# We wait about 1..2 second after this sort start and then make 2nd "photo" of monitoring counters and also store their values in another # dict object ('map_end'). -# +# # Finally, we force ISQL to finish (by moving DB in full shutdown state) and compare differences between corresp. values of map_end and map_beg. # Values for DATABASE level (mon$stat_group = 0) change only in SuperServer but never change in CS/SC and remain zero. We do not compare them. # Values for TRANSACTION level never increase; moreover, mon$memory_allocated counter at the "end-point" (when sorting is running) even is reduced # (and the reason still remain unknown for me; see letter to dimitr 04-may-2018 20:07). # So, we compare only difference of mon$memory* counters for ATTACHMENT and STATEMENT level (mon$stat_group = 1 and 3). -# +# # This difference must be not less than some threshold that depends on FB arch, for __BOTH__ levels (and this is main idea of this test) # ################### -# +# # Runs this test on firebird.conf with default TempCacheLimit show following values of differences: -# 1) for SuperServer: ~68.1 Mb; +# 1) for SuperServer: ~68.1 Mb; # 2) for Classic: ~9.4 Mb # For this reason minimal threshold for consider difference Ok is about 1 Mb (see MIN_DIFF_THRESHOLD). -# +# # Checked on (Windows 32 bit): # 25SC, build 2.5.9.27107: OK, 10.328s. # 25sS, build 2.5.8.27056: OK, 14.656s. @@ -35,7 +35,10 @@ # 30SS, build 3.0.4.32963: OK, 17.234s. # 40CS, build 4.0.0.955: OK, 11.219s. # 40SS, build 4.0.0.967: OK, 12.718s. -# +# +# [pcisar] 16.11.2021 +# This test is too complicated and fragile, and it's IMHO not worth to be implemented +# # tracker_id: CORE-2477 # min_versions: ['2.5.0'] # versions: 2.5 @@ -53,7 +56,7 @@ init_script_1 = """ create or alter view v_mon as select * from ( - select + select m.mon$stat_group as stat_gr ,rpad( decode(m.mon$stat_group, 0,'0:database', 1,'1:attachment', 2,'2:transaction', 3,'3:statement', 4,'4:call'), 15,' ') as stat_type ,m.mon$memory_used as memo_used @@ -67,24 +70,24 @@ init_script_1 = """ ,coalesce( decode( s.mon$state, 0,'finished', 1,'running', 2,'suspended' ), 'n/a') as stm_state ,lower(right( coalesce(trim(coalesce(a.mon$remote_process, a.mon$user)), ''), 20 )) as att_process -- isql.exe or Garbace Collector or Cache Writer ,lower(left( coalesce(cast(s.mon$sql_text as varchar(2000)),''), 50 )) as sql_text - from mon$memory_usage m + from mon$memory_usage m left join mon$statements s on m.mon$stat_group = 3 and m.mon$stat_id = s.mon$stat_id - left join mon$transactions t on + left join mon$transactions t on m.mon$stat_group = 2 and m.mon$stat_id = t.mon$stat_id or m.mon$stat_group = 3 and m.mon$stat_id = s.mon$stat_id and t.mon$transaction_id = s.mon$transaction_id - left join mon$attachments a on - m.mon$stat_group = 1 and m.mon$stat_id = a.mon$stat_id + left join mon$attachments a on + m.mon$stat_group = 1 and m.mon$stat_id = a.mon$stat_id or m.mon$stat_group=2 and m.mon$stat_id = t.mon$stat_id and a.mon$attachment_id = t.mon$attachment_id or m.mon$stat_group=3 and m.mon$stat_id = s.mon$stat_id and a.mon$attachment_id = s.mon$attachment_id - where - s.mon$sql_text is null - or + where + s.mon$sql_text is null + or -- NB: There is additional statement like "SELECT RDB$MAP_USING, RDB$MAP_PLUGIN ..." in 4.0! -- We have to filter it out and leave here only "our" statement that does SORT job: s.mon$sql_text containing 'distinct' ) t - where - t.stat_gr = 0 + where + t.stat_gr = 0 or t.att_process similar to '%[\\/]isql(.exe){0,1}' order by stat_type, stat_id; @@ -98,55 +101,55 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import subprocess # from subprocess import Popen # import time # from fdb import services -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # db_file=db_conn.database_name -# +# # db_conn.close() -# +# # ISQL_USER=user_name # ISQL_PSWD=user_password -# +# # MIN_DIFF_THRESHOLD=1000000 -# +# # # change on True if one need to look at intermediate results of gathering mon$ info # # (before ISQL async launch; after launch but before starting sort; while sorting) # RUN_DBG=False -# +# # DELAY_IN_ISQL_BEFORE_IT_STARTS_SORT = 3 # DELAY_FOR_ISQL_ESTABLISH_ITS_CONNECT = 1 # DELAY_BEFORE_MON_WHILE_SORT_IS_RUNNING = DELAY_IN_ISQL_BEFORE_IT_STARTS_SORT + DELAY_FOR_ISQL_ESTABLISH_ITS_CONNECT + 1 -# +# # SQL_GATHER_SORT_INFO=''' -# select -# v.att_process, -# replace(replace(replace(replace(v.sql_text, ascii_char(10),' '), ascii_char(13),' '),' ',' '),' ',' ') as sql_text, -# v.stat_type, -# v.stm_state, -# v.att_id, -# v.trn_id, -# v.sttm_id, -# v.memo_used, -# v.memo_allo, -# v.max_memo_used, -# v.max_memo_allo -# from v_mon v +# select +# v.att_process, +# replace(replace(replace(replace(v.sql_text, ascii_char(10),' '), ascii_char(13),' '),' ',' '),' ',' ') as sql_text, +# v.stat_type, +# v.stm_state, +# v.att_id, +# v.trn_id, +# v.sttm_id, +# v.memo_used, +# v.memo_allo, +# v.max_memo_used, +# v.max_memo_allo +# from v_mon v # where v.att_id is distinct from current_connection # ''' -# +# # #-------------------------------------------------------------- -# +# # def result_msg(a_diff_value, a_min_threshold): # return ( ('OK, expected: increased significantly.') if a_diff_value > a_min_threshold else ('BAD! Did not increased as expected. Difference: ' + "{:d}".format(a_diff_value)+'.') ) -# +# # def debug_store_mon_view(dsn, SQL_GATHER_SORT_INFO, file_name_suffix): # global os # global subprocess @@ -157,14 +160,14 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # subprocess.call( [ context['isql_path'], dsn, '-q', '-n', '-i', f_sql_dbg.name ], stdout=f_log_dbg, stderr = subprocess.STDOUT) # f_log_dbg.close() # os.remove(f_sql_dbg.name) -# +# # def forcely_clean_attachments_by_shutdown_online( db_file ): -# +# # global RUN_DBG # global os -# +# # f_shutdown_log = open( os.path.join(context['temp_directory'],'tmp_shutdown_and_online_2477.log'), 'w') -# +# # subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr", # "action_properties", "prp_shutdown_mode", "prp_sm_full", "prp_shutdown_db", "0", # "dbname", db_file, @@ -172,7 +175,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout = f_shutdown_log, # stderr = subprocess.STDOUT # ) -# +# # subprocess.call( [context['fbsvcmgr_path'],"localhost:service_mgr", # "action_db_stats", # "dbname", db_file, "sts_hdr_pages" @@ -180,7 +183,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout = f_shutdown_log, # stderr=subprocess.STDOUT # ) -# +# # subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr", # "action_properties", "prp_db_online", # "dbname", db_file, @@ -195,16 +198,16 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout = f_shutdown_log, # stderr=subprocess.STDOUT # ) -# +# # f_shutdown_log.close() -# +# # if not RUN_DBG: # os.remove(f_shutdown_log.name) -# -# +# +# # #-------------------------------------------------------- -# -# +# +# # sql_text=''' # commit; # set transaction lock timeout %(DELAY_IN_ISQL_BEFORE_IT_STARTS_SORT)s; @@ -218,15 +221,15 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # on external 'localhost:' || rdb$get_context('SYSTEM','DB_NAME') # as user '%(ISQL_USER)s' password '%(ISQL_PSWD)s' # ; -# when any do -# begin +# when any do +# begin # end # end -# -# select count(*) -# from ( +# +# select count(*) +# from ( # -- this force to use "PLAN SORT": -# select distinct lpad('', 500, uuid_to_char(gen_uuid())) s from rdb$types a,rdb$types b, rdb$types c +# select distinct lpad('', 500, uuid_to_char(gen_uuid())) s from rdb$types a,rdb$types b, rdb$types c # ) # into c; # suspend; @@ -234,73 +237,73 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # ^ # set term ;^ # ''' %locals() -# -# +# +# # f_isql_cmd=open( os.path.join(context['temp_directory'],'tmp_2477_sort.sql'), 'w') # f_isql_cmd.write(sql_text) # f_isql_cmd.close() -# +# # if RUN_DBG: # debug_store_mon_view(dsn, SQL_GATHER_SORT_INFO, '0') -# -# +# +# # # Launch async-ly ISQL which must establish connect and: # # 1) stay in small delay # # 2) start "big sorting" job (for at least 20-30 seconds): -# +# # f_log_sort=open( os.path.join(context['temp_directory'], 'tmp_2477_sort.log'), 'w') # p_sql = subprocess.Popen( [ context['isql_path'], dsn, '-q', '-n', '-i', f_isql_cmd.name ], stdout=f_log_sort, stderr = subprocess.STDOUT) -# +# # # do NOT remove this delay: we have to wait while ISQL for sure will establish connection. # ########################## # time.sleep( DELAY_FOR_ISQL_ESTABLISH_ITS_CONNECT ) -# -# +# +# # if RUN_DBG: # # NOTE: assign RUN_DBG to True and look in debug snapshot file tmp_c2477_dbg1.log # # with results of 1st gathering of mon$ info. If it will contain only one record (of DB level) # # than it means that we have to increase DELAY_FOR_ISQL_ESTABLISH_ITS_CONNECT value): # debug_store_mon_view(dsn, SQL_GATHER_SORT_INFO, '1') -# -# +# +# # # Start separate connect for gather monio # con_mon=fdb.connect(dsn=dsn) # cur_mon=con_mon.cursor() -# +# # # Gather info from mon$memory_usage before SORT start (ISQL stays in pause now): # ################################### # cur_mon.execute(SQL_GATHER_SORT_INFO) -# +# # map_beg={} -# +# # # Take at once all the records that cursor can return (actually it can return only 5 records in 3.0+ SS): # for x in cur_mon.fetchmanymap(99): # # we need only several for storing as KEY-VALUE pairs from the whole set of columns of view v_mon: # (stat_type, att_id, trn_id, sttm_id) = ( v for k,v in x.items() if k in ( 'STAT_TYPE', 'ATT_ID', 'TRN_ID', 'STTM_ID') ) # val = [ v for k,v in x.items() if k in ('MEMO_USED', 'MEMO_ALLO') ] -# +# # map_beg[ stat_type, att_id, trn_id, sttm_id ] = val -# +# # #for k,v in sorted(map_beg.items()): # # print('::: beg ::: k=',k,'; v=',v) -# -# +# +# # cur_mon.close() -# +# # # This is mandatory before any subsequent gathering mon$ info: # con_mon.commit() -# +# # # This *seems* not necessary but one need to test this again in SC/CS: # con_mon.close() # con_mon=fdb.connect(dsn=dsn) -# +# # # this delay is mandatory and must be greater than delay in ISQL # # (see 'set tran lock timeout N' in its sql script). # # We have to give ISQL to actually start SORT job: # time.sleep( DELAY_BEFORE_MON_WHILE_SORT_IS_RUNNING ) -# +# # cur_mon=con_mon.cursor() -# +# # # Gather info from mon$memory_usage when SORT is running: # ################################### # cur_mon.execute(SQL_GATHER_SORT_INFO) @@ -308,66 +311,66 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # for x in cur_mon.fetchmanymap(99): # (stat_type, att_id, trn_id, sttm_id) = ( v for k,v in x.items() if k in ( 'STAT_TYPE', 'ATT_ID', 'TRN_ID', 'STTM_ID') ) # val = [ v for k,v in x.items() if k in ('MEMO_USED', 'MEMO_ALLO') ] -# +# # map_end[ stat_type, att_id, trn_id, sttm_id ] = val -# +# # cur_mon.close() -# +# # if RUN_DBG: # # NOTE: assign RUN_DBG to True and look in debug snapshot file tmp_c2477_dbg1.log # # with results of 1st gathering of mon$ info. # debug_store_mon_view(dsn, SQL_GATHER_SORT_INFO, '2') -# +# # con_mon.close() -# +# # # We have to be sure that NO ANY activity remains in the database before finish this test. # # Unfortunately, it seems that just killing process of ISQL (that was launched async-ly) not enough, # # so we turn database offline and bring back online: # forcely_clean_attachments_by_shutdown_online( db_file ) -# +# # # ::: !! ::: # ######################################## # # TERMINATE ISQL THAT DOES HUGE SORT JOB # ######################################## # p_sql.terminate() # f_log_sort.close() -# +# # #time.sleep(1) -# +# # for k,v in sorted(map_beg.items()): -# +# # if 'database' in k[0]: # # mon$memory_* counters always ZERO in CS/SC for database level # pass -# +# # if 'transaction' in k[0]: # # mon$memory_* counters never change for transaction level (reason currently is unknown). # pass -# +# # if 'attachment' in k[0] or 'statement' in k[0]: -# +# # (beg_memo_used, beg_memo_allo) = v -# +# # (end_memo_used, end_memo_allo) = map_end.get(k) # (dif_memo_used, dif_memo_allo) = (end_memo_used - beg_memo_used, end_memo_allo - beg_memo_allo) -# +# # #print( k[0].rstrip()+':' ) # # 4debug: output value of mon$memory* counters difference: # #print( ' '.join( (' * DELTA of mon$memory_used:', "{:9d}".format(dif_memo_used), result_msg(dif_memo_used, MIN_DIFF_THRESHOLD) ) ) ) # #print( ' '.join( (' * DELTA of mon$memory_allo:', "{:9d}".format(dif_memo_allo), result_msg(dif_memo_allo, MIN_DIFF_THRESHOLD) ) ) ) -# +# # print( ' '.join( k[0].split(':') ).rstrip() ) # print( ' * DELTA of mon$memory_used: ' + result_msg(dif_memo_used, MIN_DIFF_THRESHOLD) ) # print( ' * DELTA of mon$memory_allo: ' + result_msg(dif_memo_allo, MIN_DIFF_THRESHOLD) ) -# +# # # cleanup: # ########## # time.sleep(1) # f_list = (f_isql_cmd, f_log_sort) # for f in f_list: # os.remove(f.name) -# -# +# +# #--- #act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) diff --git a/tests/bugs/core_2484_test.py b/tests/bugs/core_2484_test.py index c3e41960..e3578e7c 100644 --- a/tests/bugs/core_2484_test.py +++ b/tests/bugs/core_2484_test.py @@ -2,61 +2,66 @@ # # id: bugs.core_2484 # title: Success message when connecting to tiny trash database file -# decription: +# decription: # We make invalid FDB file by creating binary file and write small data piece to it. # Then we try to connect to such "database" using ISQL with passing trivial command # like 'select current_timestamp' for execution. # ISQL must raise error and quit (obviously without any result to STDOUT). -# +# # ::: NB ::: # If Windows with non-ascii language is used then message about overlapped IO # ("-Overlapped I/O operation is in progress") will be translated by OS to localized # text and it will be displayed in STDERR. This message must be suppressed or ignored. -# +# # Because of this, it was decided to redirect output of ISQL to logs and open # them using codecs.open() with errors='ignore' option. # We check presense of error message in STDERR file created by ISQL. # It is ennough to verify that STDERR log contains pattern 'SQLSTATE = '. # Checked on 4.0.0.2164; 3.0.7.33356 -# +# # 02-mar-2021. Re-implemented in order to have ability to run this test on Linux. -# +# # NOTE: message that clearly points to the reason of failed connection is shown # only on Linux FB 3.x: # ===== # Statement failed, SQLSTATE = 08004 # file <...>/tmp_2484_fake.fdb is not a valid database # ===== -# +# # All other cases produce SQLSTATE = 08001 and somewhat strange after it (from my POV): # ===== # Windows: # I/O error during "ReadFile" operation for file "<...>\\TMP_2484_FAKE.FDB" # -Error while trying to read from file -# +# # Linux: # I/O error during "read_retry" operation for file "<...>/tmp_2484_fake.fdb" # -Error while trying to read from file # -Success <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< :-) # ===== -# +# # Checked on: # * Windows: 4.0.0.2377, 3.0.8.33420 -- both on SS/CS # * Linux: 4.0.0.2377, 3.0.8.33415 -# -# +# +# # tracker_id: CORE-2484 # min_versions: ['3.0'] # versions: 3.0 -# qmid: +# qmid: import pytest -from firebird.qa import db_factory, isql_act, Action +from pathlib import Path +from firebird.qa import db_factory, python_act, Action, temp_file # version: 3.0 # resources: None -substitutions_1 = [('SQLSTATE = 08004', 'SQLSTATE = 08001')] +substitutions_1 = [('SQLSTATE = 08004', 'SQLSTATE = 08001'), + ('I/O error during.*', ''), + ('-File size is less than expected', ''), + ('-Error while.*', ''), + ('Use CONNECT or CREATE DATABASE to specify a database', '')] init_script_1 = """""" @@ -64,34 +69,34 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import subprocess # import codecs # import re # import time -# +# # db_conn.close() # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # #-------------------------------------------- -# +# # def flush_and_close( file_handle ): # # https://docs.python.org/2/library/os.html#os.fsync # # If you're starting with a Python file object f, # # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb') and file_handle.name != os.devnull: # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -102,20 +107,20 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # else: # print('Unrecognized type of element:', f_names_list[i], ' - can not be treated as file.') # del_name = None -# +# # if del_name and os.path.isfile( del_name ): # os.remove( del_name ) -# +# # #-------------------------------------------- -# +# # f_fake_fdb = open( os.path.join(context['temp_directory'],'tmp_2484_fake.fdb'), 'wb') # f_fake_fdb.write('ŒåŁä') # flush_and_close( f_fake_fdb ) -# +# # f_fake_sql = open( os.path.splitext(f_fake_fdb.name)[0]+'.sql', 'w') # f_fake_sql.write('set heading off; select current_timestamp from rdb$database; quit;') # flush_and_close( f_fake_sql ) -# +# # f_fake_log = open( os.path.splitext(f_fake_fdb.name)[0]+'.log', 'w') # f_fake_err = open( os.path.splitext(f_fake_fdb.name)[0]+'.err', 'w') # subprocess.call( [ context['isql_path'], 'localhost:' + f_fake_fdb.name, "-i", f_fake_sql.name ], @@ -124,7 +129,7 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # ) # flush_and_close( f_fake_log ) # flush_and_close( f_fake_err ) -# +# # ########################################################################### # # Linux, FB 3.x: # # Statement failed, SQLSTATE = 08004 @@ -133,7 +138,7 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # # Statement failed, SQLSTATE = 08001 # # I/O error during "ReadFile" operation for file "<...>\\TMP_2484_FAKE.FDB" # # -Error while trying to read from file -# +# # # Linux, FB 4.x: # # Statement failed, SQLSTATE = 08001 # # I/O error during "read_retry" operation for file "<...>/tmp_2484_fake.fdb" @@ -145,34 +150,43 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # # -Error while trying to read from file # # Overlapped I/O operation is in progress << WILL BE IN LOCALIZED FORM! # ########################################################################### -# +# # p = re.compile('SQLSTATE\\s+=\\s+',re.IGNORECASE) # with codecs.open( filename = f_fake_err.name, mode = 'r', errors = 'ignore') as f: # for line in f: # if p.search(line): # print(line) -# +# # with codecs.open( filename = f_fake_log.name, mode = 'r', errors = 'ignore') as f: # for line in f: # if p.search(line): # print('UNEXPECTED STDOUT: ' + line) -# +# # # cleanup: # ########## # time.sleep(1) # cleanup( ( f_fake_fdb, f_fake_sql, f_fake_log, f_fake_err ) ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) -expected_stdout_1 = """ - Statement failed, SQLSTATE = 08001 - """ +act_1 = python_act('db_1', substitutions=substitutions_1) + +expected_stderr_1 = """ +Statement failed, SQLSTATE = 08001 +""" + +fake_db = temp_file('tmp_2484_fake.fdb') @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action, fake_db: Path): + fake_db.write_bytes(bytes([1, 2, 3, 4, 5, 6, 0])) + act_1.expected_stdout = '' + act_1.expected_stderr = expected_stderr_1 + act_1.isql(switches=['-user', act_1.db.user, '-password', act_1.db.password, str(fake_db)], + connect_db=False, + input='set heading off; select current_timestamp from rdb$database; quit;') + assert act_1.clean_stderr == act_1.clean_expected_stderr + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2531_test.py b/tests/bugs/core_2531_test.py index 9b765f1e..45470521 100644 --- a/tests/bugs/core_2531_test.py +++ b/tests/bugs/core_2531_test.py @@ -2,59 +2,61 @@ # # id: bugs.core_2531 # title: The famous "cannot transliterate" error may be thrown when selecting data from the monitoring tables -# decription: +# decription: # In order to check issues of ticket we have to create Python script which is encoded in non-ascii codepage and also # its codepage should be NON utf8 (choosed: Windows 1252). This is because all .fbt files have to be encoded only in UTF8, # so we can not put inside .fbt a statement which contains non-ascii SINGLE-BYTE characters. -# +# # Character |å|, "LATIN SMALL LETTER A WITH RING ABOVE", was selected in order to verify ticket issue, see: # http://www.fileformat.info/info/unicode/char/00e5/index.htm -# +# # Temporary Python file ("tmp_2531_run.py") will contain encoding statement ('# coding: latin-1') followed by commands for: # 1) make first attachment to database, by Python itself, with charset = Win1252, with preparing: "select 'gång' from rdb$database"; # also, this attachments SAVES string of this query into table (NOTE: it works with charset = win1252); # 2) make second attachment by ISQL, with charset = utf8, which will query mon$statements.mon$sql_text - this should return query # which has been prepared by first attachment. This query is compared to that which was stored into table by attachment-1, # and these rows should be equal: |select 'gång' from rdb$database| -# +# # Confirmed wrong (incompleted) output of mon$sql_text on 2.1.0.17798 (but there is no "cannot transliterate" error): # === -# select 'gång: ' || current_timestamp from rdb$database -# select 'g +# select 'gång: ' || current_timestamp from rdb$database +# select 'g # ^^^ # | # +--- three white-spaces here (after 'g') -# === +# === # No such effect on builds >= 2.1.3.18185. -# +# # Refactored 08-may-2017: replaced buggy "folder.replace()" with bulk of backslashes ("") with locals() usage. -# Checked again on Classic for: WI-V3.0.2.32708, WI-T4.0.0.633 +# Checked again on Classic for: WI-V3.0.2.32708, WI-T4.0.0.633 # (added expression to WHERE-block to filter out record from mon$statements with RDB$AUTH_MAPPING data). -# +# # 02-MAR-2021: # Changed code in order to remove dependency on PATH-list (CLI utilities must be invoked using context['..._path']) # Full path+name of FB client library file is passed to generated Python code. # Removed unneeded variables. -# +# # Checked on Windows: 4.0.0.2377 (SS/CS), 3.0.8.33420 (SS/CS), 2.5.9.27152 (SC) # Checked on Linux: 4.0.0.2377, 3.0.8.33415. -# +# # tracker_id: CORE-2531 # min_versions: ['2.5.0'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None substitutions_1 = [('SQL_TEXT_BLOB_ID .*', ''), ('[\t ]+', ' ')] -init_script_1 = """""" +init_script_1 = """ +recreate table non_ascii(stored_sql_expr varchar(255) character set win1252); +""" -db_1 = db_factory(sql_dialect=3, init=init_script_1) +db_1 = db_factory(sql_dialect=3, init=init_script_1, charset='WIN1252') # test_script_1 #--- @@ -64,18 +66,16 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import time # import codecs # from fdb import services -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password # engine = db_conn.engine_version # db_conn.close() -# +# # svc = services.connect() -# FB_HOME = os.path.normpath( svc.get_home_directory() ) # 'c: -# irebird' --> 'c: -# irebird' (i.e. remove trailing backslash if needed) +# FB_HOME = os.path.normpath( svc.get_home_directory() ) # 'c:\\firebird\\' --> 'c:\\firebird' (i.e. remove trailing backslash if needed) # svc.close() -# +# # FB_CLNT = '' # if os.name == 'nt': # # For Windows we assume that client library is always in FB_HOME dir: @@ -87,24 +87,24 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # # For Linux client library will be searched in 'lib' subdirectory of FB_HOME: # # con=fdb.connect( dsn='localhost:employee', fb_library_name='/var/tmp/fb40tmp/lib/libfbclient.so', ...) # FB_CLNT=os.path.join(FB_HOME, 'lib', 'libfbclient.so' ) -# +# # #-------------------------------------------- -# +# # def flush_and_close( file_handle ): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -115,19 +115,19 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # else: # print('Unrecognized type of element:', f_names_list[i], ' - can not be treated as file.') # del_name = None -# +# # if del_name and os.path.isfile( del_name ): # os.remove( del_name ) -# +# # #-------------------------------------------- -# +# # #non_ascii_query="select 'gång' from rdb$database" # non_ascii_query=u"select 'gång' as non_ascii_literal from rdb$database" -# +# # f_sql_txt=''' # set count on; # set blob all; -# set list on; +# set list on; # select stored_sql_expr from non_ascii; # select # c.rdb$character_set_name as connection_charset @@ -140,14 +140,14 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # and s.mon$sql_text containing 'non_ascii_literal' # ; # ''' -# +# # f_mon_sql = open( os.path.join(context['temp_directory'], 'tmp_2531_w1252.sql'), 'w' ) # f_mon_sql.write(f_sql_txt) # flush_and_close( f_mon_sql ) -# +# # isql_path = context['isql_path'] # isql_file = f_mon_sql.name -# +# # ###################################################################### # ### t e m p o r a r y P y t h o n s c r i p t s t ar t ### # ###################################################################### @@ -157,57 +157,58 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import subprocess # os.environ["ISC_USER"] = '%(user_name)s' # os.environ["ISC_PASSWORD"] = '%(user_password)s' -# +# # att1 = fdb.connect(dsn = r'%(dsn)s', fb_library_name = r'%(FB_CLNT)s', charset = 'win1252') # att1.execute_immediate("recreate table non_ascii(stored_sql_expr varchar(255) character set win1252)") # att1.commit() -# +# # txt = "%(non_ascii_query)s" -# +# # cur1=att1.cursor() # cur1.execute( "insert into non_ascii(stored_sql_expr) values('%%s')" %% txt.replace("'","''") ) # att1.commit() -# +# # # The will cause SQL expression with non-ascii character # # appear in mon$statements.mon$sql_text column. # # NOTE: charset of current connection is >>> win1252 <<< # cur1.prep(txt) -# +# # subprocess.call([ r'%(isql_path)s', r'%(dsn)s', '-ch', 'utf8', '-i', r'%(isql_file)s' ]) -# +# # cur1.close() # att1.commit() # att1.close() # ''' % dict(globals(), **locals()) # ############## temporary Python script finish ################### -# -# +# +# # ##################################################################### # ### Create temporary Python script with code page = Windows-1252, ### # ### so |select 'gång' from rdb$database| will be written there in ### -# ### single-byte encoding rather than in utf8. ### +# ### single-byte encoding rather than in utf8. ### # #################################################################### # f_python_run=codecs.open( os.path.join(context['temp_directory'],'tmp_2531_w1252.py'), encoding='cp1252', mode='w') # f_python_run.write(f_python_txt) -# +# # flush_and_close( f_python_run ) # time.sleep(1) -# +# # runProgram( sys.executable, [f_python_run.name] ) -# +# # # 02.03.2021 DO NOT! subprocess.call( [sys.executable, f_python_run.name] ) -- for unknown reason this will NOT issu anything to STDOUT! -# +# # time.sleep(1) -# +# # # 02.03.2021: do NOT pass to cleanup() files! # # It will fail with 'uknown type' for executed script ('f_python_run') # # because it will consider its type as 'instance' rather than common 'file'! # # We have to pass only NAMES list of used files here: # cleanup( [i.name for i in (f_python_run,f_mon_sql)] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ STORED_SQL_EXPR select 'gång' as non_ascii_literal from rdb$database @@ -218,8 +219,32 @@ expected_stdout_1 = """ """ @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + non_ascii_query = "select 'gång' as non_ascii_literal from rdb$database" + non_ascii_query_inline = non_ascii_query.replace("'","''") + script = ''' + set count on; + set blob all; + set list on; + select stored_sql_expr from non_ascii; + select + c.rdb$character_set_name as connection_charset + ,s.mon$sql_text as sql_text_blob_id + from mon$attachments a + left join rdb$character_sets c on a.mon$character_set_id = c.rdb$character_set_id + left join mon$statements s on a.mon$attachment_id = s.mon$attachment_id + where + s.mon$attachment_id <> current_connection + and s.mon$sql_text containing 'non_ascii_literal' + ; + ''' + act_1.expected_stdout = expected_stdout_1 + with act_1.db.connect(charset='WIN1252') as con: + c = con.cursor() + c.execute(f"insert into non_ascii(stored_sql_expr) values('{non_ascii_query_inline}')") + con.commit() + x = c.prepare(non_ascii_query) + act_1.isql(switches=[], input=script, charset='WIN1252') + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2573_test.py b/tests/bugs/core_2573_test.py index 232c7014..4d09445d 100644 --- a/tests/bugs/core_2573_test.py +++ b/tests/bugs/core_2573_test.py @@ -2,14 +2,14 @@ # # id: bugs.core_2573 # title: The server crashes when selecting from the MON$ tables in the ON DISCONNECT trigger -# decription: +# decription: # tracker_id: CORE-2573 # min_versions: [] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None @@ -30,24 +30,39 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # select mon$stat_id from mon$attachments rows 1 into :i; # end # """ -# +# # c = db_conn.cursor() # c.execute(sql) # db_conn.commit() # db_conn.close() -# +# # db_conn = kdb.connect(dsn=dsn,user=user_name,password=user_password) # c = db_conn.cursor() # c.execute("DROP TRIGGER DIST") # db_conn.commit() # db_conn.close() #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + sql = """CREATE OR ALTER TRIGGER DIST + ON DISCONNECT POSITION 0 + AS + declare variable i integer; + begin + select mon$stat_id from mon$attachments rows 1 into :i; + end + """ + with act_1.db.connect() as con: + c = con.cursor() + c.execute(sql) + con.commit() + # The server should could be crashed at this point + with act_1.db.connect() as con: + c = con.cursor() + c.execute('DROP TRIGGER DIST') + con.commit() diff --git a/tests/bugs/core_2602_test.py b/tests/bugs/core_2602_test.py index 5f78d319..970e4d24 100644 --- a/tests/bugs/core_2602_test.py +++ b/tests/bugs/core_2602_test.py @@ -2,7 +2,7 @@ # # id: bugs.core_2602 # title: Attachments using NONE charset may cause reads from MON$ tables to fail -# decription: +# decription: # Fully refactored 05-feb-2018. Apply HASH() functions to unicode mon$sql_text in order to returnonly numeric value # rather than non-translatable unicode representation which can differ on machines with different codepage / OS. # For example, instead of (on Win XP with codepage 1251): @@ -16,7 +16,7 @@ # - lock conflict on no wait transaction', -901, 335544345) # - just after issued 1st cursor result. # No error since 2.5.0.26074. -# +# # 25.12.2019: added filtering expr to WHERE clause in order to prevent from passing through records related to RDB$AUTH table (4.x). # Last tested 25.12.2019 on: # 4.0.0.1707 SS: 1.448s. @@ -27,14 +27,14 @@ # 3.0.5.33212 CS: 1.755s. # 2.5.9.27149 SC: 0.218s. # 2.5.9.27119 SS: 2.411s. -# +# # tracker_id: CORE-2602 # min_versions: ['2.5'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None @@ -47,34 +47,34 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import fdb # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # db_conn.close() -# +# # c1none=fdb.connect(dsn=dsn,charset='none') # c1utf8=fdb.connect(dsn=dsn,charset='utf8') -# +# # c2none=fdb.connect(dsn=dsn,charset='none') # c2utf8=fdb.connect(dsn=dsn,charset='utf8') -# +# # r1none = c1none.cursor() # r1utf8 = c1utf8.cursor() -# +# # r2none = c2none.cursor() # r2utf8 = c2utf8.cursor() -# +# # # In attachment with charset = NONE we start query to mon$database which includes two non-ascii characters: -# # 1) Unicode Character 'LATIN SMALL LETTER A WITH ACUTE' (U+00E1) +# # 1) Unicode Character 'LATIN SMALL LETTER A WITH ACUTE' (U+00E1) # # 2) Unicode Character 'LATIN SMALL LETTER E WITH ACUTE' (U+00E9) # r1none.execute("select '123áé456' from mon$database") -# +# # sql=''' -# select -# iif(s.mon$attachment_id = current_connection, 'this', 'othr') as attach +# select +# iif(s.mon$attachment_id = current_connection, 'this', 'othr') as attach # -- do NOT return SQL text of query with non-ascii characters: it can be displayed differently # -- depending on machine codepage (or OS?): # -- >>> disabled 05-feb-2018 >>> , iif(s.mon$sql_text like '%123%456%', s.mon$sql_text, '') as sql_text @@ -85,49 +85,50 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # join mon$attachments a on s.mon$attachment_id = a.mon$attachment_id # join rdb$character_sets c on a.mon$character_set_id = c.rdb$character_set_id # where -# s.mon$attachment_id != current_connection +# s.mon$attachment_id != current_connection # and s.mon$sql_text NOT containing 'mon$statements' # and s.mon$sql_text NOT containing 'rdb$auth' # and a.mon$remote_protocol is not null # ''' -# +# # r2none.execute(sql) # for r in r2none: # #print( ' '.join( 'THIS connect with charset NONE. OTHER connect tiwh charset UTF8.', 'WE see other sqltest hash as: ', r[0], '; sql_text_hash: ', str( r[1] ), '; charset_name', r[2] ) ) # #print('r2none',r) # print( ' '.join( ( 'Attach: r2none.', r[0], '; sql_text_hash: ', str( r[1] ), '; charset_name', r[2] ) ) ) -# +# # r2utf8.execute(sql) # for r in r2utf8: # print( ' '.join( ( 'Attach: r2utf8.', r[0], '; sql_text_hash: ', str( r[1] ), '; charset_name', r[2] ) ) ) # #print('r2utf8',r) -# +# # c1none.close() # #---------------------------- -# +# # r1utf8.execute("select '123áé456' from mon$database") -# +# # c2none.commit() # c2utf8.commit() -# +# # r2none.execute(sql) # for r in r2none: # #print('r2none', r) # print( ' '.join( ( 'Attach: r2none.', r[0], '; sql_text_hash: ', str( r[1] ), '; charset_name', r[2] ) ) ) -# +# # r2utf8.execute(sql) # for r in r2utf8: # #print('r2utf8', r) # print( ' '.join( ( 'Attach: r2utf8.', r[0], '; sql_text_hash: ', str( r[1] ), '; charset_name', r[2] ) ) ) -# +# # c1utf8.close() # #--------------------------- -# +# # c2none.close() # c2utf8.close() -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ Attach: r2none. othr ; sql_text_hash: 98490476833044645 ; charset_name NONE @@ -137,8 +138,57 @@ expected_stdout_1 = """ """ @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action, capsys): + with act_1.db.connect(charset='UTF8') as con1_utf8, \ + act_1.db.connect(charset='NONE') as con2_none, \ + act_1.db.connect(charset='UTF8') as con2_utf8: + with act_1.db.connect(charset='NONE') as con1_none: + r1none = con1_none.cursor() + r1utf8 = con1_utf8.cursor() + r2none = con2_none.cursor() + r2utf8 = con2_utf8.cursor() + # In attachment with charset = NONE we start query to mon$database which includes two non-ascii characters: + # 1) Unicode Character 'LATIN SMALL LETTER A WITH ACUTE' (U+00E1) + # 2) Unicode Character 'LATIN SMALL LETTER E WITH ACUTE' (U+00E9) + r1none.execute("select '123áé456' from mon$database") + sql = ''' + select + iif(s.mon$attachment_id = current_connection, 'this', 'othr') as attach + -- do NOT return SQL text of query with non-ascii characters: it can be displayed differently + -- depending on machine codepage (or OS?): + -- >>> disabled 05-feb-2018 >>> , iif(s.mon$sql_text like '%123%456%', s.mon$sql_text, '') as sql_text + -- Use HASH(sql_text) instead. Bug (in 2.1.3) still can be reproduces with hash() also: + ,hash(s.mon$sql_text) as sql_text_hash + ,trim(c.rdb$character_set_name) as charset_name + from mon$statements s + join mon$attachments a on s.mon$attachment_id = a.mon$attachment_id + join rdb$character_sets c on a.mon$character_set_id = c.rdb$character_set_id + where + s.mon$attachment_id != current_connection + and s.mon$sql_text NOT containing 'mon$statements' + and s.mon$sql_text NOT containing 'rdb$auth' + and a.mon$remote_protocol is not null + ''' + r2none.execute(sql) + for r in r2none: + print(' '.join(('Attach: r2none.', r[0], '; sql_text_hash: ', str(r[1]), '; charset_name', r[2]))) + r2utf8.execute(sql) + for r in r2utf8: + print(' '.join(('Attach: r2utf8.', r[0], '; sql_text_hash: ', str(r[1]), '; charset_name', r[2]))) + + r1utf8.execute("select '123áé456' from mon$database") + con2_none.commit() + con2_utf8.commit() + r2none.execute(sql) + for r in r2none: + print(' '.join(('Attach: r2none.', r[0], '; sql_text_hash: ', str(r[1]), '; charset_name', r[2]))) + + r2utf8.execute(sql) + for r in r2utf8: + print(' '.join(('Attach: r2utf8.', r[0], '; sql_text_hash: ', str(r[1]), '; charset_name', r[2]))) + # + act_1.expected_stdout = expected_stdout_1 + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2606_test.py b/tests/bugs/core_2606_test.py index bc3ed163..24742843 100644 --- a/tests/bugs/core_2606_test.py +++ b/tests/bugs/core_2606_test.py @@ -2,14 +2,14 @@ # # id: bugs.core_2606 # title: Multibyte CHAR value requested as VARCHAR is returned with padded spaces -# decription: +# decription: # tracker_id: CORE-2606 # min_versions: ['3.0'] # versions: 3.0 -# qmid: +# qmid: import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 3.0 # resources: None @@ -22,57 +22,89 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os -# +# # db_conn.close() # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # sql_cmd=''' # set list on; -# +# # select s.rdb$character_set_name as client_charset -# from mon$attachments a -# join rdb$character_sets s on a.mon$character_set_id = s.rdb$character_set_id +# from mon$attachments a +# join rdb$character_sets s on a.mon$character_set_id = s.rdb$character_set_id # where a.mon$attachment_id=current_connection; -# +# # --set sqlda_display on; # --set planonly; # --set echo on; -# +# # select cast('A' as char character set utf8) || '.' as char1_utf8 from rdb$database; # select cast('A' as varchar(1) character set utf8) || '.' as varc1_utf8 from rdb$database; # select cast('A' as char character set ascii) || '.' char1_ascii from rdb$database; # select cast('A' as varchar(1) character set ascii) || '.' varc1_ascii from rdb$database; # ''' -# +# # # Wrong result was encountered only in FB 1.5.6, for cast('A' as Char charset utf8 / ascii), on any client charset. # # Varchar always returns expected output. -# +# # runProgram('isql',['-q', '-ch', 'UTF8', dsn], sql_cmd) # runProgram('isql',['-q', '-ch', 'SJIS_0208', dsn], sql_cmd) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) -expected_stdout_1 = """ +act_1 = python_act('db_1', substitutions=substitutions_1) + +test_script_1 = """ +set list on; + +select s.rdb$character_set_name as client_charset +from mon$attachments a +join rdb$character_sets s on a.mon$character_set_id = s.rdb$character_set_id +where a.mon$attachment_id=current_connection; + +--set sqlda_display on; +--set planonly; +--set echo on; + +select cast('A' as char character set utf8) || '.' as char1_utf8 from rdb$database; +select cast('A' as varchar(1) character set utf8) || '.' as varc1_utf8 from rdb$database; +select cast('A' as char character set ascii) || '.' char1_ascii from rdb$database; +select cast('A' as varchar(1) character set ascii) || '.' varc1_ascii from rdb$database; +""" + +expected_stdout_1_a = """ CLIENT_CHARSET UTF8 CHAR1_UTF8 A. VARC1_UTF8 A. CHAR1_ASCII A. VARC1_ASCII A. +""" + +expected_stdout_1_b = """ CLIENT_CHARSET SJIS_0208 CHAR1_UTF8 A. VARC1_UTF8 A. CHAR1_ASCII A. VARC1_ASCII A. - """ +""" @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + act_1.expected_stdout = expected_stdout_1_a + act_1.script = test_script_1 + act_1.charset = 'UTF8' + act_1.execute() + assert act_1.clean_stdout == act_1.clean_expected_stdout + act_1.reset() + act_1.expected_stdout = expected_stdout_1_b + act_1.script = test_script_1 + act_1.charset = 'SJIS_0208' + act_1.execute() + assert act_1.clean_stdout == act_1.clean_expected_stdout + diff --git a/tests/bugs/core_2607_test.py b/tests/bugs/core_2607_test.py index f48d88e5..b28aef06 100644 --- a/tests/bugs/core_2607_test.py +++ b/tests/bugs/core_2607_test.py @@ -9,7 +9,7 @@ # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None @@ -25,26 +25,37 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # c = db_conn.cursor() # con2 = kdb.connect(dsn=dsn,user=user_name,password=user_password) # c2 = con2.cursor() -# +# # c.execute("select _dos850 '123áé456' from rdb$database") # c2.execute("select mon$sql_text from mon$statements s where s.mon$sql_text containing '_dos850'") # #printData(c2) # for r in c2: # print(r[0]) -# +# # con2.close() -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ select mon$sql_text from mon$statements s where s.mon$sql_text containing '_dos850' select _dos850 X'313233C3A1C3A9343536' from rdb$database - """ +""" @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action, capsys): + act_1.expected_stdout = expected_stdout_1 + with act_1.db.connect() as con1: + c1 = con.cursor() + with act_1.db.connect() as con2: + c2 = con.cursor() + c1.execute("select _dos850 '123áé456' from rdb$database") + c2.execute("select mon$sql_text from mon$statements s where s.mon$sql_text containing '_dos850'") + for r in c2: + print(r[0]) + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout + diff --git a/tests/bugs/core_2635_test.py b/tests/bugs/core_2635_test.py index 576a0efd..5aac7a68 100644 --- a/tests/bugs/core_2635_test.py +++ b/tests/bugs/core_2635_test.py @@ -2,19 +2,20 @@ # # id: bugs.core_2635 # title: Unique index with a lot of NULL keys can be corrupted at level 1 -# decription: +# decription: # tracker_id: CORE-2635 # min_versions: ['2.1.4', '2.0.6', '2.5'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None -substitutions_1 = [] +substitutions_1 = [('[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9]', ''), + ('Relation [0-9]{3,4}', 'Relation')] init_script_1 = """set term ^; recreate table t (id int, sss varchar(255)) ^ @@ -61,12 +62,25 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # db_conn.close() # runProgram('gfix',['-validate','-full','-no_update','-user',user_name,'-password',user_password,dsn]) #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) +act_1 = python_act('db_1', substitutions=substitutions_1) + +expected_stdout_1 = """ +Validation started +Relation (T) +process pointer page 0 of 1 +Index 1 (T_ID_DESC) +Index 2 (T_ID_ASC) +Index 3 (T_ID_SSS_DESC) +Index 4 (T_ID_SSS_ASC) +Relation (T) is ok +Validation finished +""" @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action): + act_1.expected_stdout = expected_stdout_1 + with act_1.connect_server() as srv: + srv.database.validate(database=str(act_1.db.db_path)) + act_1.stdout = '\n'.join(srv.readlines()) + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2651_test.py b/tests/bugs/core_2651_test.py index 3cbca85f..a01ac58f 100644 --- a/tests/bugs/core_2651_test.py +++ b/tests/bugs/core_2651_test.py @@ -2,14 +2,14 @@ # # id: bugs.core_2651 # title: Incorrect "string right truncation" error with NONE column and multibyte connection charset -# decription: +# decription: # tracker_id: CORE-2651 # min_versions: [] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None @@ -35,12 +35,16 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # finally: # con.close() #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + with act_1.db.connect(charset='CP943C') as con: + c = con.cursor() + try: + c.execute("select * from TEST_NONE") + except: + pytest.fail("Test FAILED") diff --git a/tests/bugs/core_2668_test.py b/tests/bugs/core_2668_test.py index e0a25f27..67c775a5 100644 --- a/tests/bugs/core_2668_test.py +++ b/tests/bugs/core_2668_test.py @@ -2,18 +2,18 @@ # # id: bugs.core_2668 # title: Write note into log when automatic sweep is started -# decription: +# decription: # In order to make database be 'ready' for sweep auto-start we: # 1. Launch ISQL in async. process (using Python subprocess.Popen() unit) which does: # 1.1. Change DB sweep interval to small value (100 selected). # 1.2. Create table and add one row in it (marked as 'LOCKED_FOR_PAUSE') + COMMIT. -# 1.3. Start EXECUTE BLOCK with adding 150 of rows in autonomous transactions +# 1.3. Start EXECUTE BLOCK with adding 150 of rows in autonomous transactions # (this lead eventually to difference Next - OIT =~ 150). # 1.4. Run execute block with ES/EDS which will establish new attachment (we specify # there connection ROLE as some non-existent string, see ":v_role" usage below). # This EB will try to UPDATE record which is LOCKED now by 1st ("main") attachment. # ISQL will infinitelly hang since this moment. -# +# # 2. Run (in main Python thread) FBSVCMGR with: # 2.1. Moving database to SHUTDOWN state; # 2.2. Returning database back to ONLINE. @@ -22,20 +22,26 @@ # 2.5. Take small delay (~2..3 seconds) in order to allow OS to finish writing new messages # about SWEEP auto-start in firebird.log. # 2.6. Obtaining content of firebird.log and storing it in separate file ('f_fblog_after'). -# +# # 3. Compare two files: f_fblog_before and f_fblog_after (using Python package "diff"). # Checked on: # 4.0.0.2173 SS/SC/CS # 3.0.7.33357 SS/SC/CS # 2.5.9.27152 SS/SC/CS -# +# # tracker_id: CORE-2668 # min_versions: ['2.5.2'] # versions: 2.5.2 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +import time +import subprocess +import re +from pathlib import Path +from difflib import unified_diff +from firebird.qa import db_factory, python_act, Action, temp_file +from firebird.driver import DbWriteMode, ShutdownMode, ShutdownMethod # version: 2.5.2 # resources: None @@ -56,16 +62,16 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import difflib # import shutil # import re -# +# # def svc_get_fb_log( engine, f_fb_log ): -# +# # import subprocess -# +# # if engine.startswith('2.5'): # get_firebird_log_key='action_get_ib_log' # else: # get_firebird_log_key='action_get_fb_log' -# +# # subprocess.call([ context['fbsvcmgr_path'], # "localhost:service_mgr", # get_firebird_log_key @@ -73,32 +79,32 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_fb_log, stderr=subprocess.STDOUT # ) # return -# +# # engine = str(db_conn.engine_version) # db_file=db_conn.database_name -# +# # db_conn.close() -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # #--------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -106,11 +112,11 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # os.remove( f_names_list[i] ) # if os.path.isfile( f_names_list[i]): # print('ERROR: can not remove file ' + f_names_list[i]) -# +# # #-------------------------------------------- -# +# # f_init_log = open( os.path.join(context['temp_directory'],'tmp_init_2668.log'), 'w') -# +# # subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr", # "action_properties", "prp_sweep_interval", "100", # "dbname", db_file, @@ -118,40 +124,40 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout = f_init_log, # stderr = subprocess.STDOUT # ) -# +# # subprocess.call([ context['fbsvcmgr_path'], "localhost:service_mgr", # "action_properties", "prp_write_mode", "prp_wm_async", # "dbname", db_file ], # stdout = f_init_log, # stderr = subprocess.STDOUT # ) -# +# # f_work_sql=open( os.path.join(context['temp_directory'],'tmp_work_2668.sql'), 'w') -# +# # sql_dml=''' # recreate table test(s varchar(36) unique); # insert into test(s) values('LOCKED_FOR_PAUSE'); # commit; -# +# # set transaction read committed WAIT; -# +# # update test set s = s where s = 'LOCKED_FOR_PAUSE'; -# +# # set term ^; # execute block as # declare n int = 150; # declare v_role varchar(31); # begin -# while (n > 0) do -# in autonomous transaction do -# insert into test(s) values( rpad('', 36, uuid_to_char(gen_uuid()) ) ) +# while (n > 0) do +# in autonomous transaction do +# insert into test(s) values( rpad('', 36, uuid_to_char(gen_uuid()) ) ) # returning :n-1 into n; -# +# # v_role = left(replace( uuid_to_char(gen_uuid()), '-', ''), 31); -# +# # begin # execute statement ('update test set s = s where s = ?') ('LOCKED_FOR_PAUSE') -# on external +# on external # 'localhost:' || rdb$get_context('SYSTEM', 'DB_NAME') # as user 'SYSDBA' password 'masterkey' role v_role # with autonomous transaction; @@ -159,7 +165,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # begin # end # end -# +# # end # ^ # set term ;^ @@ -168,12 +174,12 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # ''' # f_work_sql.write(sql_dml) # flush_and_close( f_work_sql ) -# +# # f_work_log=open( os.path.join(context['temp_directory'],'tmp_work_2668.log'), 'w') -# +# # p_work_sql=Popen( [context['isql_path'], dsn, "-i", f_work_sql.name], stdout = f_work_log, stderr = subprocess.STDOUT ) # time.sleep(3) -# +# # subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr", # "action_properties", "prp_shutdown_mode", "prp_sm_full", "prp_shutdown_db", "0", # "dbname", db_file, @@ -181,11 +187,11 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout = f_init_log, # stderr = subprocess.STDOUT # ) -# -# +# +# # p_work_sql.terminate() # flush_and_close( f_work_log ) -# +# # subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr", # "action_properties", "prp_db_online", # "dbname", db_file, @@ -193,75 +199,137 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout = f_init_log, # stderr = subprocess.STDOUT # ) -# +# # flush_and_close( f_init_log ) -# +# # # 4 debug only -- shutil.copy2(db_file, os.path.splitext(db_file)[0]+'.copy.fdb') -# +# # f_fblog_before=open( os.path.join(context['temp_directory'],'tmp_2668_fblog_before.txt'), 'w') # svc_get_fb_log( engine, f_fblog_before ) # flush_and_close( f_fblog_before ) -# +# # ########################################### # # Make connection to database. # # SWEEP should be auto started at this point: # ########################################### -# +# # con_for_sweep_start=fdb.connect(dsn = dsn) -# +# # # NOTE: it is mandatory to start transaction for auto-sweep: # con_for_sweep_start.begin() -# +# # # _!_!_!_!_!_!_!_!_!_! do NOT reduce this delay: firebird.log get new messages NOT instantly !_!_!_!_!_!_!_!_ # # Currently firebird.log can stay with OLD content if heavy concurrent workload exists on the same host! # time.sleep(2) -# +# # con_for_sweep_start.close() -# +# # f_fblog_after=open( os.path.join(context['temp_directory'],'tmp_2668_fblog_after.txt'), 'w') # svc_get_fb_log( engine, f_fblog_after ) # flush_and_close( f_fblog_after ) -# +# # oldfb=open(f_fblog_before.name, 'r') # newfb=open(f_fblog_after.name, 'r') -# +# # difftext = ''.join(difflib.unified_diff( -# oldfb.readlines(), +# oldfb.readlines(), # newfb.readlines() # )) # oldfb.close() # newfb.close() -# +# # f_diff_txt=open( os.path.join(context['temp_directory'],'tmp_2668_diff.txt'), 'w') # f_diff_txt.write(difftext) # flush_and_close( f_diff_txt ) -# +# # pattern = re.compile('Sweep\\s+.*SWEEPER', re.IGNORECASE) -# -# # NB: difflib.unified_diff() can show line(s) that present in both files, without marking that line(s) with "+". +# +# # NB: difflib.unified_diff() can show line(s) that present in both files, without marking that line(s) with "+". # # Usually these are 1-2 lines that placed just BEFORE difference starts. # # So we have to check output before display diff content: lines that are really differ must start with "+". -# +# # with open( f_diff_txt.name,'r') as f: # for line in f: # if line.startswith('+') and pattern.search(line): # print('Expected line FOUND.') -# +# # ############################### # # Cleanup. # time.sleep(1) # cleanup( [x.name for x in (f_init_log,f_work_sql,f_work_log,f_fblog_before,f_fblog_after,f_diff_txt) ] ) -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ Expected line FOUND. - """ +""" + +work_script_1 = temp_file('work_script.sql') @pytest.mark.version('>=2.5.2') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action, work_script_1: Path): + work_script_1.write_text(''' + recreate table test(s varchar(36) unique); + insert into test(s) values('LOCKED_FOR_PAUSE'); + commit; + set transaction read committed WAIT; + update test set s = s where s = 'LOCKED_FOR_PAUSE'; + + set term ^; + execute block as + declare n int = 150; + declare v_role varchar(31); + begin + while (n > 0) do + in autonomous transaction do + insert into test(s) values( rpad('', 36, uuid_to_char(gen_uuid()) ) ) + returning :n-1 into n; + + v_role = left(replace( uuid_to_char(gen_uuid()), '-', ''), 31); + + begin + execute statement ('update test set s = s where s = ?') ('LOCKED_FOR_PAUSE') + on external + 'localhost:' || rdb$get_context('SYSTEM', 'DB_NAME') + as user 'SYSDBA' password 'masterkey' role v_role + with autonomous transaction; + when any do + begin + end + end + + end + ^ + set term ;^ + set heading off; + select '-- shutdown me now --' from rdb$database; + ''') + with act_1.connect_server() as srv: + srv.database.set_sweep_interval(database=str(act_1.db.db_path), interval=100) + srv.database.set_write_mode(database=str(act_1.db.db_path), mode=DbWriteMode.ASYNC) + p_work_sql = subprocess.Popen([act_1.vars['isql'], '-i', str(work_script_1), + '-user', act_1.db.user, + '-password', act_1.db.password, act_1.db.dsn], + stderr = subprocess.STDOUT) + time.sleep(3) + srv.database.shutdown(database=str(act_1.db.db_path), mode=ShutdownMode.FULL, + method=ShutdownMethod.FORCED, timeout=0) + p_work_sql.terminate() + srv.database.bring_online(database=str(act_1.db.db_path)) + srv.info.get_log() + fblog_before = srv.readlines() + with act_1.db.connect() as con_for_sweep_start: + con_for_sweep_start.begin() + time.sleep(2) + srv.info.get_log() + fblog_after = srv.readlines() + pattern = re.compile('Sweep\\s+.*SWEEPER', re.IGNORECASE) + success = False + for line in unified_diff(fblog_before, fblog_after): + if line.startswith('+') and pattern.search(line): + success = True + assert success diff --git a/tests/bugs/core_2712_test.py b/tests/bugs/core_2712_test.py index 7723a8e5..3e3f80eb 100644 --- a/tests/bugs/core_2712_test.py +++ b/tests/bugs/core_2712_test.py @@ -2,15 +2,16 @@ # # id: bugs.core_2712 # title: Do not print "invalid request BLR" for par.cpp errors with valid BLR -# decription: -# +# decription: +# # tracker_id: CORE-2712 # min_versions: ['3.0.0'] # versions: 3.0 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action +from firebird.driver import DatabaseError # version: 3.0 # resources: None @@ -23,26 +24,26 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # db_conn.close() # msg='' -# +# # att1 = kdb.connect(dsn=dsn) # cur1 = att1.cursor() # cur1.execute("recreate table test(x int)") # att1.commit() # cur1.execute("insert into test values(1)") # att1.commit() -# +# # att2 = kdb.connect(dsn=dsn) # cur2 = att2.cursor() # cur2.execute("select 1 from rdb$database") -# +# # cur1.execute("drop table test") # try: # cur2.prep("update test set x=-x") @@ -50,22 +51,27 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # except Exception as e: # msg = e[0] # print(msg) -# +# # att1.close() # att2.close() -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) -expected_stdout_1 = """ - Error while preparing SQL statement: - - SQLCODE: -219 - - table id 128 is not defined - """ +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - +def test_1(act_1: Action, capsys): + with act_1.db.connect() as att1: + cur1 = att1.cursor() + cur1.execute("recreate table test(x int)") + att1.commit() + cur1.execute("insert into test values(1)") + att1.commit() + with act_1.db.connect() as att2: + cur2 = att2.cursor() + cur2.execute("select 1 from rdb$database") + cur1.execute("drop table test") + with pytest.raises(DatabaseError, match='.*table id [0-9]+ is not defined.*'): + cur2.prepare("update test set x=-x") + att2.commit() diff --git a/tests/bugs/core_2724_test.py b/tests/bugs/core_2724_test.py index dfe38f52..35a82f72 100644 --- a/tests/bugs/core_2724_test.py +++ b/tests/bugs/core_2724_test.py @@ -2,21 +2,21 @@ # # id: bugs.core_2724 # title: Validate or transform string of DML queries so that engine internals doesn't receive malformed strings -# decription: +# decription: # Code from doc/sql.extensions/README.ddl_triggers.txt was taken as basis for this test # (see ticket issue: "This situation happened with DDL triggers ..."). # Several DB objects are created here and their DDL contain unicode (Greek) text. -# Attachment these issues these DDL intentionally is run with charset = NONE. +# Attachment these issues these DDL intentionally is run with charset = NONE. # This charset (NONE) should result in question marks after we finish DDL and want to query log table # that was filled by DDL trigger and contains issued DDL statements. -# +# # tracker_id: CORE-2724 # min_versions: ['3.0'] # versions: 3.0 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 3.0 # resources: None @@ -41,7 +41,7 @@ init_script_1 = """ result_info blob sub_type text ); commit; - + set term ^; create trigger trig_ddl_log_before before any ddl statement as @@ -52,18 +52,18 @@ init_script_1 = """ -- didn't run, the log will survive. in autonomous transaction do begin - + select coalesce(c.rdb$character_set_name, '??? NULL ???') from mon$attachments a left join rdb$character_sets c on a.mon$character_set_id = c.rdb$character_set_id where a.mon$attachment_id = current_connection into v_current_connection_cset; - - insert into ddl_log (id, moment, current_connection_cset, + + insert into ddl_log (id, moment, current_connection_cset, event_type, object_type, ddl_event, object_name, old_object_name, new_object_name, sql_text, ok, result_info) - values (next value for ddl_seq, - 'now', + values (next value for ddl_seq, + 'now', :v_current_connection_cset, rdb$get_context('DDL_TRIGGER', 'EVENT_TYPE'), rdb$get_context('DDL_TRIGGER', 'OBJECT_TYPE'), @@ -79,7 +79,7 @@ init_script_1 = """ end end ^ - + -- Note: the above trigger will fire for this DDL command. It's good idea to use -nodbtriggers -- when working with them! create trigger trig_ddl_log_after after any ddl statement @@ -89,14 +89,14 @@ init_script_1 = """ -- record inserted on the BEFORE trigger autonomous transaction if user transaction is not -- READ COMMITTED. in autonomous transaction do - update ddl_log set ok = 'Y', + update ddl_log set ok = 'Y', result_info = 'Τα πάντα ήταν επιτυχής' -- Everything has completed successfully where id = rdb$get_context('USER_SESSION', 'trig_ddl_log_id'); end ^ set term ;^ commit; - + -- So lets delete the record about trig_ddl_log_after creation. delete from ddl_log; commit; @@ -106,33 +106,33 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import time # import subprocess -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # db_conn.close() -# +# # #--------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -140,13 +140,13 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # os.remove( f_names_list[i] ) # if os.path.isfile( f_names_list[i]): # print('ERROR: can not remove file ' + f_names_list[i]) -# +# # #-------------------------------------------- -# -# +# +# # sql_check=''' delete from ddl_log; # commit; -# +# # create domain dm_name varchar(50) check (value in ('αμορτισέρ', 'κόμβο', 'σωλήνα', 'φέροντα', 'βραχίονα')); # recreate table t1 ( # saller_id integer -- αναγνωριστικό εμπόρου // ID of saler @@ -155,39 +155,39 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # ); # commit; # set list on; -# +# # select id, current_connection_cset, sql_text, result_info, ddl_event, object_name # from ddl_log order by id; -# +# # commit; # drop table t1; # drop domain dm_name; # exit; # ''' -# +# # f_check_sql = open( os.path.join(context['temp_directory'],'tmp_check_2724.sql'), 'w') # f_check_sql.write(sql_check) # flush_and_close( f_check_sql ) -# +# # ########################################################################################## -# +# # f_ch_none_log = open( os.path.join(context['temp_directory'],'tmp_ch_none_2724.log'), 'w') # f_ch_none_err = open( os.path.join(context['temp_directory'],'tmp_ch_none_2724.err'), 'w') -# +# # subprocess.call( [context['isql_path'], dsn, "-i", f_check_sql.name, # "-ch", "none"], # stdout = f_ch_none_log, # stderr = f_ch_none_err # ) -# +# # flush_and_close( f_ch_none_log ) # flush_and_close( f_ch_none_err ) -# +# # ########################################################################################## -# +# # f_ch_utf8_log = open( os.path.join(context['temp_directory'],'tmp_ch_utf8_2724.log'), 'w') # f_ch_utf8_err = open( os.path.join(context['temp_directory'],'tmp_ch_utf8_2724.err'), 'w') -# +# # subprocess.call( [context['isql_path'], dsn, "-user", user_name, "-password", user_password, "-i", f_check_sql.name, # "-ch", "utf8"], # stdout = f_ch_utf8_log, @@ -195,50 +195,53 @@ db_1 = db_factory(charset='UTF8', sql_dialect=3, init=init_script_1) # ) # flush_and_close( f_ch_utf8_log ) # flush_and_close( f_ch_utf8_err ) -# -# +# +# # f_list = [f_ch_none_log, f_ch_none_err, f_ch_utf8_log, f_ch_utf8_err] # for f in f_list: # with open( f.name,'r') as f: # print(f.read()) -# -# +# +# # # Cleanup # ######### # time.sleep(1) -# +# # f_list.append(f_check_sql) # cleanup( [i.name for i in f_list] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) -expected_stdout_1 = """ +act_1 = python_act('db_1', substitutions=substitutions_1) + +expected_stdout_1_a = """ ID 2 - CURRENT_CONNECTION_CSET NONE - SQL_TEXT + CURRENT_CONNECTION_CSET NONE + SQL_TEXT create domain dm_name varchar(50) check (value in ('??????????????????', '??????????', '????????????', '??????????????', '????????????????')) - RESULT_INFO + RESULT_INFO Τα πάντα ήταν επιτυχής DDL_EVENT CREATE DOMAIN OBJECT_NAME DM_NAME ID 3 - CURRENT_CONNECTION_CSET NONE - SQL_TEXT + CURRENT_CONNECTION_CSET NONE + SQL_TEXT recreate table t1 ( saller_id integer -- ?????????????????????????? ?????????????? // ID of saler ,customer_id integer -- ?????????????????????????? ???????????? // ID of customer ,product_name dm_name ) - RESULT_INFO + RESULT_INFO Τα πάντα ήταν επιτυχής DDL_EVENT CREATE TABLE - OBJECT_NAME T1 - + OBJECT_NAME T1 +""" + +expected_stdout_1_b = """ ID 6 - CURRENT_CONNECTION_CSET UTF8 + CURRENT_CONNECTION_CSET UTF8 SQL_TEXT 80:0 create domain dm_name varchar(50) check (value in ('αμορτισέρ', 'κόμβο', 'σωλήνα', 'φέροντα', 'βραχίονα')) RESULT_INFO 80:2 @@ -247,22 +250,48 @@ expected_stdout_1 = """ OBJECT_NAME DM_NAME ID 7 - CURRENT_CONNECTION_CSET UTF8 - SQL_TEXT + CURRENT_CONNECTION_CSET UTF8 + SQL_TEXT recreate table t1 ( saller_id integer -- αναγνωριστικό εμπόρου // ID of saler ,customer_id integer -- αναγνωριστικό πελάτη // ID of customer ,product_name dm_name ) - RESULT_INFO + RESULT_INFO Τα πάντα ήταν επιτυχής DDL_EVENT CREATE TABLE - OBJECT_NAME T1 + OBJECT_NAME T1 """ @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + sql_check = ''' + delete from ddl_log; + commit; + create domain dm_name varchar(50) check (value in ('αμορτισέρ', 'κόμβο', 'σωλήνα', 'φέροντα', 'βραχίονα')); + recreate table t1 ( + saller_id integer -- αναγνωριστικό εμπόρου // ID of saler + ,customer_id integer -- αναγνωριστικό πελάτη // ID of customer + ,product_name dm_name + ); + commit; + set list on; + select id, current_connection_cset, sql_text, result_info, ddl_event, object_name + from ddl_log order by id; + + commit; + drop table t1; + drop domain dm_name; + exit; + ''' + # + act_1.expected_stdout = expected_stdout_1_a + act_1.isql(switches=[], charset='NONE', input=sql_check) + assert act_1.clean_stdout == act_1.clean_expected_stdout + # + act_1.reset() + act_1.expected_stdout = expected_stdout_1_b + act_1.isql(switches=[], charset='UTF8', input=sql_check) + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2788_test.py b/tests/bugs/core_2788_test.py index 1899c20c..4ae7e3a5 100644 --- a/tests/bugs/core_2788_test.py +++ b/tests/bugs/core_2788_test.py @@ -2,15 +2,15 @@ # # id: bugs.core_2788 # title: isql extracts the array dimensions after the character set name -# decription: -# +# decription: +# # tracker_id: CORE-2788 # min_versions: ['3.0'] # versions: 3.0 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 3.0 # resources: None @@ -27,29 +27,29 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import time # import subprocess # from subprocess import Popen -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # db_conn.close() -# +# # #--------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -57,20 +57,20 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # os.remove( f_names_list[i] ) # if os.path.isfile( f_names_list[i]): # print('ERROR: can not remove file ' + f_names_list[i]) -# +# # #-------------------------------------------- -# +# # sql_ddl=''' create domain dm_test as char(1) character set iso8859_1[1:2]; # create domain dm_test as char(1)[1:2] character set iso8859_1; # commit; # show domain dm_test; # ''' -# -# +# +# # f_init_sql = open( os.path.join(context['temp_directory'],'tmp_init_2788.sql'), 'w') # f_init_sql.write(sql_ddl) # flush_and_close( f_init_sql ) -# +# # f_init_log = open( os.path.join(context['temp_directory'],'tmp_init_2788.log'), 'w') # f_init_err = open( os.path.join(context['temp_directory'],'tmp_init_2788.err'), 'w') # subprocess.call( [context['isql_path'], dsn, "-i", f_init_sql.name], @@ -79,35 +79,35 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # ) # # This file should be empty: # flush_and_close( f_init_log ) -# +# # # This file should contain error messages about 1st 'CREATE DOMAIN' statement that has syntax trouble: # flush_and_close( f_init_err ) -# +# # # This file should contain error messages about 1st 'CREATE DOMAIN' statement that has syntax trouble: # with open( f_init_err.name,'r') as f: # print(f.read()) -# -# +# +# # f_xmeta_log = open( os.path.join(context['temp_directory'],'tmp_xmeta_2788.log'), 'w') # f_xmeta_err = open( os.path.join(context['temp_directory'],'tmp_xmeta_2788.err'), 'w') # subprocess.call( [context['isql_path'], dsn, "-x"], stdout = f_xmeta_log, stderr = f_xmeta_err) -# +# # # This file should contain metadata - domain definition: # flush_and_close( f_xmeta_log ) -# +# # # This file should be empty: # flush_and_close( f_xmeta_err ) -# +# # att1 = fdb.connect(dsn=dsn.encode()) # cur1=att1.cursor() # cur1.execute("drop domain dm_test") # att1.commit() -# +# # att1.close() -# +# # # This shoudl issue "There is no domain DM_TEST in this database": # runProgram('isql',[dsn, '-q'],'show domain dm_test;') -# +# # f_apply_log = open( os.path.join(context['temp_directory'],'tmp_apply_2788.log'), 'w') # f_apply_err = open( os.path.join(context['temp_directory'],'tmp_apply_2788.err'), 'w') # subprocess.call( [context['isql_path'], dsn, "-i", f_xmeta_log.name], @@ -117,49 +117,82 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # # Both of these files should be empty: # flush_and_close( f_apply_log ) # flush_and_close( f_apply_err ) -# +# # # This shoudl issue DDL of just created domain: # runProgram('isql',[dsn, '-q'],'show domain dm_test;') -# +# # # No output should be here: # with open( f_xmeta_err.name,'r') as f: # print(f.read()) -# +# # # No output should be here: # with open( f_apply_log.name,'r') as f: # print(f.read()) -# -# +# +# # # No output should be here: # with open( f_apply_err.name,'r') as f: # print(f.read()) -# -# +# +# # ################################################ # # Cleanup # time.sleep(1) # cleanup( [i.name for i in (f_init_sql,f_init_log,f_init_err,f_xmeta_log,f_xmeta_err,f_apply_log,f_apply_err) ] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ - Statement failed, SQLSTATE = 42000 - Dynamic SQL Error - -SQL error code = -104 - -Token unknown - line 1, column 57 - -[ - DM_TEST ARRAY OF [2] - CHAR(1) CHARACTER SET ISO8859_1 Nullable - """ -expected_stderr_1 = """ - There is no domain DM_TEST in this database - """ + DM_TEST ARRAY OF [2] + CHAR(1) CHARACTER SET ISO8859_1 Nullable + """ + +expected_stderr_1_a = """ + Statement failed, SQLSTATE = 42000 + Dynamic SQL Error + -SQL error code = -104 + -Token unknown - line 1, column 57 + -[ +""" + +expected_stderr_1_b = """ + There is no domain DM_TEST in this database +""" @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action): + sql_ddl = ''' create domain dm_test as char(1) character set iso8859_1[1:2]; + create domain dm_test as char(1)[1:2] character set iso8859_1; + commit; + show domain dm_test; + ''' + act_1.expected_stderr = expected_stderr_1_a + act_1.expected_stdout = expected_stdout_1 + act_1.isql(switches=[], input=sql_ddl) + assert act_1.clean_stderr == act_1.clean_expected_stderr + assert act_1.clean_stdout == act_1.clean_expected_stdout + # + act_1.reset() + act_1.isql(switches=['-x']) + xmeta = act_1.stdout + # + with act_1.db.connect() as con: + c = con.cursor() + c.execute('drop domain dm_test') + con.commit() + # + act_1.reset() + act_1.expected_stderr = expected_stderr_1_b + act_1.isql(switches=['-q'], input='show domain dm_test;') + assert act_1.clean_stderr == act_1.clean_expected_stderr + # + act_1.reset() + act_1.isql(switches=[], input=xmeta) + # + act_1.reset() + act_1.expected_stdout = expected_stdout_1 + act_1.isql(switches=['-q'], input='show domain dm_test;') + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2817_test.py b/tests/bugs/core_2817_test.py index 488cc4c2..a0b37821 100644 --- a/tests/bugs/core_2817_test.py +++ b/tests/bugs/core_2817_test.py @@ -2,15 +2,15 @@ # # id: bugs.core_2817 # title: If stored procedure or trigger contains query with PLAN ORDER it could fail after disconnect of attachment where procedure rigger executed first time -# decription: -# +# decription: +# # tracker_id: CORE-2817 # min_versions: ['2.5.0'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None @@ -39,9 +39,9 @@ init_script_1 = """ set term ^; create or alter procedure sp_test(a_odd smallint) as declare c_ord cursor for ( - select s - from test - where mod(x, 2) = :a_odd + select s + from test + where mod(x, 2) = :a_odd order by x ); declare v_s type of column test.s; @@ -57,7 +57,7 @@ init_script_1 = """ end ^ -- sp_test set term ;^ - commit; + commit; """ db_1 = db_factory(sql_dialect=3, init=init_script_1) @@ -65,32 +65,36 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- # import fdb -# +# # db_conn.close() # att1 = kdb.connect(dsn=dsn.encode(),user='SYSDBA',password='masterkey') # att2 = kdb.connect(dsn=dsn.encode(),user='SYSDBA',password='masterkey') -# +# # cur1 = att1.cursor() # cur2 = att2.cursor() -# +# # sp_run='execute procedure sp_test' -# +# # cur1.execute('execute procedure sp_test(0)') # cur2.execute('execute procedure sp_test(1)') -# +# # att1.commit() # att1.close() -# +# # cur2.execute('execute procedure sp_test(1)') # att2.close() -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action): + with act_1.db.connect() as att2: + with act_1.db.connect() as att1: + cur1 = att1.cursor() + cur2 = att2.cursor() + cur1.execute('execute procedure sp_test(0)') + cur2.execute('execute procedure sp_test(1)') + att1.commit() + cur2.execute('execute procedure sp_test(1)') diff --git a/tests/bugs/core_2831_test.py b/tests/bugs/core_2831_test.py index 85e3cfce..7a1653a0 100644 --- a/tests/bugs/core_2831_test.py +++ b/tests/bugs/core_2831_test.py @@ -2,14 +2,14 @@ # # id: bugs.core_2831 # title: isql shouldn't display db and user name when extracting a script -# decription: +# decription: # tracker_id: CORE-2831 # min_versions: ['2.0.6', '2.1.4', '2.5'] # versions: 3.0 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 3.0 # resources: None @@ -25,12 +25,12 @@ db_1 = db_factory(page_size=4096, sql_dialect=3, init=init_script_1) # # # runProgram('isql',['-x',dsn,'-user',user_name,'-pass',user_password]) #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - +def test_1(act_1: Action): + act_1.isql(switches=['-x']) + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2861_test.py b/tests/bugs/core_2861_test.py index 60733ee1..996a0945 100644 --- a/tests/bugs/core_2861_test.py +++ b/tests/bugs/core_2861_test.py @@ -2,32 +2,30 @@ # # id: bugs.core_2861 # title: Cannot remove user with dot in login -# decription: -# Since 10.01.2016 this test (for 3.0) is based on totally new algorithm with checking ability of -# normal work with randomly generated logins. These logins consists only of punctuation chars and +# decription: +# Since 10.01.2016 this test (for 3.0) is based on totally new algorithm with checking ability of +# normal work with randomly generated logins. These logins consists only of punctuation chars and # for sure will have at least one dot. # The reason of this replacement was failed results on Classic 3.0 when 'gsec' utility is invoked. # Code for 2.5 was not changed and is preserved (though it was missed for 2.5 before, but it works OK). -# +# # See http://web.firebirdsql.org/download/prerelease/results/archive/ for builds: 3.0.0.32266 3.0.0.32268 -# +# # Correctness of current code was verified by batch scenario, totally about ~1500 iterations was done. # Several samples of logins that were be checked: # ,(/;.>_:%$^`.&<|#?=[~\\*}-{@) # >=[{+%\\.&|~$`(;#._,])}?*@:^! # }^\\*@.)#>|/;&!=~`]<[,?.-:(%. -# +# # NOTE: currently we EXCLUDE single and double quotes from random login because of CORE-5072. -# +# # This login is handled then by both FBSVCMGR and ISQL utilities: # 1) run FBSVCMGR and: -# 1.1) add user +# 1.1) add user # 1.2) modifying some of its attributes (password, firstname etc). # NOTE! We do *not* run 'fbsvcmgr action_delete_user' because it does not work (at least on build 32268) # ###################################################################################################### -# COMMAND: fbsvcmgr localhost/3333:service_mgr user sysdba password masterkey action_delete_user dbname C:\\MIX -# irebird -# b30\\security3.fdb sec_username john +# COMMAND: fbsvcmgr localhost/3333:service_mgr user sysdba password masterkey action_delete_user dbname C:\\MIX\\firebird\\fb30\\security3.fdb sec_username john # STDERR: unexpected item in service parameter block, expected isc_spb_sec_username # (sent letter to Alex, 09-jan-2016 22:34; after getting reply about this issue test can be further changed). # 2) run ISQL and: @@ -35,23 +33,27 @@ # 2.2) create this login again; # 2.3) modifying some of this login attributes; # 2.4) drop it finally. -# +# # See also: # CORE-1810 ("Usernames with '.' character"; login 'JOHN.SMITH' is used there). # CORE-3000 (use of unquoted reserved word ADMIN as user name in SQL command). -# +# # tracker_id: CORE-2861 # min_versions: ['2.5.0'] # versions: 3.0 -# qmid: +# qmid: import pytest -from firebird.qa import db_factory, isql_act, Action +import string +from random import sample, choice +from firebird.qa import db_factory, python_act, Action +from firebird.driver.types import UserInfo # version: 3.0 # resources: None -substitutions_1 = [('[ ]+', ' '), ('[\t]*', ' '), ('.* Name: .*', ' Name: ')] +substitutions_1 = [('.* name: .*', 'Name: '), + ('.*USER_NAME.*', 'USER_NAME ')] init_script_1 = """""" @@ -66,60 +68,60 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import string # from random import sample, choice # from fdb import services -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # db_name=db_conn.database_name # db_conn.close() -# +# # svc = services.connect(host='localhost') # security_db_name = svc.get_security_database_path() # path+name of security DB # svc.close() -# +# # # Useful links related to this .fbt: # # https://docs.python.org/2/library/string.html # # http://stackoverflow.com/questions/3854692/generate-password-in-python # # http://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python # # http://stackoverflow.com/questions/1024049/is-it-pythonic-to-import-inside-functions # # http://stackoverflow.com/questions/3095071/in-python-what-happens-when-you-import-inside-of-a-function -# +# # #-------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): # if os.path.isfile( f_names_list[i]): # os.remove( f_names_list[i] ) -# -# +# +# # #-------------------------------------------- -# +# # def svc_show_user( f_svc_log, f_svc_err, title_msg, security_db_name, sec_user ): -# +# # global subprocess -# +# # f_svc_log.seek(0,2) # f_svc_log.write("\\n") # f_svc_log.write(title_msg) # f_svc_log.write("\\n") # f_svc_log.seek(0,2) -# +# # subprocess.call( [ context['fbsvcmgr_path'], # "localhost:service_mgr", # "action_display_user_adm", @@ -129,38 +131,38 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_svc_log, stderr=f_svc_err # ) # return -# +# # #-------------------------------------------- -# +# # f_svc_log=open( os.path.join(context['temp_directory'],'tmp_2861_fbsvc.log'), 'w') # f_svc_err=open( os.path.join(context['temp_directory'],'tmp_2861_fbsvc.err'), 'w') -# -# chars = string.punctuation -# -# # + string.digits +# +# chars = string.punctuation +# +# # + string.digits # #string.letters + string.digits # # do NOT: chars = string.printable -- it includes whitespaces, i.e. LF etc! -# +# # # NB: maximal part of user name that is displayed in fbsvcmgr action_display_user is only 28 chars. # # We build user name as two parts delimited by dot, so max. length of these parts is bound to 13, # # otherwise total length of user name will be 14+1+14 = 29 and we'll get mismatch on expected stdout. -# -# +# +# # ''' # length = 13 # dotted_user=''.join(sample(chars,length)).replace('"','.').replace("'",'.') # dotted_user=dotted_user+'.'+''.join(sample(chars,length)) # dotted_user=dotted_user.replace('"','""').replace("'","''") # ''' -# +# # length = 28 # dotted_user=''.join(sample(chars,length)).replace('"','.').replace("'",'.') # quoted_user='"'+dotted_user+'"' -# +# # f_svc_log.write("Try to add user with name: "+quoted_user) # f_svc_log.write("\\n") # f_svc_log.seek(0,2) -# +# # subprocess.call( [ context['fbsvcmgr_path'], # "localhost:service_mgr", # "action_add_user", @@ -171,15 +173,15 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # ], # stdout=f_svc_log, stderr=f_svc_err # ) -# +# # svc_show_user( f_svc_log, f_svc_err, "Try to display user after adding.", security_db_name, quoted_user ) -# +# # f_svc_log.seek(0,2) # f_svc_log.write("\\n") # f_svc_log.write("Try to modify user: change password and some attributes.") # f_svc_log.write("\\n") # f_svc_log.seek(0,2) -# +# # subprocess.call( [ context['fbsvcmgr_path'], # "localhost:service_mgr", # "action_modify_user", @@ -193,23 +195,23 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # ], # stdout=f_svc_log, stderr=f_svc_err # ) -# +# # svc_show_user( f_svc_log, f_svc_err, "Try to display user after modifying.", security_db_name, quoted_user ) -# +# # f_svc_log.seek(0,2) # f_svc_log.write("\\n") # f_svc_log.write("All done.") # f_svc_log.write("\\n") -# +# # flush_and_close( f_svc_log ) # flush_and_close( f_svc_err ) -# +# # ##################################### -# +# # # Now we drop user (using ISQL - fbsvcmgr currently does not work) # # and then create + modify + drop him again by ISQL. -# -# +# +# # isql_txt='''---- %s # set list on; # --set echo on; @@ -217,24 +219,24 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # from sec$users # where upper(sec$user_name)=upper('%s'); # commit; -# +# # -- select * from v_sec; commit; -- ==> FOO.RIO.BAR, in UPPER case (left after fbsvcmgr add_user) -# +# # drop user %s; # commit; -# +# # select 'Try to add user with name: ' || '%s' as isql_msg from rdb$database; -# +# # create or alter user %s password '123' grant admin role; # commit; -# +# # select 'Try to display user after adding.' as isql_msg from rdb$database; -# +# # select * from v_sec; # commit; -# +# # select 'Try to modify user: change password and some attributes.' as isql_msg from rdb$database; -# +# # alter user %s # password 'Zeppelin' # firstname 'John' @@ -243,97 +245,147 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # revoke admin role # ; # commit; -# +# # select 'Try to display user after modifying.' as isql_msg from rdb$database; # select * from v_sec; # commit; -# +# # select 'Try to drop user.' as isql_msg from rdb$database; # drop user %s; # commit; # select 'All done.' as isql_msg from rdb$database; # ''' % (dotted_user, dotted_user, quoted_user.upper(), dotted_user, quoted_user, quoted_user, quoted_user ) -# +# # f_sql_txt=open( os.path.join(context['temp_directory'],'tmp_2861_isql.sql'), 'w') # f_sql_txt.write(isql_txt) # flush_and_close( f_sql_txt ) -# +# # f_sql_log=open( os.path.join(context['temp_directory'],'tmp_2861_isql.log'), 'w') # f_sql_err=open( os.path.join(context['temp_directory'],'tmp_2861_isql.err'), 'w') -# +# # subprocess.call( [ context['isql_path'], dsn, "-i", f_sql_txt.name ], # stdout=f_sql_log, stderr=f_sql_err # ) -# +# # flush_and_close( f_sql_log ) # flush_and_close( f_sql_err ) -# +# # with open( f_svc_log.name,'r') as f: # l = [l for l in f.readlines() if l.strip()] -# +# # for line in l: # print("SVC STDOUT: "+line.replace(dotted_user.upper(),"") ) -# +# # with open( f_svc_err.name,'r') as f: # l = [l for l in f.readlines() if l.strip()] -# +# # for line in l: # print("SVC STDERR: "+line+", user: "+dotted_user) -# +# # with open( f_sql_log.name,'r') as f: # l = [l for l in f.readlines() if l.strip()] -# +# # for line in l: # print("SQL STDOUT: "+line.replace(dotted_user,"")) -# +# # with open( f_sql_err.name,'r') as f: # l = [l for l in f.readlines() if l.strip()] -# +# # for line in l: # print("SQL STDERR: "+line+", user: "+dotted_user) -# -# +# +# # # Cleanup. # ########## -# +# # time.sleep(1) # cleanup( [i.name for i in (f_svc_log,f_svc_err,f_sql_log,f_sql_err,f_sql_txt)] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ - SVC STDOUT: Try to add user with name: "" - SVC STDOUT: Try to display user after adding. - SVC STDOUT: Login Full name uid gid adm - SVC STDOUT: 0 0 yes - SVC STDOUT: Try to modify user: change password and some attributes. - SVC STDOUT: Try to display user after modifying. - SVC STDOUT: Login Full name uid gid adm - SVC STDOUT: Ozzy The Terrible Osbourne 0 0 no - SVC STDOUT: All done. - SQL STDOUT: ISQL_MSG Try to add user with name: - SQL STDOUT: ISQL_MSG Try to display user after adding. - SQL STDOUT: SEC$USER_NAME - SQL STDOUT: SEC$FIRST_NAME - SQL STDOUT: SEC$MIDDLE_NAME - SQL STDOUT: SEC$LAST_NAME - SQL STDOUT: SEC$ADMIN - SQL STDOUT: ISQL_MSG Try to modify user: change password and some attributes. - SQL STDOUT: ISQL_MSG Try to display user after modifying. - SQL STDOUT: SEC$USER_NAME - SQL STDOUT: SEC$FIRST_NAME John - SQL STDOUT: SEC$MIDDLE_NAME Bonzo The Beast - SQL STDOUT: SEC$LAST_NAME Bonham - SQL STDOUT: SEC$ADMIN - SQL STDOUT: ISQL_MSG Try to drop user. - SQL STDOUT: ISQL_MSG All done. - """ +ISQL_MSG Try to add user with name: +ISQL_MSG Try to display user after adding. +SEC$USER_NAME +SEC$FIRST_NAME +SEC$MIDDLE_NAME +SEC$LAST_NAME +SEC$ADMIN +ISQL_MSG Try to modify user: change password and some attributes. +ISQL_MSG Try to display user after modifying. +SEC$USER_NAME +SEC$FIRST_NAME John +SEC$MIDDLE_NAME Bonzo The Beast +SEC$LAST_NAME Bonham +SEC$ADMIN +ISQL_MSG Try to drop user. +ISQL_MSG All done. +""" @pytest.mark.version('>=3.0') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + chars = string.punctuation + length = 28 + dotted_user = ''.join(sample(chars, length)).replace('"', '.').replace("'", '.') + quoted_user = '"' + dotted_user + '"' + # Via services + with act_1.connect_server() as srv: + srv.user.add(user_name=quoted_user, password='foobarkey', admin=True) + user = srv.user.get(user_name=quoted_user) + assert user == UserInfo(user_name=dotted_user, password=None, admin=True, + first_name='', middle_name='', last_name='', user_id=0, + group_id=0) + srv.user.update(user_name=quoted_user, password='BSabbath', first_name='Ozzy', + middle_name='The Terrible', last_name='Osbourne', admin=False) + user = srv.user.get(user_name=quoted_user) + assert user == UserInfo(user_name=dotted_user, password=None, admin=False, + first_name='Ozzy', middle_name='The Terrible', last_name='Osbourne', + user_id=0, group_id=0) + srv.user.delete(user_name=quoted_user) + assert srv.user.get(user_name=quoted_user) is None + # Via ISQL + isql_txt = f'''---- {dotted_user} + set list on; + --set echo on; + create or alter view v_sec as select sec$user_name, sec$first_name, sec$middle_name, sec$last_name, sec$admin + from sec$users + where upper(sec$user_name)=upper('{dotted_user}'); + commit; + select 'Try to add user with name: ' || '{dotted_user}' as isql_msg from rdb$database; + + create or alter user {quoted_user} password '123' grant admin role; + commit; + + select 'Try to display user after adding.' as isql_msg from rdb$database; + + select * from v_sec; + commit; + + select 'Try to modify user: change password and some attributes.' as isql_msg from rdb$database; + + alter user {quoted_user} + password 'Zeppelin' + firstname 'John' + middlename 'Bonzo The Beast' + lastname 'Bonham' + revoke admin role + ; + commit; + + select 'Try to display user after modifying.' as isql_msg from rdb$database; + select * from v_sec; + commit; + + select 'Try to drop user.' as isql_msg from rdb$database; + drop user {quoted_user}; + commit; + select 'All done.' as isql_msg from rdb$database; + ''' + act_1.expected_stdout = expected_stdout_1 + act_1.isql(switches=[], input=isql_txt) + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2879_test.py b/tests/bugs/core_2879_test.py index 573c82b6..e09888ba 100644 --- a/tests/bugs/core_2879_test.py +++ b/tests/bugs/core_2879_test.py @@ -2,29 +2,33 @@ # # id: bugs.core_2879 # title: Sweep could raise error : page 0 is of wrong type (expected 6, found 1) -# decription: +# decription: # Test receives content of firebird.log _before_ and _after_ running query that is show in the ticket. # Then we compare these two files. # Difference between them should relate ONLY to sweep start and finish details, and NOT about page wrong type. -# +# # Checked on: WI-V2.5.7.27024, WI-V3.0.1.32570, WI-T4.0.0.316 -- all works OK. # Refactored 01.02.2020, checked on: # 4.0.0.1759 SS: 4.754s. # 3.0.6.33240 SS: 3.704s. # 2.5.9.27119 SS: 6.607s. -# +# # tracker_id: CORE-2879 # min_versions: ['2.5.1'] # versions: 2.5.1 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +import time +import subprocess +from pathlib import Path +from difflib import unified_diff +from firebird.qa import db_factory, python_act, Action, temp_file # version: 2.5.1 # resources: None -substitutions_1 = [('\t+', ' '), ('^((?!start|finish|expected|page|wrong).)*$', '')] +substitutions_1 = [('^((?!start|finish|expected|page|wrong).)*$', '')] # ('\t+', ' '), init_script_1 = """""" @@ -32,7 +36,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import sys # import subprocess @@ -41,61 +45,61 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import time # import datetime # from datetime import datetime -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # engine = str(db_conn.engine_version) # db_file = db_conn.database_name # "$(DATABASE_LOCATION)bugs.core_2879.fdb" -# +# # db_conn.close() -# +# # #-------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): # if os.path.isfile( f_names_list[i]): # os.remove( f_names_list[i] ) -# +# # #-------------------------------------------- -# +# # def svc_get_fb_log( engine, f_fb_log ): -# +# # global subprocess -# +# # if engine.startswith('2.5'): # get_firebird_log_key='action_get_ib_log' # else: # get_firebird_log_key='action_get_fb_log' -# +# # subprocess.call([ context['fbsvcmgr_path'], # "localhost:service_mgr", # get_firebird_log_key # ], -# stdout=f_fb_log, +# stdout=f_fb_log, # stderr=subprocess.STDOUT # ) -# +# # return -# +# # #----------------------------------------------------- -# +# # sql_ddl=''' set list on; # set term ^; # execute block returns (dts timestamp, sql varchar(80)) as @@ -110,110 +114,155 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # sql = 'create global temporary table ' || :s || ' (id int);'; # execute statement sql with autonomous transaction; # suspend; -# +# # dts = 'now'; # sql = 'drop table ' || :s || ';'; # execute statement sql with autonomous transaction; # suspend; -# +# # i = i + 1; # end # end ^ # set term ;^ # ''' -# +# # f_isql_cmd=open( os.path.join(context['temp_directory'],'tmp_make_lot_GTT_2879.sql'), 'w', buffering = 0) # f_isql_cmd.write(sql_ddl) # f_isql_cmd.close() -# +# # # Get content of firebird.log BEFORE test: # ########################################## -# +# # f_fblog_before=open( os.path.join(context['temp_directory'],'tmp_2879_fblog_before.txt'), 'w', buffering = 0) # svc_get_fb_log( engine, f_fblog_before ) # flush_and_close( f_fblog_before ) -# +# # # LAUNCH ISQL ASYNCHRONOUSLY # ############################ -# +# # f_isql_log=open( os.path.join(context['temp_directory'],'tmp_make_lot_GTT_2879.log'), 'w', buffering = 0) # p_isql=subprocess.Popen( [context['isql_path'], dsn, "-i", f_isql_cmd.name], -# stdout=f_isql_log, +# stdout=f_isql_log, # stderr=subprocess.STDOUT # ) # #------------ # time.sleep(2) # #------------ -# +# # # LAUNCH SWEEP while ISQL is working: # # ############ # fbsvc_log=open( os.path.join(context['temp_directory'],'tmp_svc_2879.log'), 'w', buffering = 0) # subprocess.call( [ context['fbsvcmgr_path'],"localhost:service_mgr", "action_repair", "dbname", db_file, "rpr_sweep_db"], stdout=fbsvc_log, stderr=subprocess.STDOUT ) # flush_and_close( fbsvc_log ) -# +# # p_isql.terminate() # f_isql_log.close() -# -# +# +# # # Get content of firebird.log AFTER test: # ######################################### -# +# # f_fblog_after=open( os.path.join(context['temp_directory'],'tmp_2879_fblog_after.txt'), 'w', buffering = 0) # svc_get_fb_log( engine, f_fblog_after ) # flush_and_close( f_fblog_after ) -# +# # # 07.08.2016 # # DIFFERENCE in the content of firebird.log should be EMPTY: # #################### -# +# # oldfb=open(f_fblog_before.name, 'r') # newfb=open(f_fblog_after.name, 'r') -# +# # difftext = ''.join(difflib.unified_diff( -# oldfb.readlines(), +# oldfb.readlines(), # newfb.readlines() # )) # oldfb.close() # newfb.close() -# +# # f_diff_txt=open( os.path.join(context['temp_directory'],'tmp_2879_diff.txt'), 'w', buffering = 0) # f_diff_txt.write(difftext) # flush_and_close( f_diff_txt ) -# -# # NB: difflib.unified_diff() can show line(s) that present in both files, without marking that line(s) with "+". +# +# # NB: difflib.unified_diff() can show line(s) that present in both files, without marking that line(s) with "+". # # Usually these are 1-2 lines that placed just BEFORE difference starts. # # So we have to check output before display diff content: lines that are really differ must start with "+". -# +# # with open( f_diff_txt.name,'r') as f: # for line in f: # if line.startswith('+') and line.split('+'): # print(line.replace('+',' ')) -# +# # # 01.02.2020. We have to change DB state to full shutdown and bring it back online # # in order to prevent "Object in use" while fbtest will try to drop this DB # ##################################### # runProgram('gfix',[dsn,'-shut','full','-force','0']) # runProgram('gfix',[dsn,'-online']) -# -# -# +# +# +# # # Cleanup: # ########## # time.sleep(1) # cleanup( [i.name for i in (f_isql_cmd, f_isql_log, fbsvc_log, f_fblog_before,f_fblog_after, f_diff_txt)] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ Sweep is started by SYSDBA Sweep is finished - """ +""" + +isql_script = temp_file('test-script.sql') @pytest.mark.version('>=2.5.1') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action, isql_script: Path, capsys): + isql_script.write_text(''' set list on; + set term ^; + execute block returns (dts timestamp, sql varchar(80)) as + declare i int; + declare s varchar(256); + begin + i = 1; + while (i < 32767) do + begin + s = 'tmp' || :i; + dts = 'now'; + sql = 'create global temporary table ' || :s || ' (id int);'; + execute statement sql with autonomous transaction; + suspend; + + dts = 'now'; + sql = 'drop table ' || :s || ';'; + execute statement sql with autonomous transaction; + suspend; + + i = i + 1; + end + end ^ + set term ;^ + ''') + with act_1.connect_server() as srv: + # Get content of firebird.log BEFORE test + srv.info.get_log() + log_before = srv.readlines() + p_isql = subprocess.Popen([act_1.vars['isql'], act_1.db.dsn, '-i', str(isql_script)], + stderr=subprocess.STDOUT) + time.sleep(2) + # LAUNCH SWEEP while ISQL is working + srv.database.sweep(database=str(act_1.db.db_path)) + p_isql.terminate() + # Get content of firebird.log AFTER test + srv.info.get_log() + log_after = srv.readlines() + for line in unified_diff(log_before, log_after): + if line.startswith('+') and line.split('+'): + print(line.replace('+', ' ')) + act_1.expected_stdout = expected_stdout_1 + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2912_test.py b/tests/bugs/core_2912_test.py index 7779f686..acffb67f 100644 --- a/tests/bugs/core_2912_test.py +++ b/tests/bugs/core_2912_test.py @@ -2,25 +2,28 @@ # # id: bugs.core_2912 # title: Exception when upper casing string with lowercase y trema (code 0xFF in ISO8859_1 ) -# decription: +# decription: # 02-mar-2021. Re-implemented in order to have ability to run this test on Linux. -# Ttest creates table and fills it with non-ascii characters in init_script, using charset = UTF8. +# Test creates table and fills it with non-ascii characters in init_script, using charset = UTF8. # Then it generates .sql script for running it in separae ISQL process. # This script makes connection to test DB using charset = ISO8859_1 and perform several queries. # Result will be redirected to .log and .err files (they will be encoded, of course, also in ISO8859_1). # Finally, we open .log file (using codecs package), convert its content to UTF8 and show in expected_stdout. -# +# # Checked on: -# * Windows: 4.0.0.2377, 3.0.8.33420, 2.5.9.27152 +# * Windows: 4.0.0.2377, 3.0.8.33420, 2.5.9.27152 # * Linux: 4.0.0.2377, 3.0.8.33415 -# +# +# [pcisar] 16.11.2021 +# This test fails as UPPER('ÿ') does not work properly +# # tracker_id: CORE-2912 # min_versions: ['2.5.3'] # versions: 2.5.3 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5.3 # resources: None @@ -33,39 +36,39 @@ init_script_1 = """ insert into test(c) values('ÿ'); insert into test(c) values('Faÿ'); commit; - create index test_cu on test computed by (upper (c collate iso8859_1)); - commit; + create index test_cu on test computed by (upper (c collate iso8859_1)); + commit; """ db_1 = db_factory(charset='ISO8859_1', sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import codecs # import subprocess # import time -# +# # db_conn.close() -# +# # #-------------------------------------------- -# +# # def flush_and_close( file_handle ): # # https://docs.python.org/2/library/os.html#os.fsync # # If you're starting with a Python file object f, # # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb') and file_handle.name != os.devnull: # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -76,13 +79,13 @@ db_1 = db_factory(charset='ISO8859_1', sql_dialect=3, init=init_script_1) # else: # print('Unrecognized type of element:', f_names_list[i], ' - can not be treated as file.') # del_name = None -# +# # if del_name and os.path.isfile( del_name ): # os.remove( del_name ) -# +# # #-------------------------------------------- -# -# sql_txt=''' set names ISO8859_1; +# +# sql_txt=''' set names ISO8859_1; # connect '%(dsn)s' user '%(user_name)s' password '%(user_password)s'; # set list on; # select upper('aÿb') au from rdb$database; @@ -95,15 +98,15 @@ db_1 = db_factory(charset='ISO8859_1', sql_dialect=3, init=init_script_1) # -- will be specified not in UPPER case (see note in CORE-4740 08/Apr/15 05:48 PM): # select c, upper(c) cu from test where c similar to '[[:ALPHA:]]{1,}ÿ%%'; # set plan on; -# select c from test where upper (c collate iso8859_1) = upper('ÿ'); -# select c, upper(c) cu from test where upper (c collate iso8859_1) starting with upper('Faÿ'); +# select c from test where upper (c collate iso8859_1) = upper('ÿ'); +# select c, upper(c) cu from test where upper (c collate iso8859_1) starting with upper('Faÿ'); # ''' % dict(globals(), **locals()) -# +# # f_run_sql = open( os.path.join(context['temp_directory'], 'tmp_2912_iso8859_1.sql'), 'w' ) # f_run_sql.write( sql_txt.decode('utf8').encode('iso-8859-1') ) # flush_and_close( f_run_sql ) # # result: file tmp_2912_iso8859_1.sql is encoded in iso8859_1 (aka 'latin-1') -# +# # f_run_log = open( os.path.splitext(f_run_sql.name)[0]+'.log', 'w') # f_run_err = open( os.path.splitext(f_run_sql.name)[0]+'.err', 'w') # subprocess.call( [ context['isql_path'], '-q', '-i', f_run_sql.name ], @@ -112,52 +115,67 @@ db_1 = db_factory(charset='ISO8859_1', sql_dialect=3, init=init_script_1) # ) # flush_and_close( f_run_log ) # flush_and_close( f_run_err ) -# +# # # result: output will be encoded in iso9959_1, error log must be empty. # with codecs.open(f_run_log.name, 'r', encoding='iso-8859-1' ) as f: # stdout_encoded_in_latin_1 = f.readlines() -# +# # with codecs.open(f_run_err.name, 'r', encoding='iso-8859-1' ) as f: # stderr_encoded_in_latin_1 = f.readlines() -# +# # for i in stdout_encoded_in_latin_1: # print( i.encode('utf8') ) -# +# # # NO error must occur: # ###################### # for i in stderr_encoded_in_latin_1: # print( 'UNEXPECTED STDERR: ', i.encode('utf8') ) -# +# # # cleanup: -# ########### +# ########### # cleanup( (f_run_sql, f_run_log, f_run_err) ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ - AU AÿB - C ÿ - CU ÿ - C Faÿ - CU FAÿ - C Faÿ - CU FAÿ - C Faÿ - CU FAÿ - C Faÿ - CU FAÿ - PLAN (TEST INDEX (TEST_CU)) - C ÿ - PLAN (TEST INDEX (TEST_CU)) - C Faÿ - CU FAÿ - """ + AU AÿB + C ÿ + CU ÿ + C Faÿ + CU FAÿ + C Faÿ + CU FAÿ + C Faÿ + CU FAÿ + C Faÿ + CU FAÿ + PLAN (TEST INDEX (TEST_CU)) + C ÿ + PLAN (TEST INDEX (TEST_CU)) + C Faÿ + CU FAÿ +""" @pytest.mark.version('>=2.5.3') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action): + sql_txt = '''set names ISO8859_1; + set list on; + select upper('aÿb') au from rdb$database; + select c, upper(c) cu from test where c starting with upper('ÿ'); + select c, upper(c) cu from test where c containing 'Faÿ'; + select c, upper(c) cu from test where c starting with 'Faÿ'; + select c, upper(c) cu from test where c like 'Faÿ%'; + -- ### ACHTUNG ### + -- As of WI-V2.5.4.26857, following will FAILS if character class "alpha" + -- will be specified not in UPPER case (see note in CORE-4740 08/Apr/15 05:48 PM): + select c, upper(c) cu from test where c similar to '[[:ALPHA:]]{1,}ÿ%'; + set plan on; + select c from test where upper (c collate iso8859_1) = upper('ÿ'); + select c, upper(c) cu from test where upper (c collate iso8859_1) starting with upper('Faÿ'); + ''' + act_1.expected_stdout = expected_stdout_1 + act_1.isql(switches=['-q'], charset='ISO8859_1', input=sql_txt) + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_2940_test.py b/tests/bugs/core_2940_test.py index 944d9adf..cc0edf59 100644 --- a/tests/bugs/core_2940_test.py +++ b/tests/bugs/core_2940_test.py @@ -2,45 +2,46 @@ # # id: bugs.core_2940 # title: Trace output could contain garbage data left from filtered out statements -# decription: +# decription: # 1. Obtain engine_version from built-in context variable. # 2. Make config for trace in proper format according to FB engine version, with 'exclude_filter' parameter from ticket. # 3. Launch trace session in separate child process using 'FBSVCMGR action_trace_start' # 4. Run ISQL with test commands. Only one of these command does not contain token that is specified in 'exclude_filter' # 5. Stop trace session. Output its log with filtering only statistics info. -# +# # Checked on: WI-V2.5.5.26916 (SS, SC, CS); WI-V3.0.0.32008 (SS, SC, CS). Result: OK. # Checked on: 3.0.1.32525, 4.0.0.238 // 07-jun-2016 # ::: NB ::: # Several delays (time.sleep) added in main thread because of OS buffering. Couldn't switch this buffering off. -# -# +# +# # ::: NB ::: 07-jun-2016. -# +# # WI-T4.0.0.238 will issue in trace log following statement with its statistics ("1 records fetched"): # === -# with recursive role_tree as ( -# select rdb$relation_name as nm, 0 as ur from rdb$user_privileges -# where -# rdb$privilege = 'M' and rdb$field_name = 'D' -# and rdb$user = ? and rdb$user_type = 8 -# union all -# select rdb$role_name as nm, 1 as ur from rdb$roles +# with recursive role_tree as ( +# select rdb$relation_name as nm, 0 as ur from rdb$user_privileges +# where +# rdb$privilege = 'M' and rdb$field_name = 'D' +# and rdb$user = ? and rdb$user_type = 8 +# union all +# select rdb$role_name as nm, 1 as ur from rdb$roles # where rdb$role_name =... -# +# # param0 = varchar(93), "SYSDBA" # param1 = varchar(93), "NONE" # === # We have to SKIP this statement statistics and start to check only "our" selects from rdb$database # see usage of first_sttm_pattern and trace_stat_pattern. -# +# # tracker_id: CORE-2940 # min_versions: ['2.5.0'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from threading import Thread +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None @@ -58,40 +59,40 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # from subprocess import Popen # import time # import re -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # engine=str(db_conn.engine_version) # db_conn.close() -# -# +# +# # #-------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): # if os.path.isfile( f_names_list[i]): # os.remove( f_names_list[i] ) -# +# # #-------------------------------------------- -# -# +# +# # txt25 = '''# Trace config, format for 2.5. Generated auto, do not edit! # # enabled true @@ -102,15 +103,15 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # log_statement_finish true # print_plan true # print_perf true -# time_threshold 0 +# time_threshold 0 # # ''' -# +# # # NOTES ABOUT TRACE CONFIG FOR 3.0: # # 1) Header contains `database` clause in different format vs FB 2.5: its data must be enclosed with '{' '}' # # 2) Name and value must be separated by EQUALITY sign ('=') in FB-3 trace.conf, otherwise we get runtime error: # # element "<. . .>" have no attribute value set -# +# # txt30 = '''# Trace config, format for 3.0. Generated auto, do not edit! # database=%[\\\\\\\\/]bugs.core_2940.fdb # { @@ -122,7 +123,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # log_statement_finish = true # print_plan = true # print_perf = true -# time_threshold = 0 +# time_threshold = 0 # } # ''' # trccfg=open( os.path.join(context['temp_directory'],'tmp_trace_2940.cfg'), 'w') @@ -131,40 +132,40 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # else: # trccfg.write(txt30) # trccfg.close() -# +# # trclog=open( os.path.join(context['temp_directory'],'tmp_trace_2940.log'), 'w') # trclog.close() # trclst=open( os.path.join(context['temp_directory'],'tmp_trace_2940.lst'), 'w') # trclst.close() -# +# # ##################################################### # # Starting trace session in new child process (async.): -# +# # f_trclog=open(trclog.name,'w') # # Execute a child program in a new process, redirecting STDERR to the same target as of STDOUT: # p_trace=Popen([context['fbsvcmgr_path'], "localhost:service_mgr", # "action_trace_start", # "trc_cfg", trccfg.name], # stdout=f_trclog, stderr=subprocess.STDOUT) -# +# # # Wait! Trace session is initialized not instantly! # time.sleep(1) -# +# # ##################################################### # # Running ISQL with test commands: -# +# # sqltxt=''' # set list on; # -- statistics for this statement SHOULD appear in trace log: -# select 1 k1 from rdb$database; +# select 1 k1 from rdb$database; # commit; -# +# # -- statistics for this statement should NOT appear in trace log: -# select 2 k2 from rdb$types rows 2 /* no_trace*/; -# +# select 2 k2 from rdb$types rows 2 /* no_trace*/; +# # -- statistics for this statement should NOT appear in trace log: -# select 3 no_trace from rdb$types rows 3; -# +# select 3 no_trace from rdb$types rows 3; +# # -- statistics for this statement should NOT appear in trace log: # set term ^; # execute block returns(k4 int) as @@ -174,15 +175,15 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # ^ # set term ;^ # ''' -# +# # runProgram('isql',[dsn,'-n'],sqltxt) -# +# # # do NOT remove this otherwise trace log can contain only message about its start before being closed! # time.sleep(3) -# +# # ##################################################### # # Getting ID of launched trace session and STOP it: -# +# # # Save active trace session info into file for further parsing it and obtain session_id back (for stop): # f_trclst=open(trclst.name,'w') # subprocess.call([context['fbsvcmgr_path'], "localhost:service_mgr", @@ -190,7 +191,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_trclst, stderr=subprocess.STDOUT # ) # flush_and_close( f_trclst ) -# +# # trcssn=0 # with open( trclst.name,'r') as f: # for line in f: @@ -201,7 +202,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # trcssn=word # i=i+1 # break -# +# # # Result: `trcssn` is ID of active trace session. Now we have to terminate it: # f_trclst=open(trclst.name,'a') # f_trclst.seek(0,2) @@ -211,14 +212,14 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_trclst, stderr=subprocess.STDOUT # ) # flush_and_close( f_trclst ) -# +# # # Terminate child process of launched trace session (though it should already be killed): # p_trace.terminate() # flush_and_close( f_trclog ) -# +# # ##################################################### # # Output log of trace session, with filtering only info about statistics ('fetches'): -# +# # first_sttm_pattern=re.compile("select 1 k1") # trace_stat_pattern=re.compile("1 records fetched") # flag=0 @@ -228,24 +229,74 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # flag=1 # if flag==1 and trace_stat_pattern.match(line): # print(line) -# +# # # Cleanup: # ########## # time.sleep(1) -# +# # cleanup([i.name for i in (trccfg,trclst,trclog)]) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ 1 records fetched - """ +""" + +test_script_1 = """ + set list on; + -- statistics for this statement SHOULD appear in trace log: + select 1 k1 from rdb$database; + commit; + -- statistics for this statement should NOT appear in trace log: + select 2 k2 from rdb$types rows 2 /* no_trace*/; + -- statistics for this statement should NOT appear in trace log: + select 3 no_trace from rdb$types rows 3; + -- statistics for this statement should NOT appear in trace log: + set term ^; + execute block returns(k4 int) as + begin + for select 4 from rdb$types rows 4 into k4 do suspend; + end -- no_trace + ^ + set term ;^ +""" + +def trace_session(act: Action): + cfg30 = ['# Trace config, format for 3.0. Generated auto, do not edit!', + f'database=%[\\\\/]{act.db.db_path.name}', + '{', + ' enabled = true', + ' #include_filter', + ' exclude_filter = %no_trace%', + ' log_connections = true', + ' log_transactions = true', + ' log_statement_finish = true', + ' print_plan = true', + ' print_perf = true', + ' time_threshold = 0', + '}'] + with act.connect_server() as srv: + srv.trace.start(config='\n'.join(cfg30)) + for line in srv: + print(line) + +@pytest.mark.version('>=3.0') +def test_1(act_1: Action, capsys): + trace_thread = Thread(target=trace_session, args=[act_1]) + trace_thread.start() + act_1.isql(switches=['-n'], input=test_script_1) + with act_1.connect_server() as srv: + for session in list(srv.trace.sessions.keys()): + srv.trace.stop(session_id=session) + trace_thread.join(1.0) + if trace_thread.is_alive(): + pytest.fail('Trace thread still alive') + act_1.expected_stdout = expected_stdout_1 + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout -@pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") diff --git a/tests/bugs/core_2981_test.py b/tests/bugs/core_2981_test.py index 2506e011..f05cb7cd 100644 --- a/tests/bugs/core_2981_test.py +++ b/tests/bugs/core_2981_test.py @@ -2,21 +2,23 @@ # # id: bugs.core_2981 # title: Error in Trace plugin (use local symbols in query) -# decription: +# decription: # Test prepares trace config as it was mentioned in the ticket, then creates .sql with russian text in UTF8 encoding # and run trace and ISQL. # Finally, we compare content of firebird.log before and after running this query (it should be empty) and check that # size of error log of trace session is zero. -# +# # Checked on: WI-V2.5.7.27024, WI-V3.0.1.32570, WI-T4.0.0.316 -- all works OK. -# +# # tracker_id: CORE-2981 # min_versions: ['2.5.1'] # versions: 2.5.1 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from threading import Thread +from difflib import unified_diff +from firebird.qa import db_factory, python_act, Action # version: 2.5.1 # resources: None @@ -34,60 +36,60 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import subprocess # import difflib # from subprocess import Popen -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # engine = str(db_conn.engine_version) # db_conn.close() -# +# # #-------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): # if os.path.isfile( f_names_list[i]): # os.remove( f_names_list[i] ) -# +# # #-------------------------------------------- -# +# # def svc_get_fb_log( engine, f_fb_log ): -# +# # global subprocess -# +# # if engine.startswith('2.5'): # get_firebird_log_key='action_get_ib_log' # else: # get_firebird_log_key='action_get_fb_log' -# +# # subprocess.call([ context['fbsvcmgr_path'], # "localhost:service_mgr", # get_firebird_log_key # ], -# stdout=f_fb_log, +# stdout=f_fb_log, # stderr=subprocess.STDOUT # ) -# +# # return -# +# # #-------------------------------------------- -# -# +# +# # txt25 = '''# Trace config, format for 2.5. Generated auto, do not edit! # # enabled true @@ -115,20 +117,20 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # max_blr_length 500 # max_dyn_length 500 # max_arg_length 80 -# max_arg_count 30 +# max_arg_count 30 # # # enabled false # log_services false # log_service_query false -# +# # ''' -# +# # # NOTES ABOUT TRACE CONFIG FOR 3.0: # # 1) Header contains `database` clause in different format vs FB 2.5: its data must be enclosed with '{' '}' # # 2) Name and value must be separated by EQUALITY sign ('=') in FB-3 trace.conf, otherwise we get runtime error: # # element "<. . .>" have no attribute value set -# +# # txt30 = '''# Trace config, format for 3.0. Generated auto, do not edit! # database=%[\\\\\\\\/]bugs.core_2981.fdb # { @@ -156,7 +158,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # max_blr_length = 500 # max_dyn_length = 500 # max_arg_length = 80 -# max_arg_count = 30 +# max_arg_count = 30 # } # services { # enabled = false @@ -164,52 +166,52 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # log_service_query = false # } # ''' -# +# # f_trccfg=open( os.path.join(context['temp_directory'],'tmp_trace_2981.cfg'), 'w') # if engine.startswith('2.5'): # f_trccfg.write(txt25) # else: # f_trccfg.write(txt30) # flush_and_close( f_trccfg ) -# +# # # Get content of firebird.log BEFORE test: # ########################################## -# +# # f_fblog_before=open( os.path.join(context['temp_directory'],'tmp_2981_fblog_before.txt'), 'w') # svc_get_fb_log( engine, f_fblog_before ) # flush_and_close( f_fblog_before ) -# +# # # Starting trace session in new child process (async.): # ####################################################### -# +# # f_trclog=open( os.path.join(context['temp_directory'],'tmp_trace_2981.log'), 'w') # f_trcerr=open( os.path.join(context['temp_directory'],'tmp_trace_2981.err'), 'w') -# +# # # Execute a child program in a new process, redirecting STDERR to the same target as of STDOUT: # p_trace=Popen([context['fbsvcmgr_path'], "localhost:service_mgr", # "action_trace_start", # "trc_cfg", f_trccfg.name], -# stdout=f_trclog, +# stdout=f_trclog, # stderr=f_trcerr # ) -# +# # # Wait! Trace session is initialized not instantly! # time.sleep(2) -# +# # localized_query=''' # set list on; # select '*Лев Николаевич Толстой * -# +# # *Анна Каренина * -# +# # /Мне отмщение, и аз воздам/ -# +# # *ЧАСТЬ ПЕРВАЯ* -# +# # *I * -# +# # Все счастливые семьи похожи друг на друга, каждая несчастливая -# семья несчастлива по-своему. +# семья несчастлива по-своему. # Все смешалось в доме Облонских. Жена узнала, что муж был в связи # с бывшею в их доме француженкою-гувернанткой, и объявила мужу, что # не может жить с ним в одном доме. Положение это продолжалось уже @@ -221,20 +223,20 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # своих комнат, мужа третий день не было дома. Дети бегали по всему # дому, как потерянные; англичанка поссорилась с экономкой и написала # записку приятельнице, прося приискать ей новое место; повар ушел еще -# вчера со двора, во время обеда; черная кухарка и кучер просили расчета. +# вчера со двора, во время обеда; черная кухарка и кучер просили расчета. # На третий день после ссоры князь Степан Аркадьич Облонский -- # Стива, как его звали в свете, -- в обычный час, то есть в восемь # часов утра, проснулся не в спальне жены, а в своем кабинете, на # сафьянном диване... Он повернул свое полное, выхоленное тело на # пружинах дивана, как бы желая опять заснуть надолго, с другой # стороны крепко обнял подушку и прижался к ней щекой; но вдруг -# вскочил, сел на диван и открыл глаза. +# вскочил, сел на диван и открыл глаза. # "Да, да, как это было? -- думал он, вспоминая сон. -- Да, как это # было? Да! Алабин давал обед в Дармштадте; нет, не в Дармштадте, а # что-то американское. Да, но там Дармштадт был в Америке. Да, Алабин # давал обед на стеклянных столах, да, -- и столы пели: Il mio tesoro, # и не Il mio tesoro, а что-то лучше, и какие-то маленькие графинчики, -# и они же женщины", -- вспоминал он. +# и они же женщины", -- вспоминал он. # Глаза Степана Аркадьича весело заблестели, и он задумался, # улыбаясь. "Да, хорошо было, очень хорошо. Много еще там было # отличного, да не скажешь словами и мыслями даже наяву не выразишь". @@ -244,27 +246,27 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # по старой, девятилетней привычке, не вставая, потянулся рукой к тому # месту, где в спальне у него висел халат. И тут он вспомнил вдруг, # как и почему он спит не в спальне жены, а в кабинете; улыбка исчезла -# с его лица, он сморщил лоб. +# с его лица, он сморщил лоб. # "Ах, ах, ах! Ааа!.." -- замычал он, вспоминая все, что было. И # его воображению представились опять все подробности ссоры с женою, # вся безвыходность его положения и мучительнее всего собственная вина -# его. +# его. # "Да! она не простит и не может простить. И всего ужаснее то, что # виной всему я, виной я, а не виноват. В этом-то вся драма, -- думал # он. -- Ах, ах, ах!" -- приговаривал он с отчаянием, вспоминая самые -# тяжелые для себя впечатления из этой ссоры. +# тяжелые для себя впечатления из этой ссоры. # Неприятнее всего была та первая минута, когда он, вернувшись из # театра, веселый и довольный, с огромною грушей для жены в руке, не # нашел жены в гостиной; к удивлению, не нашел ее и в кабинете и, # наконец, увидал ее в спальне с несчастною, открывшею все, запиской в -# руке. +# руке. # Она, эта вечно озабоченная, и хлопотливая, и недалекая, какою он # считал ее, Долли, неподвижно сидела с запиской в руке и с выражением -# ужаса, отчаяния и гнева смотрела на него. -# -- Что это? это? -- спрашивала она, указывая на записку. +# ужаса, отчаяния и гнева смотрела на него. +# -- Что это? это? -- спрашивала она, указывая на записку. # И при этом воспоминании, как это часто бывает, мучала Степана # Аркадьича не столько самое событие, сколько то, как он ответил на -# эти слова жены. +# эти слова жены. # С ним случилось в эту минуту то, что случается с людьми, когда # они неожиданно уличены в чем-нибудь слишком постыдном. Он не сумел # приготовить свое лицо к тому положению, в которое он становился @@ -273,45 +275,45 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # равнодушным -- все было бы лучше того, что он сделал! -- его лицо # совершенно невольно ("рефлексы головного мозга", -- подумал Степан # Аркадьич, который любил физиологию), совершенно невольно вдруг -# улыбнулось привычною, доброю и потому глупою улыбкой. +# улыбнулось привычною, доброю и потому глупою улыбкой. # Эту глупую улыбку он не мог простить себе. Увидав эту улыбку, # Долли вздрогнула, как от физической боли, разразилась, со # свойственною ей горячностью, потоком жестоких слов и выбежала из -# комнаты. С тех пор она не хотела видеть мужа. -# "Всему виной эта глупая улыбка", -- думал Степан Аркадьич. +# комнаты. С тех пор она не хотела видеть мужа. +# "Всему виной эта глупая улыбка", -- думал Степан Аркадьич. # "Но что же делать? что делать?" -- с отчаянием говорил он себе и -# не находил ответа. +# не находил ответа. # ' from rdb$database; # ''' # f_utf8 = open( os.path.join(context['temp_directory'],'tmp_2981_run.sql'), 'w') # f_utf8.write(localized_query) # flush_and_close( f_utf8 ) -# +# # # RUN QUERY WITH NON-ASCII CHARACTERS # ##################################### -# +# # f_run_log = open( os.path.join(context['temp_directory'],'tmp_2981_run.log'), 'w') # f_run_err = open( os.path.join(context['temp_directory'],'tmp_2981_run.err'), 'w') # subprocess.call( [context['isql_path'], dsn, "-q", "-i", f_utf8.name, '-ch', 'utf8' ], # stdout = f_run_log, # stderr = f_run_err # ) -# +# # flush_and_close( f_run_log ) # flush_and_close( f_run_err ) -# +# # ##################################################### # # Getting ID of launched trace session and STOP it: -# +# # # Save active trace session info into file for further parsing it and obtain session_id back (for stop): # f_trclst=open( os.path.join(context['temp_directory'],'tmp_trace_2981.lst'), 'w') # subprocess.call([context['fbsvcmgr_path'], "localhost:service_mgr", # "action_trace_list"], -# stdout=f_trclst, +# stdout=f_trclst, # stderr=subprocess.STDOUT # ) # flush_and_close( f_trclst ) -# +# # trcssn=0 # with open( f_trclst.name,'r') as f: # for line in f: @@ -322,7 +324,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # trcssn=word # i=i+1 # break -# +# # # Result: `trcssn` is ID of active trace session. Now we have to terminate it: # f_trclst=open(f_trclst.name,'a') # f_trclst.seek(0,2) @@ -332,23 +334,23 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_trclst, stderr=subprocess.STDOUT # ) # flush_and_close( f_trclst ) -# +# # # do NOT remove this delay: trase session can not be stopped immediatelly: # time.sleep(2) -# +# # # Terminate child process of launched trace session (though it should already be killed): # p_trace.terminate() # flush_and_close( f_trclog ) # flush_and_close( f_trcerr ) -# -# +# +# # # Get content of firebird.log AFTER test: # ######################################### -# +# # f_fblog_after=open( os.path.join(context['temp_directory'],'tmp_2981_fblog_after.txt'), 'w') # svc_get_fb_log( engine, f_fblog_after ) # flush_and_close( f_fblog_after ) -# +# # # STDERR for ISQL (that created DB) and trace session - they both must be EMPTY: # ################# # f_list=[f_run_err, f_trcerr] @@ -358,43 +360,184 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # with open( f_name,'r') as f: # for line in f: # print("Unexpected STDERR, file "+f_name+": "+line) -# +# # # DIFFERENCE in the content of firebird.log should be EMPTY: # #################### -# +# # oldfb=open(f_fblog_before.name, 'r') # newfb=open(f_fblog_after.name, 'r') -# +# # difftext = ''.join(difflib.unified_diff( -# oldfb.readlines(), +# oldfb.readlines(), # newfb.readlines() # )) # oldfb.close() # newfb.close() -# +# # f_diff_txt=open( os.path.join(context['temp_directory'],'tmp_2981_diff.txt'), 'w') # f_diff_txt.write(difftext) # flush_and_close( f_diff_txt ) -# +# # with open( f_diff_txt.name,'r') as f: # for line in f: # print("Unexpected DIFF in firebird.log: "+line) -# -# -# +# +# +# # # Cleanup: # ########### # time.sleep(1) # cleanup( [i.name for i in (f_run_log, f_run_err, f_trccfg, f_trclst, f_trcerr, f_fblog_before,f_fblog_after, f_diff_txt, f_trclog, f_utf8)] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) +act_1 = python_act('db_1', substitutions=substitutions_1) + +test_script_1 = """ +set list on; +select '*Лев Николаевич Толстой * + +*Анна Каренина * + +/Мне отмщение, и аз воздам/ + +*ЧАСТЬ ПЕРВАЯ* + +*I * + + Все счастливые семьи похожи друг на друга, каждая несчастливая + семья несчастлива по-своему. + Все смешалось в доме Облонских. Жена узнала, что муж был в связи + с бывшею в их доме француженкою-гувернанткой, и объявила мужу, что + не может жить с ним в одном доме. Положение это продолжалось уже + третий день и мучительно чувствовалось и самими супругами, и всеми + членами семьи, и домочадцами. Все члены семьи и домочадцы + чувствовали, что нет смысла в их сожительстве и что на каждом + постоялом дворе случайно сошедшиеся люди более связаны между собой, + чем они, члены семьи и домочадцы Облонских. Жена не выходила из + своих комнат, мужа третий день не было дома. Дети бегали по всему + дому, как потерянные; англичанка поссорилась с экономкой и написала + записку приятельнице, прося приискать ей новое место; повар ушел еще + вчера со двора, во время обеда; черная кухарка и кучер просили расчета. + На третий день после ссоры князь Степан Аркадьич Облонский -- + Стива, как его звали в свете, -- в обычный час, то есть в восемь + часов утра, проснулся не в спальне жены, а в своем кабинете, на + сафьянном диване... Он повернул свое полное, выхоленное тело на + пружинах дивана, как бы желая опять заснуть надолго, с другой + стороны крепко обнял подушку и прижался к ней щекой; но вдруг + вскочил, сел на диван и открыл глаза. + "Да, да, как это было? -- думал он, вспоминая сон. -- Да, как это + было? Да! Алабин давал обед в Дармштадте; нет, не в Дармштадте, а + что-то американское. Да, но там Дармштадт был в Америке. Да, Алабин + давал обед на стеклянных столах, да, -- и столы пели: Il mio tesoro, + и не Il mio tesoro, а что-то лучше, и какие-то маленькие графинчики, + и они же женщины", -- вспоминал он. + Глаза Степана Аркадьича весело заблестели, и он задумался, + улыбаясь. "Да, хорошо было, очень хорошо. Много еще там было + отличного, да не скажешь словами и мыслями даже наяву не выразишь". + И, заметив полосу света, пробившуюся сбоку одной из суконных стор, + он весело скинул ноги с дивана, отыскал ими шитые женой (подарок ко + дню рождения в прошлом году), обделанные в золотистый сафьян туфли и + по старой, девятилетней привычке, не вставая, потянулся рукой к тому + месту, где в спальне у него висел халат. И тут он вспомнил вдруг, + как и почему он спит не в спальне жены, а в кабинете; улыбка исчезла + с его лица, он сморщил лоб. + "Ах, ах, ах! Ааа!.." -- замычал он, вспоминая все, что было. И + его воображению представились опять все подробности ссоры с женою, + вся безвыходность его положения и мучительнее всего собственная вина + его. + "Да! она не простит и не может простить. И всего ужаснее то, что + виной всему я, виной я, а не виноват. В этом-то вся драма, -- думал + он. -- Ах, ах, ах!" -- приговаривал он с отчаянием, вспоминая самые + тяжелые для себя впечатления из этой ссоры. + Неприятнее всего была та первая минута, когда он, вернувшись из + театра, веселый и довольный, с огромною грушей для жены в руке, не + нашел жены в гостиной; к удивлению, не нашел ее и в кабинете и, + наконец, увидал ее в спальне с несчастною, открывшею все, запиской в + руке. + Она, эта вечно озабоченная, и хлопотливая, и недалекая, какою он + считал ее, Долли, неподвижно сидела с запиской в руке и с выражением + ужаса, отчаяния и гнева смотрела на него. + -- Что это? это? -- спрашивала она, указывая на записку. + И при этом воспоминании, как это часто бывает, мучала Степана + Аркадьича не столько самое событие, сколько то, как он ответил на + эти слова жены. + С ним случилось в эту минуту то, что случается с людьми, когда + они неожиданно уличены в чем-нибудь слишком постыдном. Он не сумел + приготовить свое лицо к тому положению, в которое он становился + перед женой после открытия его вины. Вместо того чтоб оскорбиться, + отрекаться, оправдываться, просить прощения, оставаться даже + равнодушным -- все было бы лучше того, что он сделал! -- его лицо + совершенно невольно ("рефлексы головного мозга", -- подумал Степан + Аркадьич, который любил физиологию), совершенно невольно вдруг + улыбнулось привычною, доброю и потому глупою улыбкой. + Эту глупую улыбку он не мог простить себе. Увидав эту улыбку, + Долли вздрогнула, как от физической боли, разразилась, со + свойственною ей горячностью, потоком жестоких слов и выбежала из + комнаты. С тех пор она не хотела видеть мужа. + "Всему виной эта глупая улыбка", -- думал Степан Аркадьич. + "Но что же делать? что делать?" -- с отчаянием говорил он себе и + не находил ответа. +' from rdb$database; +""" + +def trace_session(act: Action): + cfg30 = ['# Trace config, format for 3.0. Generated auto, do not edit!', + f'database=%[\\\\/]{act.db.db_path.name}', + '{', + ' enabled = true', + ' include_filter = %(SELECT|INSERT|UPDATE|DELETE)%', + ' exclude_filter = %no_trace%', + ' log_connections = true', + ' log_transactions = true', + ' log_statement_prepare = true', + ' log_statement_free = true', + ' log_statement_start = true', + ' log_statement_finish = true', + ' log_trigger_start = true', + ' log_trigger_finish = true', + ' log_context = true', + ' print_plan = true', + ' print_perf = true', + ' time_threshold = 0', + ' max_sql_length = 5000', + ' max_blr_length = 500', + ' max_dyn_length = 500', + ' max_arg_length = 80', + ' max_arg_count = 30', + '}', + 'services {', + ' enabled = false', + ' log_services = false', + ' log_service_query = false', + '}'] + with act.connect_server() as srv: + srv.encoding = 'utf8' + srv.trace.start(config='\n'.join(cfg30)) + for line in srv: + pass # we are not interested in trace output @pytest.mark.version('>=2.5.1') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def test_1(act_1: Action): + # Get content of firebird.log BEFORE test + with act_1.connect_server() as srv: + srv.info.get_log() + log_before = srv.readlines() + trace_thread = Thread(target=trace_session, args=[act_1]) + trace_thread.start() + # RUN QUERY WITH NON-ASCII CHARACTERS + act_1.isql(switches=['-n', '-q'], input=test_script_1) + with act_1.connect_server() as srv: + for session in list(srv.trace.sessions.keys()): + srv.trace.stop(session_id=session) + trace_thread.join(1.0) + if trace_thread.is_alive(): + pytest.fail('Trace thread still alive') + # Get content of firebird.log AFTER test + with act_1.connect_server() as srv: + srv.info.get_log() + log_after = srv.readlines() + assert '\n'.join(unified_diff(log_before, log_after)) == '' diff --git a/tests/bugs/core_2988_test.py b/tests/bugs/core_2988_test.py index 6609470f..8ef7e465 100644 --- a/tests/bugs/core_2988_test.py +++ b/tests/bugs/core_2988_test.py @@ -2,7 +2,7 @@ # # id: bugs.core_2988 # title: Concurrent transaction number not reported if lock timeout occurs -# decription: +# decription: # 08-aug-2018. # ::: ACHTUNG ::: # Important change has been added in FB 4.0. @@ -16,29 +16,32 @@ # as ticket says in its case-1: # === # set transaction read committed no record_version lock timeout 10; -# select * from test; +# select * from test; # === # For this reason it was decided to create separate section for major version 4.0 -# and use UPDATE statement instead of 'select * from test' (UPDATE also must READ +# and use UPDATE statement instead of 'select * from test' (UPDATE also must READ # data before changing). -# +# # Checked on: # FB25SC, build 2.5.9.27115: OK, 3.750s. # FB30SS, build 3.0.4.33022: OK, 4.343s. # FB40SS, build 4.0.0.1154: OK, 4.875s. -# +# # tracker_id: CORE-2988 # min_versions: ['2.5.4'] # versions: 3.0, 4.0 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, isql_act, Action, python_act +from firebird.driver import TPB, TraAccessMode, Isolation # version: 3.0 # resources: None -substitutions_1 = [('record not found for user:.*', ''), ('-concurrent transaction number is.*', '-concurrent transaction number is'), ('-At block line: [\\d]+, col: [\\d]+', '-At block line')] +substitutions_1 = [('record not found for user:.*', ''), + ('-concurrent transaction number is.*', '-concurrent transaction number is'), + ('-At block line: [\\d]+, col: [\\d]+', '-At block line')] init_script_1 = """""" @@ -49,7 +52,7 @@ test_script_1 = """ commit; create user tmp$2988 password 'tmp$2988'; commit; - + recreate table test (id integer); insert into test values(1); commit; @@ -60,7 +63,7 @@ test_script_1 = """ set transaction lock timeout 1; update test set id = id; - + set term ^; execute block as declare v_usr char(31) = 'tmp$2988'; @@ -76,10 +79,10 @@ test_script_1 = """ ^ set term ;^ rollback; - + set transaction read committed no record_version lock timeout 1; update test set id = id; - + set term ^; execute block as declare v_usr char(31) = 'tmp$2988'; @@ -95,11 +98,11 @@ test_script_1 = """ ^ set term ;^ rollback; - + set list on; set transaction read committed no record_version lock timeout 1; select id from test with lock; - + set term ^; execute block as declare v_usr char(31) = 'tmp$2988'; @@ -114,7 +117,7 @@ test_script_1 = """ begin end end - + execute statement ('select id from test with lock') with autonomous transaction as user v_usr password v_pwd @@ -155,7 +158,8 @@ act_1 = isql_act('db_1', test_script_1, substitutions=substitutions_1) expected_stdout_1 = """ ID 1 - """ +""" + expected_stderr_1 = """ Statement failed, SQLSTATE = HY000 record not found for user: TMP$2988 @@ -177,7 +181,7 @@ expected_stderr_1 = """ -read conflicts with concurrent update -concurrent transaction number is 24 -At block line: 7, col: 9 - """ +""" @pytest.mark.version('>=3.0,<4.0') def test_1(act_1: Action): @@ -190,7 +194,8 @@ def test_1(act_1: Action): # version: 4.0 # resources: None -substitutions_2 = [('^((?!concurrent transaction number is).)*$', ''), ('[\\-]{0,1}concurrent transaction number is [0-9]+', 'concurrent transaction number is')] +substitutions_2 = [('^((?!concurrent transaction number is).)*$', ''), + ('[\\-]{0,1}concurrent transaction number is [0-9]+', 'concurrent transaction number is')] init_script_2 = """ create table test(id int, x int, constraint test_pk primary key(id) using index test_pk); @@ -203,42 +208,42 @@ db_2 = db_factory(sql_dialect=3, init=init_script_2) # test_script_2 #--- -# +# # import os # db_conn.close() -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # con = fdb.connect(dsn = dsn) -# -# tx1 = con.trans() +# +# tx1 = con.trans() # tx1.begin() # cur1=tx1.cursor() # cur1.execute( 'update test set x = ? where id = ?', (222, 1) ) -# -# +# +# # # **INSTEAD** of ticket case-1: # # set transaction read committed no record_version lock timeout N; # # -- we start Tx with lock_timeout using custom TPB and try just to **update** record which is locked now # # (but NOT 'SELECT ...'! It is useless with default value of confign parameter ReadConsistency = 1). # # Message about concurrent transaction (which holds lock) in any case must appear in exception text. # # NB: NO_rec_version is USELESS in default FB 4.0 config! -# -# +# +# # # Linsk to doc for creating instance of custom TPB and start transaction which using it: # # https://pythonhosted.org/fdb/reference.html#fdb.TPB # # https://pythonhosted.org/fdb/reference.html#fdb.Connection.trans -# +# # custom_tpb = fdb.TPB() # custom_tpb.access_mode = fdb.isc_tpb_write # custom_tpb.isolation_level = (fdb.isc_tpb_read_committed, fdb.isc_tpb_rec_version) # ::: NB ::: NO_rec_version is USELESS in default FB 4.0 config! # custom_tpb.lock_timeout = 1 -# -# tx2 = con.trans( default_tpb = custom_tpb ) +# +# tx2 = con.trans( default_tpb = custom_tpb ) # tx2.begin() # cur2=tx2.cursor() -# +# # try: # cur2.execute( 'update test set x = ? where id = ?', (333, 1) ) # except Exception,e: @@ -249,21 +254,21 @@ db_2 = db_factory(sql_dialect=3, init=init_script_2) # print( '-'*30 ) # finally: # tx2.commit() -# +# # #---------------------------------------------------------- -# +# # # This is for ticket case-2: # # set transaction read committed lock timeout N; -# # select * from test with lock; -# +# # select * from test with lock; +# # custom_tpb.access_mode = fdb.isc_tpb_write # custom_tpb.isolation_level = fdb.isc_tpb_concurrency # custom_tpb.lock_timeout = 1 -# -# tx3 = con.trans( default_tpb = custom_tpb ) +# +# tx3 = con.trans( default_tpb = custom_tpb ) # tx3.begin() # cur3=tx3.cursor() -# +# # try: # cur3.execute( 'select x from test where id = ? with lock', (1,) ) # for r in cur3: @@ -276,22 +281,69 @@ db_2 = db_factory(sql_dialect=3, init=init_script_2) # print( '-'*30 ) # finally: # tx3.commit() -# +# # tx1.commit() # con.close() -# -# +# +# #--- -#act_2 = python_act('db_2', test_script_2, substitutions=substitutions_2) + +act_2 = python_act('db_2', substitutions=substitutions_2) expected_stdout_2 = """ - - concurrent transaction number is 13 - - concurrent transaction number is 13 - """ +concurrent transaction number is 13 +concurrent transaction number is 13 +""" @pytest.mark.version('>=4.0') -@pytest.mark.xfail -def test_2(db_2): - pytest.fail("Test not IMPLEMENTED") - - +def test_2(act_2: Action, capsys): + with act_2.db.connect() as con: + tx1 = con.transaction_manager() + tx1.begin() + cur1 = tx1.cursor() + cur1.execute('update test set x = ? where id = ?', (222, 1)) + # **INSTEAD** of ticket case-1: + # set transaction read committed no record_version lock timeout N; + # -- we start Tx with lock_timeout using custom TPB and try just to **update** record which is locked now + # (but NOT 'SELECT ...'! It is useless with default value of confign parameter ReadConsistency = 1). + # Message about concurrent transaction (which holds lock) in any case must appear in exception text. + # NB: NO_rec_version is USELESS in default FB 4.0 config! + custom_tpb = TPB(access_mode=TraAccessMode.WRITE, + isolation=Isolation.READ_COMMITTED_RECORD_VERSION, + lock_timeout=1) + tx2 = con.transaction_manager(default_tpb=custom_tpb.get_buffer()) + tx2.begin() + cur2 = tx2.cursor() + try: + cur2.execute('update test set x = ? where id = ?', (333, 1)) + except Exception as e: + print('Exception in cur2:') + print('-' * 30) + for x in e.args: + print(x) + print('-' * 30) + finally: + tx2.commit() + # This is for ticket case-2: + # set transaction read committed lock timeout N; + # select * from test with lock; + custom_tpb.isolation = Isolation.CONCURRENCY + tx3 = con.transaction_manager(default_tpb=custom_tpb.get_buffer()) + tx3.begin() + cur3 = tx3.cursor() + try: + cur3.execute('select x from test where id = ? with lock', (1,)) + for r in cur3: + print(r[0]) + except Exception as e: + print('Exception in cur3:') + print('-' * 30) + for x in e.args: + print(x) + print('-' * 30) + finally: + tx3.commit() + tx1.commit() + act_2.expected_stdout = expected_stdout_2 + act_2.stdout = capsys.readouterr().out + assert act_2.clean_stdout == act_2.clean_expected_stdout diff --git a/tests/bugs/core_3008_test.py b/tests/bugs/core_3008_test.py index bc0ec42f..621e4cc8 100644 --- a/tests/bugs/core_3008_test.py +++ b/tests/bugs/core_3008_test.py @@ -2,30 +2,32 @@ # # id: bugs.core_3008 # title: Add attachment's CHARACTER SET name into corresponding trace records -# decription: +# decription: # 1. Obtain engine_version from built-in context variable. # 2. Make config for trace in proper format according to FB engine version, # with adding invalid element 'foo' instead on boolean ('true' or 'false') # 3. Launch trace session in separate child process using 'FBSVCMGR action_trace_start' # 4. Run ISQL single 'QUIT;' command in order trace session will register connection. # 5. Stop trace session. Output its log with filtering only messages related to connect/disconnect event. -# +# # Checked on: WI-V2.5.5.26916 (SS, SC, CS); WI-V3.0.0.32008 (SS, SC, CS). Result: OK. # ::: NB ::: # Several delays (time.sleep) added in main thread because of OS buffering. Couldn't switch this buffering off. -# +# # tracker_id: CORE-3008 # min_versions: ['2.5.0'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from threading import Thread +from firebird.qa import db_factory, python_act, Action # version: 2.5 # resources: None -substitutions_1 = [('^((?!ERROR|ELEMENT|SYSDBA:NONE).)*$', ''), ('.*SYSDBA:NONE', 'SYSDBA:NONE'), ('TCPV.*', 'TCP')] +substitutions_1 = [('^((?!ERROR|ELEMENT|SYSDBA:NONE).)*$', ''), + ('.*SYSDBA:NONE', 'SYSDBA:NONE'), ('TCPV.*', 'TCP')] init_script_1 = """""" @@ -37,32 +39,32 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # import subprocess # from subprocess import Popen # import time -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# -# +# +# # # Obtain engine version, 2.5 or 3.0, for make trace config in appropriate format: # engine=str(db_conn.engine_version) # db_conn.close() -# +# # #--------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -70,9 +72,9 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # os.remove( f_names_list[i] ) # if os.path.isfile( f_names_list[i]): # print('ERROR: can not remove file ' + f_names_list[i]) -# +# # #-------------------------------------------- -# +# # txt25 = '''# Trace config, format for 2.5. Generated auto, do not edit! # # enabled true @@ -80,12 +82,12 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # log_connections true # # ''' -# +# # # NOTES ABOUT TRACE CONFIG FOR 3.0: # # 1) Header contains `database` clause in different format vs FB 2.5: its data must be enclosed with '{' '}' # # 2) Name and value must be separated by EQUALITY sign ('=') in FB-3 trace.conf, otherwise we get runtime error: # # element "<. . .>" have no attribute value set -# +# # txt30 = '''# Trace config, format for 3.0. Generated auto, do not edit! # database=%[\\\\\\\\/]bugs.core_3008.fdb # { @@ -94,38 +96,38 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # log_connections = true # } # ''' -# +# # f_trccfg=open( os.path.join(context['temp_directory'],'tmp_trace_3008.cfg'), 'w') # if engine.startswith('2.5'): # f_trccfg.write(txt25) # else: # f_trccfg.write(txt30) # f_trccfg.close() -# +# # ##################################################### # # Starting trace session in new child process (async.): -# +# # f_trclog = open( os.path.join(context['temp_directory'],'tmp_trace_3008.log'), 'w') # # Execute a child program in a new process, redirecting STDERR to the same target as of STDOUT: # p_trace=Popen([context['fbsvcmgr_path'], "localhost:service_mgr", # "action_trace_start", # "trc_cfg", f_trccfg.name], # stdout=f_trclog, stderr=subprocess.STDOUT) -# +# # # Wait! Trace session is initialized not instantly! # time.sleep(1) -# +# # sqltxt='''quit;''' -# +# # runProgram('isql',[dsn,'-ch','utf8'],sqltxt) # runProgram('isql',[dsn,'-ch','iso8859_1'],sqltxt) -# +# # # do NOT remove this otherwise trace log can contain only message about its start before being closed! # time.sleep(3) -# +# # ##################################################### # # Getting ID of launched trace session and STOP it: -# +# # # Save active trace session info into file for further parsing it and obtain session_id back (for stop): # f_trclst = open( os.path.join(context['temp_directory'],'tmp_trace_3008.lst'), 'w') # subprocess.call([context['fbsvcmgr_path'], "localhost:service_mgr", @@ -133,7 +135,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_trclst, stderr=subprocess.STDOUT # ) # flush_and_close( f_trclst ) -# +# # trcssn=0 # with open( f_trclst.name,'r') as f: # for line in f: @@ -144,7 +146,7 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # trcssn=word # i=i+1 # break -# +# # # Result: `trcssn` is ID of active trace session. Now we have to terminate it: # f_trclst=open(f_trclst.name,'a') # f_trclst.seek(0,2) @@ -154,40 +156,66 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_trclst, stderr=subprocess.STDOUT # ) # flush_and_close( f_trclst ) -# +# # # Terminate child process of launched trace session (though it should already be killed): # p_trace.terminate() -# +# # flush_and_close( f_trclog ) -# -# +# +# # # Output log of trace for comparing it with expected: it must contain only TWO events: TRACE_INIT and TRACE_FINI # # ::: NB ::: Content if trace log is converted to UPPER case in order to reduce change of mismatching with # # updated trace output in some future versions: # with open( f_trclog.name,'r') as f: # print(f.read().upper()) -# -# +# +# # # do NOT remove this delay otherwise get access error 'Windows 32' # # (The process cannot access the file because it is being used by another process): # time.sleep(1) -# +# # cleanup( [i.name for i in (f_trccfg, f_trclst, f_trclog ) ] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ SYSDBA:NONE, UTF8, TCP SYSDBA:NONE, UTF8, TCP SYSDBA:NONE, ISO88591, TCP SYSDBA:NONE, ISO88591, TCP - """ - -@pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +""" +def trace_session(act: Action): + cfg30 = ['# Trace config, format for 3.0. Generated auto, do not edit!', + f'database=%[\\\\/]{act.db.db_path.name}', + '{', + ' enabled = true', + ' log_connections = true', + ' time_threshold = 0', + '}'] + with act.connect_server() as srv: + srv.trace.start(config='\n'.join(cfg30)) + for line in srv: + print(line.upper()) +@pytest.mark.version('>=3.0') +def test_1(act_1: Action, capsys): + trace_thread = Thread(target=trace_session, args=[act_1]) + trace_thread.start() + # make two connections with different charset + with act_1.db.connect(charset='utf8'): + pass + with act_1.db.connect(charset='iso8859_1'): + pass + with act_1.connect_server() as srv: + for session in list(srv.trace.sessions.keys()): + srv.trace.stop(session_id=session) + trace_thread.join(1.0) + if trace_thread.is_alive(): + pytest.fail('Trace thread still alive') + act_1.expected_stdout = expected_stdout_1 + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_3024_test.py b/tests/bugs/core_3024_test.py index 313d7af8..f8628461 100644 --- a/tests/bugs/core_3024_test.py +++ b/tests/bugs/core_3024_test.py @@ -2,18 +2,18 @@ # # id: bugs.core_3024 # title: Error "no current record for fetch operation" after ALTER VIEW -# decription: +# decription: # Confirmed error on: WI-V2.5.6.26962 (SC), fixed on: WI-V2.5.6.26963. # Checked on WI-V3.0.0.32268 (SS, SC, CS). # Checked on fdb version 1.5. -# +# # tracker_id: CORE-3024 # min_versions: ['2.5.6'] # versions: 2.5.6 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5.6 # resources: None @@ -27,44 +27,61 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- # db_conn.close() -# +# # att1=kdb.connect(dsn=dsn.encode(),user='SYSDBA',password='masterkey') # att2=kdb.connect(dsn=dsn.encode(),user='SYSDBA',password='masterkey') -# +# # trn1=att1.trans() -# +# # cur1=trn1.cursor() -# +# # cur1.execute("create table t(a int, b int, c int)") # att_12, tra_4 # cur1.execute("create view v as select a,b from t") # trn1.commit() -# +# # cur1.execute("insert into t values(1,2,3)") # att_12, tra_5 # cur1.execute("select * from v") # trn1.commit() -# +# # trn2=att2.trans() # cur2=trn2.cursor() # cur2.execute("select * from v") # att_13, tra_7 # trn2.commit() -# +# # cur1.execute("alter view v as select a, b, c from t") # att-12, tra_8 # trn1.commit() -# +# # cur2.execute("select * from v") # att_13, tra_9 # printData(cur2) -# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ A B C 1 2 3 - """ +""" @pytest.mark.version('>=2.5.6') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action, capsys): + with act_1.db.connect() as att1, act_1.db.connect() as att2: + trn1 = att1.transaction_manager() + cur1 = trn1.cursor() + cur1.execute("create table t(a int, b int, c int)") # att_12, tra_4 + cur1.execute("create view v as select a,b from t") + trn1.commit() + cur1.execute("insert into t values(1,2,3)") # att_12, tra_5 + cur1.execute("select * from v") + trn1.commit() + trn2 = att2.transaction_manager() + cur2 = trn2.cursor() + cur2.execute("select * from v") # att_13, tra_7 + trn2.commit() + cur1.execute("alter view v as select a, b, c from t") # att-12, tra_8 + trn1.commit() + cur2.execute("select * from v") # att_13, tra_9 + act_1.print_data(cur2) + act_1.expected_stdout = expected_stdout_1 + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_3095_test.py b/tests/bugs/core_3095_test.py index 7262dafa..7dc1b46c 100644 --- a/tests/bugs/core_3095_test.py +++ b/tests/bugs/core_3095_test.py @@ -2,23 +2,23 @@ # # id: bugs.core_3095 # title: Client receive event's with count equal to 1 despite of how many times EVENT was POSTed in same transaction -# decription: +# decription: # We create stored procedure as it was specified in the ticket, and call it with input arg = 3. # This mean that we have to receive THREE events after code which calls this SP will issue COMMIT. -# +# # Confirmed on 2.5.0.26074 - only one event was delivered instead of three. # Works OK since 2.5.1.26351. -# -# PS. Event handling code in this text was adapted from fdb manual: +# +# PS. Event handling code in this text was adapted from fdb manual: # http://pythonhosted.org/fdb/usage-guide.html#database-events -# +# # tracker_id: CORE-3095 # min_versions: ['2.5.1'] # versions: 2.5.1 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from firebird.qa import db_factory, python_act, Action # version: 2.5.1 # resources: None @@ -38,7 +38,7 @@ init_script_1 = """ ^ set term ;^ commit; - """ +""" db_1 = db_factory(sql_dialect=3, init=init_script_1) @@ -46,55 +46,61 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) #--- # import os # import threading -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # # Utility function # def send_events(command_list): # cur = db_conn.cursor() # for cmd in command_list: # cur.execute(cmd) # db_conn.commit() -# +# # timed_event = threading.Timer(3.0, send_events, args=[["execute procedure sp_test(3)",]]) -# -# # Connection.event_conduit() takes a sequence of string event names as parameter, and returns +# +# # Connection.event_conduit() takes a sequence of string event names as parameter, and returns # # EventConduit instance. # events = db_conn.event_conduit(['loop']) -# -# # To start listening for events it's necessary (starting from FDB version 1.4.2) +# +# # To start listening for events it's necessary (starting from FDB version 1.4.2) # # to call EventConduit.begin() method or use EventConduit's context manager interface -# # Immediately when begin() method is called, EventConduit starts to accumulate notifications -# # of any events that occur within the conduit's internal queue until the conduit is closed +# # Immediately when begin() method is called, EventConduit starts to accumulate notifications +# # of any events that occur within the conduit's internal queue until the conduit is closed # # (via the close() method) -# +# # events.begin() -# +# # timed_event.start() -# -# # Notifications about events are aquired through call to wait() method, that blocks the calling -# # thread until at least one of the events occurs, or the specified timeout (if any) expires, +# +# # Notifications about events are aquired through call to wait() method, that blocks the calling +# # thread until at least one of the events occurs, or the specified timeout (if any) expires, # # and returns None if the wait timed out, or a dictionary that maps event_name -> event_occurrence_count. -# +# # e = events.wait(10) -# +# # events.close() -# +# # print(e) # db_conn.close() -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ - {'loop': 3} - """ - -@pytest.mark.version('>=2.5.1') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - + {'loop': 3} +""" +@pytest.mark.version('>=3.0') +def test_1(act_1: Action, capsys): + act_1.expected_stdout = expected_stdout_1 + with act_1.db.connect() as con: + c = con.cursor() + with con.event_collector(['loop']) as events: + c.execute('execute procedure sp_test(3)') + con.commit() + print(events.wait(10)) + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_3131_test.py b/tests/bugs/core_3131_test.py index 6a3f2836..d091b640 100644 --- a/tests/bugs/core_3131_test.py +++ b/tests/bugs/core_3131_test.py @@ -2,7 +2,7 @@ # # id: bugs.core_3131 # title: WIN1257_LV (Latvian) collation is wrong for 4 letters: A E I U. -# decription: +# decription: # ::: NOTE ::: # In order to check correctness of following statements under ISQL itself (NOT under fbt_run), do following: # 1) open some text editor that supports charset = win1257 and set encoding for new document = WIN1257 @@ -19,27 +19,28 @@ # 4) save .fbt and ensure that it was saved in UTF8 encoding, otherwise exeption like # "UnicodeDecodeError: 'utf8' codec can't decode byte 0xc3 in position 621: invalid continuation byte" # will raise. -# +# # 05-mar-2021. Re-implemented in order to have ability to run this test on Linux. # Test encodes to UTF8 all needed statements (SET NAMES; CONNECT; DDL and DML) and stores this text in .sql file. # NOTE: 'SET NAMES' contain character set that must be used for reproducing problem (WIN1257 in this test). # Then ISQL is launched in separate (child) process which performs all necessary actions (using required charset). # Result will be redirected to log(s) which will be opened further via codecs.open(...encoding='cp1257'). # Finally, its content will be converted to UTF8 for showing in expected_stdout. -# +# # Confirmed bug on 2.5.0.26074. Fixed on 2.5.1.26351 and up to 2.5.9.27152 -# +# # Checked on: # * Windows: 4.0.0.2377, 3.0.8.33423 # * Linux: 4.0.0.2379, 3.0.8.33415 -# +# # tracker_id: CORE-3131 # min_versions: ['2.5.0'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from pathlib import Path +from firebird.qa import db_factory, python_act, Action, temp_file # version: 2.5 # resources: None @@ -52,31 +53,31 @@ db_1 = db_factory(charset='WIN1257', sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import codecs # import subprocess # import time # engine = db_conn.engine_version # db_conn.close() -# +# # #-------------------------------------------- -# +# # def flush_and_close( file_handle ): # # https://docs.python.org/2/library/os.html#os.fsync # # If you're starting with a Python file object f, # # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb') and file_handle.name != os.devnull: # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -87,43 +88,43 @@ db_1 = db_factory(charset='WIN1257', sql_dialect=3, init=init_script_1) # else: # print('Unrecognized type of element:', f_names_list[i], ' - can not be treated as file.') # del_name = None -# +# # if del_name and os.path.isfile( del_name ): # os.remove( del_name ) -# +# # #-------------------------------------------- -# +# # sql_txt=''' set bail on; # set names win1257; # connect '%(dsn)s' user '%(user_name)s' password '%(user_password)s'; -# +# # create collation coll_1257_ci_ai # for win1257 from win1257_lv # no pad case insensitive accent sensitive; # commit; -# +# # create table test1257 ( # letter varchar(2) collate coll_1257_ci_ai, # sort_index smallint # ); -# +# # -- ### ONCE AGAIN ### # -- 1) for checking this under ISQL following must be encoded in WIN1257 # -- 2) for running under fbt_run utility following must be encoded in UTF8. # insert into test1257 values ('Iz', 18); # insert into test1257 values ('Īb', 19); # insert into test1257 values ('Īz', 20); -# +# # insert into test1257 values ('Ķz', 24); # insert into test1257 values ('Ēz', 12); # insert into test1257 values ('Gb', 13); -# +# # insert into test1257 values ('Ģz', 16); # insert into test1257 values ('Ib', 17); -# +# # insert into test1257 values ('Gz', 14); # insert into test1257 values ('Ģb', 15); -# +# # insert into test1257 values ('Ņb', 31); # insert into test1257 values ('Ņz', 32); # insert into test1257 values ('Cb', 5); @@ -133,10 +134,10 @@ db_1 = db_factory(charset='WIN1257', sql_dialect=3, init=init_script_1) # insert into test1257 values ('Eb', 9); # insert into test1257 values ('Ez', 10); # insert into test1257 values ('Ēb', 11); -# +# # insert into test1257 values ('Ub', 37); # insert into test1257 values ('Uz', 38); -# +# # insert into test1257 values ('Lz', 26); # insert into test1257 values ('Ļb', 27); # insert into test1257 values ('Ļz', 28); @@ -147,108 +148,195 @@ db_1 = db_factory(charset='WIN1257', sql_dialect=3, init=init_script_1) # insert into test1257 values ('Cz', 6); # insert into test1257 values ('Čb', 7); # insert into test1257 values ('Čz', 8); -# +# # insert into test1257 values ('Sb', 33); # insert into test1257 values ('Sz', 34); # insert into test1257 values ('Šb', 35); -# +# # insert into test1257 values ('Nb', 29); # insert into test1257 values ('Nz', 30); # insert into test1257 values ('Ķb', 23); # insert into test1257 values ('Zz', 42); # insert into test1257 values ('Žb', 43); # insert into test1257 values ('Žz', 44); -# +# # insert into test1257 values ('Ab', 1); # insert into test1257 values ('Az', 2); # insert into test1257 values ('Āb', 3); -# insert into test1257 values ('Āz', 4); +# insert into test1257 values ('Āz', 4); # commit; -# +# # set heading off; # select * # from test1257 tls # order by tls.letter collate coll_1257_ci_ai; -# +# # ''' % dict(globals(), **locals()) -# +# # f_run_sql = open( os.path.join(context['temp_directory'], 'tmp_3131_win1257.sql'), 'w' ) # f_run_sql.write( sql_txt.decode('utf8').encode('cp1257') ) # flush_and_close( f_run_sql ) -# +# # # result: file tmp_3131_win1257.sql is encoded in win1257 -# +# # f_run_log = open( os.path.splitext(f_run_sql.name)[0]+'.log', 'w') # subprocess.call( [ context['isql_path'], '-q', '-i', f_run_sql.name ], # stdout = f_run_log, # stderr = subprocess.STDOUT # ) # flush_and_close( f_run_log ) # result: output will be encoded in win1257 -# +# # with codecs.open(f_run_log.name, 'r', encoding='cp1257' ) as f: # result_in_win1257 = f.readlines() -# +# # for i in result_in_win1257: # print( i.encode('utf8') ) -# +# # # cleanup: # ########### # cleanup( (f_run_sql, f_run_log) ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) + +test_script_1 = """ + set bail on; + set names win1257; + + create collation coll_1257_ci_ai + for win1257 from win1257_lv + no pad case insensitive accent sensitive; + + commit; + + create table test1257 ( + letter varchar(2) collate coll_1257_ci_ai, + sort_index smallint + ); + + commit; + + -- ### ONCE AGAIN ### + -- 1) for checking this under ISQL following must be encoded in WIN1257 + -- 2) for running under fbt_run utility following must be encoded in UTF8. + + insert into test1257 values ('Iz', 18); + insert into test1257 values ('Īb', 19); + insert into test1257 values ('Īz', 20); + + insert into test1257 values ('Ķz', 24); + insert into test1257 values ('Ēz', 12); + insert into test1257 values ('Gb', 13); + + insert into test1257 values ('Ģz', 16); + insert into test1257 values ('Ib', 17); + + insert into test1257 values ('Gz', 14); + insert into test1257 values ('Ģb', 15); + + insert into test1257 values ('Ņb', 31); + insert into test1257 values ('Ņz', 32); + insert into test1257 values ('Cb', 5); + insert into test1257 values ('Ūb', 39); + insert into test1257 values ('Ūz', 40); + insert into test1257 values ('Zb', 41); + insert into test1257 values ('Eb', 9); + insert into test1257 values ('Ez', 10); + insert into test1257 values ('Ēb', 11); + + insert into test1257 values ('Ub', 37); + insert into test1257 values ('Uz', 38); + + insert into test1257 values ('Lz', 26); + insert into test1257 values ('Ļb', 27); + insert into test1257 values ('Ļz', 28); + insert into test1257 values ('Kb', 21); + insert into test1257 values ('Kz', 22); + insert into test1257 values ('Šz', 36); + insert into test1257 values ('Lb', 25); + insert into test1257 values ('Cz', 6); + insert into test1257 values ('Čb', 7); + insert into test1257 values ('Čz', 8); + + insert into test1257 values ('Sb', 33); + insert into test1257 values ('Sz', 34); + insert into test1257 values ('Šb', 35); + + insert into test1257 values ('Nb', 29); + insert into test1257 values ('Nz', 30); + insert into test1257 values ('Ķb', 23); + insert into test1257 values ('Zz', 42); + insert into test1257 values ('Žb', 43); + insert into test1257 values ('Žz', 44); + + insert into test1257 values ('Ab', 1); + insert into test1257 values ('Az', 2); + insert into test1257 values ('Āb', 3); + insert into test1257 values ('Āz', 4); + commit; + + set heading off; + select * + from test1257 tls + order by tls.letter collate coll_1257_ci_ai; +""" expected_stdout_1 = """ - Ab 1 - Az 2 - Āb 3 - Āz 4 - Cb 5 - Cz 6 - Čb 7 - Čz 8 - Eb 9 - Ez 10 - Ēb 11 - Ēz 12 - Gb 13 - Gz 14 - Ģb 15 - Ģz 16 - Ib 17 - Iz 18 - Īb 19 - Īz 20 - Kb 21 - Kz 22 - Ķb 23 - Ķz 24 - Lb 25 - Lz 26 - Ļb 27 - Ļz 28 - Nb 29 - Nz 30 - Ņb 31 - Ņz 32 - Sb 33 - Sz 34 - Šb 35 - Šz 36 - Ub 37 - Uz 38 - Ūb 39 - Ūz 40 - Zb 41 - Zz 42 - Žb 43 - Žz 44 + Ab 1 + Az 2 + Āb 3 + Āz 4 + Cb 5 + Cz 6 + Čb 7 + Čz 8 + Eb 9 + Ez 10 + Ēb 11 + Ēz 12 + Gb 13 + Gz 14 + Ģb 15 + Ģz 16 + Ib 17 + Iz 18 + Īb 19 + Īz 20 + Kb 21 + Kz 22 + Ķb 23 + Ķz 24 + Lb 25 + Lz 26 + Ļb 27 + Ļz 28 + Nb 29 + Nz 30 + Ņb 31 + Ņz 32 + Sb 33 + Sz 34 + Šb 35 + Šz 36 + Ub 37 + Uz 38 + Ūb 39 + Ūz 40 + Zb 41 + Zz 42 + Žb 43 + Žz 44 """ + +script_file = temp_file('test-script.sql') + @pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - +def test_1(act_1: Action, script_file: Path): + script_file.write_text(test_script_1, encoding='cp1257') + act_1.expected_stdout = expected_stdout_1 + act_1.isql(switches=[], input_file=script_file, charset='WIN1257') + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_3168_test.py b/tests/bugs/core_3168_test.py index 721484d9..237ad524 100644 --- a/tests/bugs/core_3168_test.py +++ b/tests/bugs/core_3168_test.py @@ -2,38 +2,41 @@ # # id: bugs.core_3168 # title: exclude_filter doesn't work for section of the Trace facility -# decription: +# decription: # Note. It was encountered that FBSVCMGR does NOT wait for OS completes writing of its output on disk, # (see CORE-4896), so we use delays - see calls of time.sleep(). -# +# # Correct work was checked on: WI-V2.5.5.26916 (SS, SC) and WI-V3.0.0.31948 (SS, SC, CS) # Refactored 17.12.2016 after encountering CORE-5424 ("restore process is impossible when trace ..."): # added checking of STDERR logs for all fbsvcmgr actions. -# +# # ----------------------------------------- # Updated 27.03.2017: moved artificial delay (1 sec) at proper place. # It should be just after # subprocess.call('fbsvcmgr', 'localhost:service_mgr', 'action_trace_stop', ...) # and BEFORE 'p_trace.terminate()' command. # ----------------------------------------- -# +# # Test time (approx): -# 2.5.7.27030: SC = 3" +# 2.5.7.27030: SC = 3" # 3.0.232644 and 4.0.0.463: SS = SC = 6"; CS = 15" # Checked on 2.5.8.27056, CLASSIC server: 3.6" (27-03-2017) -# +# # 13.04.2021. Adapted for run both on Windows and Linux. Checked on: # Windows: 3.0.8.33445, 4.0.0.2416 # Linux: 3.0.8.33426, 4.0.0.2416 -# -# +# +# # tracker_id: CORE-3168 # min_versions: ['2.5.0'] # versions: 2.5 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from threading import Thread, Barrier +from io import BytesIO +from firebird.qa import db_factory, python_act, Action, temp_file +from firebird.driver import SrvStatFlag # version: 2.5 # resources: None @@ -46,37 +49,37 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import subprocess # import time # from fdb import services # from subprocess import Popen -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # # Obtain engine version: # engine = str(db_conn.engine_version) # convert to text because 'float' object has no attribute 'startswith' # db_conn.close() -# +# # #-------------------------------------------- -# +# # def flush_and_close( file_handle ): # # https://docs.python.org/2/library/os.html#os.fsync # # If you're starting with a Python file object f, # # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb') and file_handle.name != os.devnull: # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -88,13 +91,13 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # print('Unrecognized type of element:', f_names_list[i], ' - can not be treated as file.') # print('type(f_names_list[i])=',type(f_names_list[i])) # del_name = None -# +# # if del_name and os.path.isfile( del_name ): # os.remove( del_name ) -# +# # #-------------------------------------------- -# -# +# +# # # ::: NB ::: Trace config file format in 3.0 differs from 2.5 one: # # 1) header section must be enclosed in "[" and "]", # # 2) parameter-value pairs must be separated with '=' sign: @@ -102,16 +105,16 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # # { # # parameter = value # # } -# +# # if engine.startswith('2.5'): # txt = '''# Generated auto, do not edit! # # enabled true # log_services true -# +# # # This should prevent appearance of messages like "List Trace Session(s)" or "Start Trace Session(s)": # exclude_filter "%(List|LIST|list|Start|START|start)[[:WHITESPACE:]]+(Trace|TRACE|trace)[[:WHITESPACE:]]+(Session|SESSION|session)%" -# +# # # This should work even if we filter out messages about list/start trace session(s) # # (and test also check corret work of THIS filter beside previous `exclude`): # # include_filter "Database Stats" @@ -123,68 +126,68 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # { # enabled = true # log_services = true -# +# # # This should prevent appearance of messages like "List Trace Session(s)" or "Start Trace Session(s)": # exclude_filter = "%(List|LIST|list|Start|START|start)[[:WHITESPACE:]]+(Trace|TRACE|trace)[[:WHITESPACE:]]+(Session|SESSION|session)%" -# +# # # This should work even if we filter out messages about list/start trace session(s) # # (and test also check corret work of THIS filter beside previous `exclude`): # # include_filter = "Database Stats" # } # ''' -# +# # f_trc_cfg=open( os.path.join(context['temp_directory'],'tmp_trace_3168.cfg'), 'w') # f_trc_cfg.write(txt) # f_trc_cfg.close() -# +# # # Instead of using 'start /min cmd /c fbsvcmgr ... 1>%2 2>&1' deciced to exploite Popen in order to run asynchronous process # # without opening separate window. Output is catched into `trclog` file, which will be closed after call fbsvcmgr with argument # # 'action_trace_stop' (see below): # # See also: # # https://docs.python.org/2/library/subprocess.html # # http://stackoverflow.com/questions/11801098/calling-app-from-subprocess-call-with-arguments -# +# # # ############################################################## # # S T A R T T R A C E i n S E P A R A T E P R O C E S S # # ############################################################## -# +# # f_trc_log=open( os.path.join(context['temp_directory'],'tmp_trace_3168.log'), "w") # f_trc_err=open( os.path.join(context['temp_directory'],'tmp_trace_3168.err'), "w") -# +# # p_trace = Popen([ context['fbsvcmgr_path'], 'localhost:service_mgr', 'action_trace_start' , 'trc_cfg', f_trc_cfg.name],stdout=f_trc_log,stderr=f_trc_err) -# +# # thisdb='$(DATABASE_LOCATION)bugs.core_3168.fdb' # tmpbkp='$(DATABASE_LOCATION)bugs.core_3168_fbk.tmp' # tmpres='$(DATABASE_LOCATION)bugs.core_3168_new.tmp' -# +# # f_run_log=open( os.path.join(context['temp_directory'],'tmp_action_3168.log'), 'w') # f_run_err=open( os.path.join(context['temp_directory'],'tmp_action_3168.err'), 'w') -# +# # subprocess.call( [ context['fbsvcmgr_path'], 'localhost:service_mgr','action_properties','dbname', thisdb,'prp_sweep_interval', '1234321'], stdout=f_run_log,stderr=f_run_err) # subprocess.call( [ context['fbsvcmgr_path'], 'localhost:service_mgr','action_db_stats', 'dbname', thisdb, 'sts_hdr_pages'], stdout=f_run_log,stderr=f_run_err) # subprocess.call( [ context['fbsvcmgr_path'], 'localhost:service_mgr','action_backup', 'dbname', thisdb, 'bkp_file', tmpbkp], stdout=f_run_log,stderr=f_run_err) # subprocess.call( [ context['fbsvcmgr_path'], 'localhost:service_mgr','action_restore', 'bkp_file', tmpbkp, 'dbname', tmpres, 'res_replace'], stdout=f_run_log,stderr=f_run_err) -# +# # flush_and_close( f_run_log ) # flush_and_close( f_run_err ) -# +# # # do NOT try to get FB log! It can contain non-ascii messages which lead to runtime fault of fbtest! # # (see CORE-5418): # # runProgram(context['fbsvcmgr_path'],['localhost:service_mgr','action_get_fb_log']) -# -# +# +# # # #################################################### # # G E T A C T I V E T R A C E S E S S I O N I D # # #################################################### # # Save active trace session info into file for further parsing it and obtain session_id back (for stop): -# +# # f_trc_lst = open( os.path.join(context['temp_directory'],'tmp_trace_3168.lst'), 'w') # subprocess.call([context['fbsvcmgr_path'], 'localhost:service_mgr', 'action_trace_list'], stdout=f_trc_lst) # flush_and_close( f_trc_lst ) -# +# # # !!! DO NOT REMOVE THIS LINE !!! # time.sleep(1) -# +# # trcssn=0 # with open( f_trc_lst.name,'r') as f: # for line in f: @@ -196,76 +199,109 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # i=i+1 # break # # Result: `trcssn` is ID of active trace session. Now we have to terminate it: -# +# # # #################################################### # # S E N D R E Q U E S T T R A C E T O S T O P # # #################################################### -# +# # fn_nul = open(os.devnull, 'w') # subprocess.call([context['fbsvcmgr_path'], 'localhost:service_mgr', 'action_trace_stop','trc_id', trcssn], stdout=fn_nul) # fn_nul.close() -# +# # # ::: NB ::: Artificial delay, Added 27.03.2017. -# # Do NOT remove this line otherwise record with 'database restore' may not appear +# # Do NOT remove this line otherwise record with 'database restore' may not appear # # in the final trace log (file buffer is flushed not instantly). -# time.sleep(1) -# -# +# time.sleep(1) +# +# # # Doc about Popen.terminate(): # # https://docs.python.org/2/library/subprocess.html # # Stop the child. On Posix OSs the method sends SIGTERM to the child. # # On Windows the Win32 API function TerminateProcess() is called to stop the child. -# +# # # Doc about Win API TerminateProcess() function: # # https://msdn.microsoft.com/en-us/library/windows/desktop/ms686714%28v=vs.85%29.aspx # # The terminated process cannot exit until all pending I/O has been completed or canceled. # # TerminateProcess is ____asynchronous____; it initiates termination and returns immediately. # # ^^^^^^^^^^^^ -# +# # p_trace.terminate() # flush_and_close(f_trc_log) # flush_and_close(f_trc_err) -# +# # # Should be EMPTY: # with open( f_trc_err.name,'r') as f: # for line in f: # if line.split(): # print('fbsvcmgr(1) unexpected STDERR: '+line.upper() ) -# +# # # Should be EMPTY: # with open( f_run_err.name,'r') as f: # for line in f: # if line.split(): # print('fbsvcmgr(2) unexpected STDERR: '+line.upper() ) -# +# # # Output log of trace for comparing it with expected. # # ::: NB ::: Content if trace log is converted to UPPER case in order to reduce change of mismatching with # # updated trace output in some future versions: -# +# # with open( f_trc_log.name,'r') as f: # for line in f: # if line.split(): # print(line.upper()) -# +# # # CLEANUP # ######### # time.sleep(1) # cleanup( (f_trc_cfg, f_trc_lst, f_trc_log, f_trc_err, f_run_log, f_run_err, tmpbkp, tmpres) ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) + +act_1 = python_act('db_1', substitutions=substitutions_1) expected_stdout_1 = """ - "DATABASE PROPERTIES" - "DATABASE STATS" - "BACKUP DATABASE" - "RESTORE DATABASE" - """ + EXCLUDE_FILTER = "DATABASE STATS" + "DATABASE PROPERTIES" + "BACKUP DATABASE" +""" -@pytest.mark.version('>=2.5') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") +def trace_session(act: Action, barrier: Barrier): + cfg30 = ['services', + '{', + ' enabled = true', + ' log_services = true', + ' exclude_filter = "Database Stats"', + '}'] + with act.connect_server() as srv: + srv.trace.start(config='\n'.join(cfg30)) + barrier.wait() + for line in srv: + print(line.upper()) + +temp_file_1 = temp_file('test-file') + +@pytest.mark.version('>=3.0') +def test_1(act_1: Action, capsys, temp_file_1): + b = Barrier(2) + trace_thread = Thread(target=trace_session, args=[act_1, b]) + trace_thread.start() + with act_1.connect_server() as srv: + # Make some service requests + b.wait() + srv.database.set_sweep_interval(database=str(act_1.db.db_path), interval=1234321) + srv.database.get_statistics(database=str(act_1.db.db_path), flags=SrvStatFlag.HDR_PAGES) + srv.wait() + srv.database.backup(database=str(act_1.db.db_path), backup=str(temp_file_1)) + srv.wait() + # + for session in list(srv.trace.sessions.keys()): + srv.trace.stop(session_id=session) + trace_thread.join(2.0) + if trace_thread.is_alive(): + pytest.fail('Trace thread still alive') + act_1.expected_stdout = expected_stdout_1 + act_1.stdout = capsys.readouterr().out + assert act_1.clean_stdout == act_1.clean_expected_stdout diff --git a/tests/bugs/core_3188_test.py b/tests/bugs/core_3188_test.py index 1b9e3aa5..c4aa918c 100644 --- a/tests/bugs/core_3188_test.py +++ b/tests/bugs/core_3188_test.py @@ -2,7 +2,7 @@ # # id: bugs.core_3188 # title: page 0 is of wrong type (expected 6, found 1) -# decription: +# decription: # Confirmed on WI-V2.5.0.26074 # exception: # DatabaseError: @@ -13,21 +13,22 @@ # - page 0 is of wrong type (expected 6, found 1) # -902 # 335544335 -# +# # New messages in firebird.log in 2.5.0 after running ticket statements: -# +# # CSPROG (Client) Mon Feb 15 07:28:05 2016 # INET/inet_error: connect errno = 10061 # CSPROG Mon Feb 15 07:41:02 2016 # Shutting down the server with 0 active connection(s) to 0 database(s), 1 active service(s) -# +# # tracker_id: CORE-3188 # min_versions: ['2.5.1'] # versions: 2.5.1 # qmid: None import pytest -from firebird.qa import db_factory, isql_act, Action +from difflib import unified_diff +from firebird.qa import db_factory, python_act, Action # version: 2.5.1 # resources: None @@ -40,34 +41,34 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # test_script_1 #--- -# +# # import os # import difflib -# +# # os.environ["ISC_USER"] = user_name # os.environ["ISC_PASSWORD"] = user_password -# +# # engine = str(db_conn.engine_version) # db_conn.close() -# -# +# +# # #--------------------------------------------- -# +# # def flush_and_close(file_handle): # # https://docs.python.org/2/library/os.html#os.fsync -# # If you're starting with a Python file object f, -# # first do f.flush(), and +# # If you're starting with a Python file object f, +# # first do f.flush(), and # # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk. # global os -# +# # file_handle.flush() # if file_handle.mode not in ('r', 'rb'): # # otherwise: "OSError: [Errno 9] Bad file descriptor"! # os.fsync(file_handle.fileno()) # file_handle.close() -# +# # #-------------------------------------------- -# +# # def cleanup( f_names_list ): # global os # for i in range(len( f_names_list )): @@ -75,20 +76,20 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # os.remove( f_names_list[i] ) # if os.path.isfile( f_names_list[i]): # print('ERROR: can not remove file ' + f_names_list[i]) -# +# # #-------------------------------------------- -# +# # def svc_get_fb_log( engine, f_fb_log ): -# +# # import subprocess -# +# # # ::: NB ::: Service call for receive firebird.log works properly only since FB 2.5.2! -# +# # if engine.startswith('2.5'): # get_firebird_log_key='action_get_ib_log' # else: # get_firebird_log_key='action_get_fb_log' -# +# # subprocess.call([ context['fbsvcmgr_path'], # "localhost:service_mgr", # get_firebird_log_key @@ -96,73 +97,83 @@ db_1 = db_factory(sql_dialect=3, init=init_script_1) # stdout=f_fb_log, stderr=subprocess.STDOUT # ) # return -# +# # #-------------------------------------------- -# -# +# +# # # Start two attachments: # con1 = kdb.connect(dsn=dsn) # con2 = kdb.connect(dsn=dsn) -# +# # # Session-1: # c1 = con1.cursor() -# -# +# +# # f_fblog_before=open( os.path.join(context['temp_directory'],'tmp_3188_fblog_before.txt'), 'w') # svc_get_fb_log( engine, f_fblog_before ) # flush_and_close( f_fblog_before ) -# +# # c1.execute("create table test(id int primary key)") # con1.commit() -# +# # # Session-2: -# +# # c2 = con2.cursor() # c2.execute('drop table test') # con2.commit() -# +# # # cleanup # con1.close() # con2.close() -# +# # f_fblog_after=open( os.path.join(context['temp_directory'],'tmp_3188_fblog_after.txt'), 'w') # svc_get_fb_log( engine, f_fblog_after ) # flush_and_close( f_fblog_after ) -# +# # # Now we can compare two versions of firebird.log and check their difference. -# +# # oldfb=open(f_fblog_before.name, 'r') # newfb=open(f_fblog_after.name, 'r') -# +# # difftext = ''.join(difflib.unified_diff( -# oldfb.readlines(), +# oldfb.readlines(), # newfb.readlines() # )) # oldfb.close() # newfb.close() -# +# # f_diff_txt=open( os.path.join(context['temp_directory'],'tmp_3188_diff.txt'), 'w') # f_diff_txt.write(difftext) # flush_and_close( f_diff_txt ) -# +# # # Difference of firebird.log should be EMPTY: -# +# # with open( f_diff_txt.name,'r') as f: # print( f.read() ) # f.close() -# +# # ############################### # # Cleanup. # cleanup( [i.name for i in (f_fblog_before, f_fblog_after, f_diff_txt)] ) -# -# +# +# #--- -#act_1 = python_act('db_1', test_script_1, substitutions=substitutions_1) +act_1 = python_act('db_1', substitutions=substitutions_1) @pytest.mark.version('>=2.5.1') -@pytest.mark.xfail -def test_1(db_1): - pytest.fail("Test not IMPLEMENTED") - - +def test_1(act_1: Action): + with act_1.connect_server() as srv: + srv.info.get_log() + log_before = srv.readlines() + with act_1.db.connect() as con1, act_1.db.connect() as con2: + c1 = con1.cursor() + c1.execute("create table test(id int primary key)") + con1.commit() + # + c2 = con2.cursor() + c2.execute('drop table test') + con2.commit() + srv.info.get_log() + log_after = srv.readlines() + assert list(unified_diff(log_before, log_after)) == []