2525import inspect
2626import signal
2727from subprocess import TimeoutExpired
28- from typing import Any , Dict , List , Optional
28+ from typing import Any , Dict , List , Optional , Type
2929
3030import test_engine_globals
3131
@@ -40,8 +40,14 @@ class OSCommand:
4040 """
4141###
4242
43- def __init__ (self , cmd_str , output_file_path = None , error_file_path = None ,
44- set_cwd = None , use_shell = False ):
43+ def __init__ (
44+ self ,
45+ cmd_str : str ,
46+ output_file_path : Optional [str ] = None ,
47+ error_file_path : Optional [str ] = None ,
48+ set_cwd : Optional [str ] = None ,
49+ use_shell : bool = False ,
50+ ) -> None :
4551 """
4652 Args:
4753 cmd_str (str): The command to be executed
@@ -55,10 +61,13 @@ def __init__(self, cmd_str, output_file_path=None, error_file_path=None,
5561 """
5662 self ._output_file_path = None
5763 self ._error_file_path = None
58- self ._cmd_str = None
59- self ._process = None
64+ self ._cmd_str = [ "" ]
65+ self ._process : subprocess . Popen [ str ] = None # type: ignore [assignment]
6066 self ._timeout_sec = 60
61- self ._run_status = None
67+ # Use an invalid return code rather than None to identify an
68+ # unintialized value.
69+ self ._run_status_sentinel = - 999
70+ self ._run_status = self ._run_status_sentinel
6271 self ._run_output = ''
6372 self ._run_error = ''
6473 self ._run_timeout = False
@@ -71,16 +80,20 @@ def __init__(self, cmd_str, output_file_path=None, error_file_path=None,
7180 self ._signal_sec = 3
7281####
7382
74- def run (self , timeout_sec = 60 , send_signal = signal .NSIG , signal_sec = 3 , ** kwargs ):
83+ def run (self ,
84+ timeout_sec : int = 60 ,
85+ send_signal : int = signal .NSIG ,
86+ signal_sec : int = 3 ,
87+ ** kwargs : Any ,
88+ ) -> "OSCommandResult" :
7589 """ Run a command then return and OSCmdRtn object.
7690
7791 Args:
7892 timeout_sec (int): The maximum runtime in seconds before thread
7993 will be terminated and a timeout error will occur.
8094 kwargs: Extra parameters e.g., timeout_sec to override the default timeout
8195 """
82- if not (isinstance (timeout_sec , (int , float )) and not isinstance (timeout_sec , bool )):
83- raise ValueError ("ERROR: Timeout must be an int or a float" )
96+ check_param_type ("timeout_sec" , timeout_sec , int )
8497
8598 self ._timeout_sec = timeout_sec
8699 self ._signal = send_signal
@@ -92,6 +105,7 @@ def run(self, timeout_sec=60, send_signal=signal.NSIG, signal_sec=3, **kwargs):
92105 thread .join (self ._timeout_sec )
93106 if thread .is_alive ():
94107 self ._run_timeout = True
108+ assert self ._process is not None
95109 self ._process .kill ()
96110 thread .join ()
97111
@@ -102,7 +116,7 @@ def run(self, timeout_sec=60, send_signal=signal.NSIG, signal_sec=3, **kwargs):
102116
103117####
104118
105- def _run_cmd_in_subprocess (self , ** kwargs ) :
119+ def _run_cmd_in_subprocess (self , ** kwargs : Any ) -> None :
106120 """ Run the command in a subprocess """
107121 file_out = None
108122 file_err = None
@@ -163,20 +177,17 @@ def _run_cmd_in_subprocess(self, **kwargs):
163177
164178####
165179
166- def _validate_cmd_str (self , cmd_str ) :
180+ def _validate_cmd_str (self , cmd_str : str ) -> None :
167181 """ Validate the cmd_str """
168- if isinstance (cmd_str , str ):
169- if cmd_str != "" :
170- cmd_str = shlex .split (cmd_str )
171- else :
172- raise ValueError ("ERROR: OSCommand() cmd_str must not be empty" )
173- else :
182+ if not isinstance (cmd_str , str ):
174183 raise ValueError ("ERROR: OSCommand() cmd_str must be a string" )
175- self ._cmd_str = cmd_str
184+ elif not cmd_str :
185+ raise ValueError ("ERROR: OSCommand() cmd_str must not be empty" )
186+ self ._cmd_str = shlex .split (cmd_str )
176187
177188####
178189
179- def _validate_output_path (self , file_path ) :
190+ def _validate_output_path (self , file_path : Optional [ str ]) -> Optional [ str ] :
180191 """ Validate the output file path """
181192 if file_path is not None :
182193 dirpath = os .path .abspath (os .path .dirname (file_path ))
@@ -188,12 +199,12 @@ def _validate_output_path(self, file_path):
188199
189200################################################################################
190201
191- class OSCommandResult () :
202+ class OSCommandResult :
192203 """ This class returns result data about the OSCommand that was executed """
193- def __init__ (self , cmd_str , status , output , error , timeout ) :
204+ def __init__ (self , cmd_str : List [ str ] , status : int , output : str , error : str , timeout : bool ) -> None :
194205 """
195206 Args:
196- cmd_str (str): The command to be executed
207+ cmd_str (list[ str] ): The command to be executed
197208 status (int): The return status of the command execution.
198209 output (str): The standard output of the command execution.
199210 error (str): The error output of the command execution.
@@ -207,7 +218,7 @@ def __init__(self, cmd_str, status, output, error, timeout):
207218
208219####
209220
210- def __repr__ (self ):
221+ def __repr__ (self ) -> str :
211222 rtn_str = (("Cmd = {0}; Status = {1}; Timeout = {2}; " ) +
212223 ("Error = {3}; Output = {4}" )).format (self ._run_cmd_str , \
213224 self ._run_status , self ._run_timeout , self ._run_error , \
@@ -216,24 +227,24 @@ def __repr__(self):
216227
217228####
218229
219- def __str__ (self ):
230+ def __str__ (self ) -> str :
220231 return self .__repr__ ()
221232
222233####
223234
224- def cmd (self ):
235+ def cmd (self ) -> List [ str ] :
225236 """ return the command that was run """
226237 return self ._run_cmd_str
227238
228239####
229240
230- def result (self ):
241+ def result (self ) -> int :
231242 """ return the run status result """
232243 return self ._run_status
233244
234245####
235246
236- def output (self ):
247+ def output (self ) -> str :
237248 """ return the run output result """
238249 # Sometimes the output can be a unicode or a byte string - convert it
239250 if isinstance (self ._run_output , bytes ):
@@ -242,7 +253,7 @@ def output(self):
242253
243254####
244255
245- def error (self ):
256+ def error (self ) -> str :
246257 """ return the run error output result """
247258 # Sometimes the output can be a unicode or a byte string - convert it
248259 if isinstance (self ._run_error , bytes ):
@@ -251,13 +262,13 @@ def error(self):
251262
252263####
253264
254- def timeout (self ):
265+ def timeout (self ) -> bool :
255266 """ return true if the run timed out """
256267 return self ._run_timeout
257268
258269################################################################################
259270
260- def check_param_type (varname , vardata , datatype ) :
271+ def check_param_type (varname : str , vardata : Any , datatype : Type [ Any ]) -> None :
261272 """ Validate a parameter to ensure it is of the correct type.
262273
263274 Args:
@@ -278,96 +289,10 @@ def check_param_type(varname, vardata, datatype):
278289
279290################################################################################
280291
281- def strclass (cls ) :
292+ def strclass (cls : Type [ Any ]) -> str :
282293 """ Return the classname of a class"""
283294 return "%s" % (cls .__module__ )
284295
285- def strqual (cls ) :
296+ def strqual (cls : Type [ Any ]) -> str :
286297 """ Return the qualname of a class"""
287- return "%s" % (_qualname (cls ))
288-
289- ################################################################################
290- # qualname from https://github.com/wbolster/qualname to support Py2 and Py3
291- # LICENSE -> https://github.com/wbolster/qualname/blob/master/LICENSE.rst
292- #__all__ = ['qualname']
293-
294- _cache = {}
295-
296- def _qualname (obj ):
297- """Find out the qualified name for a class or function."""
298-
299- # For Python 3.3+, this is straight-forward.
300- if hasattr (obj , '__qualname__' ):
301- return obj .__qualname__
302-
303- # For older Python versions, things get complicated.
304- # Obtain the filename and the line number where the
305- # class/method/function is defined.
306- try :
307- filename = inspect .getsourcefile (obj )
308- except TypeError :
309- return obj .__qualname__ # raises a sensible error
310- if not filename :
311- return obj .__qualname__ # raises a sensible error
312- if inspect .isclass (obj ):
313- try :
314- _ , lineno = inspect .getsourcelines (obj )
315- except (OSError , IOError ):
316- return obj .__qualname__ # raises a sensible error
317- elif inspect .isfunction (obj ) or inspect .ismethod (obj ):
318- if hasattr (obj , 'im_func' ):
319- # Extract function from unbound method (Python 2)
320- obj = obj .im_func
321- try :
322- code = obj .__code__
323- except AttributeError :
324- code = obj .func_code
325- lineno = code .co_firstlineno
326- else :
327- return obj .__qualname__ # raises a sensible error
328-
329- # Re-parse the source file to figure out what the
330- # __qualname__ should be by analysing the abstract
331- # syntax tree. Use a cache to avoid doing this more
332- # than once for the same file.
333- qualnames = _cache .get (filename )
334- if qualnames is None :
335- with open (filename , 'r' ) as filehandle :
336- source = filehandle .read ()
337- node = ast .parse (source , filename )
338- visitor = _Visitor ()
339- visitor .visit (node )
340- _cache [filename ] = qualnames = visitor .qualnames
341- try :
342- return qualnames [lineno ]
343- except KeyError :
344- return obj .__qualname__ # raises a sensible error
345-
346-
347- class _Visitor (ast .NodeVisitor ):
348- """Support class for qualname function"""
349- def __init__ (self ):
350- super (_Visitor , self ).__init__ ()
351- self .stack = []
352- self .qualnames = {}
353-
354- def store_qualname (self , lineno ):
355- """Support method for _Visitor class"""
356- q_n = "." .join (n for n in self .stack )
357- self .qualnames [lineno ] = q_n
358-
359- def visit_FunctionDef (self , node ):
360- """Support method for _Visitor class"""
361- self .stack .append (node .name )
362- self .store_qualname (node .lineno )
363- self .stack .append ('<locals>' )
364- self .generic_visit (node )
365- self .stack .pop ()
366- self .stack .pop ()
367-
368- def visit_ClassDef (self , node ):
369- """Support method for _Visitor class"""
370- self .stack .append (node .name )
371- self .store_qualname (node .lineno )
372- self .generic_visit (node )
373- self .stack .pop ()
298+ return "%s" % (cls .__qualname__ )
0 commit comments