11# -------------------------------------------------------------------------------
2- # - Copyright (c) 2021 Arista Networks, Inc. All rights reserved.
2+ # - Copyright (c) 2021-2024 Arista Networks, Inc. All rights reserved.
33# -------------------------------------------------------------------------------
44# - Author:
551919import pytest
2020from packaging import version
2121from .wrappers import CLI , xapi
22+ from functools import wraps
2223
2324logger = logging .getLogger (__name__ )
2425
@@ -108,36 +109,39 @@ def skipper(node):
108109 min_chg_num = None
109110
110111 # Restrict SKUs
111- for marker in node .iter_markers ():
112- if marker .name in {"mos" , "eos" }:
113- allowed_os .add (marker .name )
114- min_chg_num = marker .kwargs .get ("min_change_number" , None )
115- min_version = marker .kwargs .get ("min_version" , None )
116-
117- elif marker .name == "only_device_type" :
118- pattern = marker .args [0 ]
119- sku = request .getfixturevalue (f"{ name } _sku" )
120- if not re .search (pattern , sku ):
121- pytest .skip (f"Skipped on this SKU: { sku } (only runs on { pattern } )" )
122-
123- elif marker .name == "skip_device_type" :
124- pattern = marker .args [0 ]
125- sku = request .getfixturevalue (f"{ name } _sku" )
126- if re .search (pattern , sku ):
127- pytest .skip (f"Skipped on this SKU: { sku } " )
128-
129- if allowed_os :
130- dut_ssh = request .getfixturevalue (f"{ name } _ssh" )
131- dut_os_version = request .getfixturevalue (f"{ name } _os_version" )
132- if dut_ssh .cli_flavor not in allowed_os :
133- pytest .skip (f"Cannot run on platform { dut_ssh .cli_flavor } " )
134- if min_version or min_chg_num :
135- # matches the pattern X.XX.X.XX with digits only
136- release , change_number = parse_version (dut_os_version )
137- if min_chg_num :
138- version_skipper (change_number , min_chg_num )
139- else :
140- version_skipper (release , min_version )
112+ try :
113+ for marker in node .iter_markers ():
114+ if marker .name in {"mos" , "eos" }:
115+ allowed_os .add (marker .name )
116+ min_chg_num = marker .kwargs .get ("min_change_number" , None )
117+ min_version = marker .kwargs .get ("min_version" , None )
118+
119+ elif marker .name == "only_device_type" :
120+ pattern = marker .args [0 ]
121+ sku = request .getfixturevalue (f"{ name } _sku" )
122+ if not re .search (pattern , sku ):
123+ pytest .skip (f"Skipped on this SKU: { sku } (only runs on { pattern } )" )
124+
125+ elif marker .name == "skip_device_type" :
126+ pattern = marker .args [0 ]
127+ sku = request .getfixturevalue (f"{ name } _sku" )
128+ if re .search (pattern , sku ):
129+ pytest .skip (f"Skipped on this SKU: { sku } " )
130+
131+ if allowed_os :
132+ dut_ssh = request .getfixturevalue (f"{ name } _ssh" )
133+ dut_os_version = request .getfixturevalue (f"{ name } _os_version" )
134+ if dut_ssh .cli_flavor not in allowed_os :
135+ pytest .skip (f"Cannot run on platform { dut_ssh .cli_flavor } " )
136+ if min_version or min_chg_num :
137+ # matches the pattern X.XX.X.XX with digits only
138+ release , change_number = parse_version (dut_os_version )
139+ if min_chg_num :
140+ version_skipper (change_number , min_chg_num )
141+ else :
142+ version_skipper (release , min_version )
143+ except pytest .FixtureLookupError :
144+ logger .debug ("Pytest fixture recursion caught." )
141145
142146 return skipper
143147
@@ -196,6 +200,32 @@ def _softened(dut_ssh):
196200 return _softened
197201
198202
203+ def retry (limit ):
204+ def decorator (fn ):
205+ @wraps (fn )
206+ def wrapper (* args , ** kwargs ):
207+ attempt , limit_ = 0 , limit
208+ result = None
209+ while attempt < limit_ :
210+ try :
211+ result = fn (* args , ** kwargs )
212+ break
213+ except Exception as e :
214+ attempt += 1
215+ logging .error (
216+ "An error occurred in %s, attempt %d of %d: %s" ,
217+ fn .__name__ ,
218+ attempt ,
219+ limit_ ,
220+ e ,
221+ )
222+ return result
223+
224+ return wrapper
225+
226+ return decorator
227+
228+
199229class _CLI_wrapper :
200230 _cli = None
201231
@@ -213,6 +243,7 @@ def close_and_re_init(self):
213243 def close (self , * args , ** kwargs ):
214244 return self ._cli .close (* args , ** kwargs )
215245
246+ @retry (3 )
216247 def login (self , * args , ** kwargs ):
217248 return self ._cli .login (* args , ** kwargs )
218249
@@ -294,15 +325,16 @@ def args(self):
294325def create_ssh_fixture (name ):
295326 @pytest .fixture (scope = "session" , name = f"{ name } _ssh" )
296327 def _ssh (request ):
328+ ssh = _CLI_wrapper (f"ssh://{ request .getfixturevalue (f'{ name } _hostname' )} " )
329+ # do not break the fixture if SSH failed
330+ # pass the failure into the test
297331 try :
298- ssh = _CLI_wrapper (f"ssh://{ request .getfixturevalue (f'{ name } _hostname' )} " )
299- except Exception as exc :
300- logging .error ("Failed to create ssh fixture and log in" )
301- raise exc
302- if ssh .cli_flavor == "mos" :
303- ssh .sendcmd ("enable" )
304- # Disable pagination
305- ssh .sendcmd ("terminal length 0" )
332+ if ssh .cli_flavor == "mos" :
333+ ssh .sendcmd ("enable" )
334+ # Disable pagination
335+ ssh .sendcmd ("terminal length 0" )
336+ except AttributeError :
337+ pass
306338 yield ssh
307339
308340 return _ssh
0 commit comments