blob: 0979cb99ac03ca85dda98d584870a31a3a39c998 [file] [log] [blame]
Michael Walshde791732016-09-06 14:25:24 -05001#!/usr/bin/env python
2
Michael Walsh7423c012016-10-04 10:27:21 -05003r"""
4This module provides many valuable functions such as my_parm_file.
5"""
Michael Walshde791732016-09-06 14:25:24 -05006
7# sys and os are needed to get the program dir path and program name.
8import sys
Michael Walsheaa16852017-09-19 16:30:43 -05009import errno
Michael Walshde791732016-09-06 14:25:24 -050010import os
11import ConfigParser
George Keishing3b7115a2018-08-02 10:48:17 -050012try:
Michael Walsh61f5e8f2018-08-03 11:16:00 -050013 import StringIO
George Keishing3b7115a2018-08-02 10:48:17 -050014except ImportError:
15 from io import StringIO
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060016import re
Michael Walsh1c85bab2017-05-04 14:29:24 -050017import socket
Michael Walsh3b621fe2018-07-24 16:27:53 -050018import tempfile
19try:
20 import psutil
21 psutil_imported = True
22except ImportError:
23 psutil_imported = False
Michael Walshde791732016-09-06 14:25:24 -050024
Michael Walsh7423c012016-10-04 10:27:21 -050025import gen_print as gp
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060026import gen_cmd as gc
27
Michael Walsh93a09f22017-11-13 15:34:46 -060028robot_env = gp.robot_env
29if robot_env:
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060030 from robot.libraries.BuiltIn import BuiltIn
Michael Walsh7423c012016-10-04 10:27:21 -050031
Michael Walshde791732016-09-06 14:25:24 -050032
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060033def add_trailing_slash(dir_path):
Michael Walsh7db77942017-01-10 11:37:06 -060034 r"""
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060035 Add a trailing slash to the directory path if it doesn't already have one
36 and return it.
Michael Walsh7db77942017-01-10 11:37:06 -060037
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060038 Description of arguments:
39 dir_path A directory path.
Michael Walsh7db77942017-01-10 11:37:06 -060040 """
41
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060042 return os.path.normpath(dir_path) + os.path.sep
43
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060044
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060045def which(file_path):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060046 r"""
47 Find the full path of an executable file and return it.
48
49 The PATH environment variable dictates the results of this function.
50
51 Description of arguments:
52 file_path The relative file path (e.g. "my_file" or
53 "lib/my_file").
54 """
55
56 shell_rc, out_buf = gc.cmd_fnc_u("which " + file_path, quiet=1,
57 print_output=0, show_err=0)
58 if shell_rc != 0:
59 error_message = "Failed to find complete path for file \"" +\
60 file_path + "\".\n"
61 error_message += gp.sprint_var(shell_rc, 1)
62 error_message += out_buf
63 if robot_env:
64 BuiltIn().fail(gp.sprint_error(error_message))
65 else:
66 gp.print_error_report(error_message)
67 return False
68
69 file_path = out_buf.rstrip("\n")
70
71 return file_path
72
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060073
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060074def dft(value, default):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060075 r"""
76 Return default if value is None. Otherwise, return value.
77
78 This is really just shorthand as shown below.
79
80 dft(value, default)
81
82 vs
83
84 default if value is None else value
85
86 Description of arguments:
87 value The value to be returned.
88 default The default value to return if value is
89 None.
90 """
91
92 return default if value is None else value
93
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060094
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060095def get_mod_global(var_name,
96 default=None,
97 mod_name="__main__"):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060098 r"""
99 Get module global variable value and return it.
100
101 If we are running in a robot environment, the behavior will default to
102 calling get_variable_value.
103
104 Description of arguments:
105 var_name The name of the variable whose value is
106 sought.
107 default The value to return if the global does not
108 exist.
109 mod_name The name of the module containing the
110 global variable.
111 """
112
113 if robot_env:
114 return BuiltIn().get_variable_value("${" + var_name + "}", default)
115
116 try:
117 module = sys.modules[mod_name]
118 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500119 gp.print_error_report("Programmer error - The mod_name passed to"
120 + " this function is invalid:\n"
121 + gp.sprint_var(mod_name))
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600122 raise ValueError('Programmer error.')
123
124 if default is None:
125 return getattr(module, var_name)
126 else:
127 return getattr(module, var_name, default)
128
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600129
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600130def global_default(var_value,
131 default=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600132 r"""
133 If var_value is not None, return it. Otherwise, return the global
134 variable of the same name, if it exists. If not, return default.
135
136 This is meant for use by functions needing help assigning dynamic default
137 values to their parms. Example:
138
139 def func1(parm1=None):
140
141 parm1 = global_default(parm1, 0)
142
143 Description of arguments:
144 var_value The value being evaluated.
145 default The value to be returned if var_value is
146 None AND the global variable of the same
147 name does not exist.
148 """
149
150 var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
151
152 return dft(var_value, get_mod_global(var_name, 0))
Michael Walsh7db77942017-01-10 11:37:06 -0600153
Michael Walsh7db77942017-01-10 11:37:06 -0600154
Michael Walsh7db77942017-01-10 11:37:06 -0600155def set_mod_global(var_value,
156 mod_name="__main__",
157 var_name=None):
Michael Walsh7db77942017-01-10 11:37:06 -0600158 r"""
159 Set a global variable for a given module.
160
161 Description of arguments:
162 var_value The value to set in the variable.
163 mod_name The name of the module whose variable is
164 to be set.
165 var_name The name of the variable to set. This
166 defaults to the name of the variable used
167 for var_value when calling this function.
168 """
169
170 try:
171 module = sys.modules[mod_name]
172 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500173 gp.print_error_report("Programmer error - The mod_name passed to"
174 + " this function is invalid:\n"
175 + gp.sprint_var(mod_name))
Michael Walsh7db77942017-01-10 11:37:06 -0600176 raise ValueError('Programmer error.')
177
178 if var_name is None:
179 var_name = gp.get_arg_name(None, 1, 2)
180
181 setattr(module, var_name, var_value)
182
Michael Walsh7db77942017-01-10 11:37:06 -0600183
Michael Walshde791732016-09-06 14:25:24 -0500184def my_parm_file(prop_file_path):
Michael Walshde791732016-09-06 14:25:24 -0500185 r"""
186 Read a properties file, put the keys/values into a dictionary and return
187 the dictionary.
188
189 The properties file must have the following format:
190 var_name<= or :>var_value
191 Comment lines (those beginning with a "#") and blank lines are allowed and
192 will be ignored. Leading and trailing single or double quotes will be
193 stripped from the value. E.g.
194 var1="This one"
195 Quotes are stripped so the resulting value for var1 is:
196 This one
197
198 Description of arguments:
Michael Walsh7423c012016-10-04 10:27:21 -0500199 prop_file_path The caller should pass the path to the
200 properties file.
Michael Walshde791732016-09-06 14:25:24 -0500201 """
202
203 # ConfigParser expects at least one section header in the file (or you
204 # get ConfigParser.MissingSectionHeaderError). Properties files don't
205 # need those so I'll write a dummy section header.
206
207 string_file = StringIO.StringIO()
208 # Write the dummy section header to the string file.
209 string_file.write('[dummysection]\n')
210 # Write the entire contents of the properties file to the string file.
211 string_file.write(open(prop_file_path).read())
212 # Rewind the string file.
213 string_file.seek(0, os.SEEK_SET)
214
215 # Create the ConfigParser object.
216 config_parser = ConfigParser.ConfigParser()
217 # Make the property names case-sensitive.
218 config_parser.optionxform = str
219 # Read the properties from the string file.
220 config_parser.readfp(string_file)
221 # Return the properties as a dictionary.
222 return dict(config_parser.items('dummysection'))
223
Michael Walsh7423c012016-10-04 10:27:21 -0500224
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600225def file_to_list(file_path,
226 newlines=0,
227 comments=1,
228 trim=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600229 r"""
230 Return the contents of a file as a list. Each element of the resulting
231 list is one line from the file.
232
233 Description of arguments:
234 file_path The path to the file (relative or
235 absolute).
236 newlines Include newlines from the file in the
237 results.
238 comments Include comment lines and blank lines in
239 the results. Comment lines are any that
240 begin with 0 or more spaces followed by
241 the pound sign ("#").
242 trim Trim white space from the beginning and
243 end of each line.
244 """
245
246 lines = []
247 file = open(file_path)
248 for line in file:
249 if not comments:
250 if re.match(r"[ ]*#|^$", line):
251 continue
252 if not newlines:
253 line = line.rstrip("\n")
254 if trim:
255 line = line.strip()
256 lines.append(line)
257
258 return lines
259
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600260
Michael Walsh7423c012016-10-04 10:27:21 -0500261def return_path_list():
Michael Walsh7423c012016-10-04 10:27:21 -0500262 r"""
263 This function will split the PATH environment variable into a PATH_LIST
264 and return it. Each element in the list will be normalized and have a
265 trailing slash added.
266 """
267
268 PATH_LIST = os.environ['PATH'].split(":")
269 PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
270
271 return PATH_LIST
272
Michael Walsh7db77942017-01-10 11:37:06 -0600273
Michael Walsh9fac55c2017-09-29 16:53:56 -0500274def escape_bash_quotes(buffer):
Michael Walsh9fac55c2017-09-29 16:53:56 -0500275 r"""
276 Escape quotes in string and return it.
277
278 The escape style implemented will be for use on the bash command line.
279
280 Example:
281 That's all.
282
283 Result:
284 That'\''s all.
285
286 The result may then be single quoted on a bash command. Example:
287
288 echo 'That'\''s all.'
289
290 Description of argument(s):
291 buffer The string whose quotes are to be escaped.
292 """
293
294 return re.sub("\'", "\'\\\'\'", buffer)
295
296
Michael Walsh7db77942017-01-10 11:37:06 -0600297def quote_bash_parm(parm):
Michael Walsh7db77942017-01-10 11:37:06 -0600298 r"""
299 Return the bash command line parm with single quotes if they are needed.
300
301 Description of arguments:
302 parm The string to be quoted.
303 """
304
305 # If any of these characters are found in the parm string, then the
306 # string should be quoted. This list is by no means complete and should
307 # be expanded as needed by the developer of this function.
308 bash_special_chars = set(' $')
309
310 if any((char in bash_special_chars) for char in parm):
311 return "'" + parm + "'"
312
313 return parm
314
Michael Walsh1c85bab2017-05-04 14:29:24 -0500315
Michael Walshf74b3e42018-01-10 11:11:54 -0600316def get_host_name_ip(host,
317 short_name=0):
Michael Walsh1c85bab2017-05-04 14:29:24 -0500318 r"""
319 Get the host name and the IP address for the given host and return them as
320 a tuple.
321
322 Description of argument(s):
Michael Walshd1b6c702017-05-30 17:54:30 -0500323 host The host name or IP address to be obtained.
Michael Walshf74b3e42018-01-10 11:11:54 -0600324 short_name Include the short host name in the
325 returned tuple, i.e. return host, ip and
326 short_host.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500327 """
328
Michael Walshf74b3e42018-01-10 11:11:54 -0600329 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500330 try:
331 host_ip = socket.gethostbyname(host)
332 except socket.gaierror as my_gaierror:
333 message = "Unable to obtain the host name for the following host:" +\
334 "\n" + gp.sprint_var(host)
335 gp.print_error_report(message)
336 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500337
Michael Walshf74b3e42018-01-10 11:11:54 -0600338 if short_name:
339 host_short_name = host_name.split(".")[0]
340 return host_name, host_ip, host_short_name
341 else:
342 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500343
Michael Walsheaa16852017-09-19 16:30:43 -0500344
345def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500346 r"""
347 Return true if pid represents an active pid and false otherwise.
348
349 Description of argument(s):
350 pid The pid whose status is being sought.
351 """
352
353 try:
354 os.kill(int(pid), 0)
355 except OSError as err:
356 if err.errno == errno.ESRCH:
357 # ESRCH == No such process
358 return False
359 elif err.errno == errno.EPERM:
360 # EPERM clearly means there's a process to deny access to
361 return True
362 else:
363 # According to "man 2 kill" possible error values are
364 # (EINVAL, EPERM, ESRCH)
365 raise
366
367 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500368
369
370def to_signed(number,
371 bit_width=gp.bit_length(long(sys.maxsize)) + 1):
Michael Walsh112c3592018-06-01 14:15:58 -0500372 r"""
373 Convert number to a signed number and return the result.
374
375 Examples:
376
377 With the following code:
378
379 var1 = 0xfffffffffffffff1
380 print_var(var1)
381 print_var(var1, 1)
382 var1 = to_signed(var1)
383 print_var(var1)
384 print_var(var1, 1)
385
386 The following is written to stdout:
387 var1: 18446744073709551601
388 var1: 0x00000000fffffffffffffff1
389 var1: -15
390 var1: 0xfffffffffffffff1
391
392 The same code but with var1 set to 0x000000000000007f produces the
393 following:
394 var1: 127
395 var1: 0x000000000000007f
396 var1: 127
397 var1: 0x000000000000007f
398
399 Description of argument(s):
400 number The number to be converted.
401 bit_width The number of bits that defines a complete
402 hex value. Typically, this would be a
403 multiple of 32.
404 """
405
406 if number < 0:
407 return number
408 neg_bit_mask = 2**(bit_width - 1)
409 if number & neg_bit_mask:
410 return ((2**bit_width) - number) * -1
411 else:
412 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500413
414
415def get_child_pids(quiet=1):
416
417 r"""
418 Get and return a list of pids representing all first-generation processes
419 that are the children of the current process.
420
421 Example:
422
423 children = get_child_pids()
424 print_var(children)
425
426 Output:
427 children:
428 children[0]: 9123
429
430 Description of argument(s):
431 quiet Display output to stdout detailing how
432 this child pids are obtained.
433 """
434
435 if psutil_imported:
436 # If "import psutil" worked, find child pids using psutil.
437 current_process = psutil.Process()
438 return [x.pid for x in current_process.children(recursive=False)]
439 else:
440 # Otherwise, find child pids using shell commands.
441 print_output = not quiet
442
443 ps_cmd_buf = "ps --no-headers --ppid " + str(os.getpid()) +\
444 " -o pid,args"
445 # Route the output of ps to a temporary file for later grepping.
446 # Avoid using " | grep" in the ps command string because it creates
447 # yet another process which is of no interest to the caller.
448 temp = tempfile.NamedTemporaryFile()
449 temp_file_path = temp.name
450 gc.shell_cmd(ps_cmd_buf + " > " + temp_file_path,
451 print_output=print_output)
452 # Sample contents of the temporary file:
453 # 30703 sleep 2
454 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args >
455 # /tmp/tmpqqorWY
456 # Use egrep to exclude the "ps" process itself from the results
457 # collected with the prior shell_cmd invocation. Only the other
458 # children are of interest to the caller. Use cut on the grep results
459 # to obtain only the pid column.
460 rc, output = \
461 gc.shell_cmd("egrep -v '" + re.escape(ps_cmd_buf) + "' "
462 + temp_file_path + " | cut -c1-5",
463 print_output=print_output)
464 # Split the output buffer by line into a list. Strip each element of
465 # extra spaces and convert each element to an integer.
466 return map(int, map(str.strip, filter(None, output.split("\n"))))