66import signal
77import subprocess
88import shutil
9+ from time import sleep
910import six
1011import testgres
1112import hashlib
1213import re
1314import getpass
1415import select
15- from time import sleep
16+ import time
1617import re
1718import json
1819import random
@@ -150,8 +151,9 @@ def __str__(self):
150151
151152class PostgresNodeExtended (testgres .PostgresNode ):
152153
153- def __init__ (self ,base_dir = None ,* args ,** kwargs ):
154- super (PostgresNodeExtended ,self ).__init__ (name = 'test' ,base_dir = base_dir ,* args ,** kwargs )
154+ def __init__ (self ,base_dir = None ,port = None ,bin_dir = None ,* args ,** kwargs ):
155+ assert port is None or type (port )== int
156+ super (PostgresNodeExtended ,self ).__init__ (name = 'test' ,base_dir = base_dir ,port = port ,bin_dir = bin_dir ,* args ,** kwargs )
155157self .is_started = False
156158
157159def slow_start (self ,replica = False ):
@@ -414,25 +416,28 @@ def is_test_result_ok(test_case):
414416#
415417# 2. python versions 3.11+ mixin, verified on 3.11, taken from: https://stackoverflow.com/a/39606065
416418
417- if not isinstance (test_case ,unittest .TestCase ):
418- raise AssertionError ("test_case is not instance of unittest.TestCase" )
419-
420- if hasattr (test_case ,'_outcome' ):# Python 3.4+
421- if hasattr (test_case ._outcome ,'errors' ):
422- # Python 3.4 - 3.10 (These two methods have no side effects)
423- result = test_case .defaultTestResult ()# These two methods have no side effects
424- test_case ._feedErrorsToResult (result ,test_case ._outcome .errors )
425- else :
426- # Python 3.11+ and pytest 5.3.5+
427- result = test_case ._outcome .result
428- if not hasattr (result ,'errors' ):
429- result .errors = []
430- if not hasattr (result ,'failures' ):
431- result .failures = []
432- else :# Python 2.7, 3.0-3.3
433- result = getattr (test_case ,'_outcomeForDoCleanups' ,test_case ._resultForDoCleanups )
419+ if hasattr (test_case ._outcome ,'errors' ):
420+ # Python 3.4 - 3.10 (These two methods have no side effects)
421+ result = test_case .defaultTestResult ()# These two methods have no side effects
422+ test_case ._feedErrorsToResult (result ,test_case ._outcome .errors )
423+ else :
424+ # Python 3.11+ and pytest 5.3.5+
425+ result = test_case ._outcome .result
426+ if not hasattr (result ,'errors' ):
427+ result .errors = []
428+ if not hasattr (result ,'failures' ):
429+ result .failures = []
434430
435431ok = all (test != test_case for test ,text in result .errors + result .failures )
432+ # check subtests as well
433+ ok = ok and all (getattr (test ,'test_case' ,None )!= test_case
434+ for test ,text in result .errors + result .failures )
435+
436+ # for pytest 8+
437+ if hasattr (result ,'_excinfo' ):
438+ if result ._excinfo is not None and len (result ._excinfo )> 0 :
439+ # if test was successful, _excinfo will be None, else it will be non-empty list
440+ ok = False
436441
437442return ok
438443
@@ -475,12 +480,14 @@ def pg_config_version(self):
475480
476481def make_empty_node (
477482self ,
478- base_dir = None ):
483+ base_dir = None ,
484+ port = None ,
485+ bin_dir = None ):
479486real_base_dir = os .path .join (self .tmp_path ,base_dir )
480487shutil .rmtree (real_base_dir ,ignore_errors = True )
481488os .makedirs (real_base_dir )
482489
483- node = PostgresNodeExtended (base_dir = real_base_dir )
490+ node = PostgresNodeExtended (base_dir = real_base_dir , port = port , bin_dir = bin_dir )
484491node .should_rm_dirs = True
485492self .nodes_to_cleanup .append (node )
486493
@@ -489,12 +496,14 @@ def make_empty_node(
489496def make_simple_node (
490497self ,
491498base_dir = None ,
499+ port = None ,
500+ bin_dir = None ,
492501set_replication = False ,
493502ptrack_enable = False ,
494503initdb_params = [],
495504pg_options = {}):
496505
497- node = self .make_empty_node (base_dir )
506+ node = self .make_empty_node (base_dir , port = port , bin_dir = bin_dir )
498507node .init (
499508initdb_params = initdb_params ,allow_streaming = set_replication )
500509
@@ -910,6 +919,24 @@ def get_backup_filelist_diff(self, filelist_A, filelist_B):
910919
911920return filelist_diff
912921
922+ def wait_instance_wal_exists (self ,backup_dir ,instance ,file ,timeout = 300 ):
923+ """Wait for WAL segment appeared in the WAL archive"""
924+ start = time .time ()
925+ fl = f'wal/{ instance } /{ file } '
926+ while time .time ()- start < timeout :
927+ if os .path .exists (fl ):
928+ break
929+ time .sleep (0.25 )
930+
931+ def wait_server_wal_exists (self ,data_dir ,wal_dir ,file ,timeout = 300 ):
932+ """Wait for WAL segment appeared in the server WAL dir"""
933+ start = time .time ()
934+ fl = f'{ data_dir } /{ wal_dir } /{ file } '
935+ while time .time ()- start < timeout :
936+ if os .path .exists (fl ):
937+ return
938+ time .sleep (0.25 )
939+
913940# used for partial restore
914941def truncate_every_file_in_dir (self ,path ):
915942for file in os .listdir (path ):