1414
1515import re
1616import socket
17+ import time
18+ from functools import wraps
19+
1720
1821def _to_int (version_str ):
19- m = re .match (r' \d+' , version_str )
22+ m = re .match (r" \d+" , version_str )
2023 return int (m .group (0 )) if m else 0
2124
25+
2226def version2tuple (version_str ):
2327 """Convert version, even if it contains non-numeric chars.
2428
@@ -27,32 +31,55 @@ def version2tuple(version_str):
2731
2832 """
2933
30- parts = version_str .split ('.' )[:2 ]
34+ parts = version_str .split ("." )[:2 ]
3135 return tuple (map (_to_int , parts ))
3236
37+
3338def instance_hostname (hostname ):
34- if hostname == ' localhost' or hostname == "127.0.0.1" :
39+ if hostname == " localhost" or hostname == "127.0.0.1" :
3540 hostname = socket .gethostname ()
3641 return hostname
3742
43+
3844def get_open_port ():
3945 s = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
4046 s .bind (("" , 0 ))
4147 port = s .getsockname ()[1 ]
4248 s .close ()
4349 return port
4450
51+
4552def conditional_decorator (condition , decorator ):
4653 """Applies a decorator if the condition is true. Accepts 0 argument callables for the condition."""
54+
4755 def _conditional_decorator (func ):
4856 if callable (condition ):
4957 condition_eval = condition ()
5058 else :
5159 condition_eval = condition
52-
60+
5361 if condition_eval :
5462 return decorator (func )
5563 else :
5664 return func
5765
5866 return _conditional_decorator
67+
68+
69+ def retry (attempts = 5 , wait = 5 ):
70+ def decorator (test_func ):
71+ @wraps (test_func )
72+ def wrapper (* args , ** kwargs ):
73+ retry_count = 1
74+ while retry_count < attempts :
75+ try :
76+ return test_func (* args , ** kwargs )
77+ except AssertionError as assert_error :
78+ time .sleep (wait )
79+ retry_count += 1
80+ # Preserve original traceback in case assertion fails.
81+ return test_func (* args , ** kwargs )
82+
83+ return wrapper
84+
85+ return decorator
0 commit comments