text: #coding:utf-8
  #
  # id:           bugs.core_4298
  # title:        fbsvcmgr doesn't recognise sts_record_versions and other sts switches
  # decription:
  #                  Test creates table and add 5 rows to it. Than we run in child async. process ISQL with EB which has ES/EDS
  #                  and will stay in pause due to update conflict in lock-timeout transaction (Python currently can not operate
  #                  with several attachments which open cursors with DML - this will crash).
  #                  When EB will be paused, we start another ISQL which will add one row to the same table and finish.
  #                  At this point table will have 5 versions and this should be shown in the output of fbsvcmgr when it is run
  #                  with 'action_db_stats sts_record_versions' keys.
  #                  Finally, we terminate hanged ISQL process and proceed with logs (STDOUR and STDERR) of fbsvcmgr.
  #                  Log of errors should be empty, log of STDOUT should contain text with non-zero number of versions.
  #                  Checked on:
  #                    WI-V2.5.5.26942 (SS), WI-V2.5.5.26952 (sC);
  #                    WI-V3.0.0.32239 (SS), WI-V3.0.0.32208 (Cs, sC).
  #                  ### NOTE ###
  #                  Classic keeps database file opened when hanged ISQL is killed by teminate(), thus leading to access error
  #                  when fbtest tries to remove database by dropping it (get "Windows error (32)"). For that reason we have
  #                  to allow ISQL that stays in pause to finish by Tx timeout expiration and close itself his own process.
  #
  #                  Checked on (28.10.2019):
  #                       4.0.0.1635 SS: 7.720s.
  #                       4.0.0.1633 CS: 7.378s.
  #                       3.0.5.33180 SS: 7.313s.
  #                       3.0.5.33178 CS: 6.720s.
  #                       2.5.9.27119 SS: 6.506s.
  #                       2.5.9.27146 SC: 5.388s.
  #
  #                  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-4298
  # min_versions: ['2.5.2']
  # versions:     2.5.2
  # qmid:         None
  
  import pytest
  from firebird.qa import db_factory, python_act, Action
  from firebird.driver import SrvStatFlag
  
  # version: 2.5.2
  # resources: None
  
  substitutions_1 = [('Average version length: [\\d]+.[\\d]+, total versions: 5, max versions: 1',
                      'total versions: 5, max versions: 1')]
  
  init_script_1 = """
      recreate table test(id int, x int);
      commit;
      insert into test values(1, 100);
      insert into test values(2, 200);
      insert into test values(3, 300);
      insert into test values(4, 400);
      insert into test values(5, 500);
      commit;
  """
  
  db_1 = db_factory(sql_dialect=3, init=init_script_1)
  
  # test_script_1
  #---
  # import os
  #  import time
  #  import subprocess
  #  from subprocess import Popen
  #  from fdb import services
  #
  #  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'
  #
  #  #--------------------------------------------
  #
  #  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 )):
  #         if type(f_names_list[i]) == file:
  #            del_name = f_names_list[i].name
  #         elif type(f_names_list[i]) == str:
  #            del_name = f_names_list[i]
  #         else:
  #            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 )
  #
  #  #--------------------------------------------
  #
  #  sqltxt='''
  #      set term ^;
  #      execute block as
  #      begin
  #        execute statement 'drop role TMP$R4298';
  #        when any do begin end
  #      end ^
  #      set term ;^
  #      commit;
  #
  #
  #      commit;
  #      set transaction lock timeout 15;
  #      update test set x = -x;
  #      set term ^;
  #      execute block as
  #      begin
  #          execute statement 'update test set x = -x'
  #          on external 'localhost:' || rdb$get_context('SYSTEM','DB_NAME')
  #          as user '%s' password '%s' role 'TMP$R4298';
  #      when any do
  #          begin
  #          end
  #      end
  #      ^
  #      set term ;^
  #      rollback;
  #
  #  ''' % (user_name, user_password)
  #
  #
  #  f_hanged_sql=open( os.path.join(context['temp_directory'],'tmp_4298_hang.sql'), 'w')
  #  f_hanged_sql.write(sqltxt)
  #  flush_and_close( f_hanged_sql )
  #
  #  f_hanged_log=open( os.path.join(context['temp_directory'],'tmp_4298_hang.log'), 'w')
  #
  #  # Execute a child program in a new process, redirecting STDERR to the same target as of STDOUT:
  #  p_hang = Popen([context['isql_path'], dsn, "-i" , f_hanged_sql.name], stdout=f_hanged_log, stderr=subprocess.STDOUT)
  #
  #  time.sleep(1)
  #
  #  runProgram('isql',[dsn],'insert into test(id, x) values(-1, -100); commit;')
  #
  #  this_fdb='$(DATABASE_LOCATION)bugs.core_4298.fdb'
  #
  #  f_stat_log=open( os.path.join(context['temp_directory'],'tmp_4298_dbstat.log'), 'w')
  #  f_stat_err=open( os.path.join(context['temp_directory'],'tmp_4298_dbstat.err'), 'w')
  #
  #  subprocess.call( [ context['fbsvcmgr_path'],"localhost:service_mgr",
  #                     "action_db_stats", "dbname", this_fdb,
  #                     "sts_record_versions"
  #                   ],
  #                   stdout=f_stat_log,
  #                   stderr=f_stat_err
  #                 )
  #
  #  flush_and_close( f_stat_log )
  #  flush_and_close( f_stat_err )
  #
  #  # do NOT remove this pause: Classic 3.0 keeps database opened even after we kill hanged ISQL by p_hang.terminate().
  #  # So we have to wait enough for 1st ISQL process that currently will hangs about 4 seconds to be CORRECTLY closed
  #  # by itself:
  #
  #  time.sleep(3)
  #
  #  # These kill and close commands are also needed here, despite that corresponding ISQL has been already closed itself.
  #  # It is so at least for Cs 3.0:
  #  p_hang.terminate()
  #  flush_and_close( f_hanged_log )
  #
  #  # ERRORS log of obtaining DB statistics should be EMPTY:
  #  with open( f_stat_err.name,'r') as f:
  #      for line in f:
  #          if line.split():
  #              print("UNEXPECTED STDERR for 'action_db_stats': " + line)
  #
  #  # STDOUT of obtaining DB statistics should NOT be EMPTY:
  #  with open(f_stat_log.name,'r') as f:
  #      for line in f:
  #          if 'versions:' in line.lower():
  #              print(line)
  #
  #  #####################################################################
  #  # 28.10.2019: add full shutdown to forcedly drop all attachments.
  #  ##                                    ||||||||||||||||||||||||||||
  #  ## ###################################|||  FB 4.0+, SS and SC  |||##############################
  #  ##                                    ||||||||||||||||||||||||||||
  #  ## If we check SS or SC and ExtConnPoolLifeTime > 0 (config parameter FB 4.0+) then current
  #  ## DB (bugs.core_NNNN.fdb) will be 'captured' by firebird.exe process and fbt_run utility
  #  ## will not able to drop this database at the final point of test.
  #  ## Moreover, DB file will be hold until all activity in firebird.exe completed and AFTER this
  #  ## we have to wait for <ExtConnPoolLifeTime> seconds after it (discussion and small test see
  #  ## in the letter to hvlad and dimitr 13.10.2019 11:10).
  #  ## This means that one need to kill all connections to prevent from exception on cleanup phase:
  #  ## SQLCODE: -901 / lock time-out on wait transaction / object <this_test_DB> is in use
  #  ## #############################################################################################
  #
  #  f_shutdown_log=open( os.path.join(context['temp_directory'],'tmp_4298_shutdown.log'), 'w')
  #
  #  subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr",
  #                    "action_properties", "prp_shutdown_mode", "prp_sm_full", "prp_shutdown_db", "0", "dbname", this_fdb,
  #                   ],
  #                   stdout = f_shutdown_log,
  #                   stderr = subprocess.STDOUT
  #                 )
  #  subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr",
  #                    "action_properties", "prp_db_online", "dbname", this_fdb,
  #                   ],
  #                   stdout = f_shutdown_log,
  #                   stderr = subprocess.STDOUT
  #                 )
  #
  #  flush_and_close( f_shutdown_log )
  #
  #  with open( f_shutdown_log.name,'r') as f:
  #      for line in f:
  #          if line.split():
  #              print( 'UNEXPECTED OUTPUT IN DB SHUTDOWN LOG: ' + (' '.join(line.split()).upper()) )
  #
  #
  #  # Cleanup:
  #  ##########
  #  # do NOT remove this pause otherwise some of logs will not be enable for deletion and test will finish with
  #  # Exception raised while executing Python test script. exception: WindowsError: 32
  #  time.sleep(1)
  #
  #  cleanup( (f_hanged_sql, f_hanged_log, f_stat_log, f_stat_err, f_shutdown_log) )
  #
  #
  #---
  
  act_1 = python_act('db_1', substitutions=substitutions_1)
  
  expected_stdout_1 = """
      Average version length: 9.00, total versions: 5, max versions: 1
  """
  
  @pytest.mark.version('>=2.5.2')
  def test_1(act_1: Action):
      with act_1.db.connect() as con:
          c = con.cursor()
          c.execute('update test set x = -x')
          con.commit()
      act_1.svcmgr(switches=['action_db_stats', 'dbname',
                             str(act_1.db.db_path), 'sts_record_versions'])
      act_1.stdout = '\n'.join([line for line in act_1.stdout.splitlines() if 'versions:' in line.lower()])
      act_1.expected_stdout = expected_stdout_1
      assert act_1.clean_stdout == act_1.clean_expected_stdout
  