blob: 84eb8948b2310584b441be3a5b0edca922f4fa29 [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
12import StringIO
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060013import re
Michael Walsh1c85bab2017-05-04 14:29:24 -050014import socket
Michael Walsh3b621fe2018-07-24 16:27:53 -050015import tempfile
16try:
17 import psutil
18 psutil_imported = True
19except ImportError:
20 psutil_imported = False
Michael Walshde791732016-09-06 14:25:24 -050021
Michael Walsh7423c012016-10-04 10:27:21 -050022import gen_print as gp
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060023import gen_cmd as gc
24
Michael Walsh93a09f22017-11-13 15:34:46 -060025robot_env = gp.robot_env
26if robot_env:
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060027 from robot.libraries.BuiltIn import BuiltIn
Michael Walsh7423c012016-10-04 10:27:21 -050028
Michael Walshde791732016-09-06 14:25:24 -050029
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060030def add_trailing_slash(dir_path):
Michael Walsh7db77942017-01-10 11:37:06 -060031 r"""
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060032 Add a trailing slash to the directory path if it doesn't already have one
33 and return it.
Michael Walsh7db77942017-01-10 11:37:06 -060034
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060035 Description of arguments:
36 dir_path A directory path.
Michael Walsh7db77942017-01-10 11:37:06 -060037 """
38
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060039 return os.path.normpath(dir_path) + os.path.sep
40
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060041
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060042def which(file_path):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060043 r"""
44 Find the full path of an executable file and return it.
45
46 The PATH environment variable dictates the results of this function.
47
48 Description of arguments:
49 file_path The relative file path (e.g. "my_file" or
50 "lib/my_file").
51 """
52
53 shell_rc, out_buf = gc.cmd_fnc_u("which " + file_path, quiet=1,
54 print_output=0, show_err=0)
55 if shell_rc != 0:
56 error_message = "Failed to find complete path for file \"" +\
57 file_path + "\".\n"
58 error_message += gp.sprint_var(shell_rc, 1)
59 error_message += out_buf
60 if robot_env:
61 BuiltIn().fail(gp.sprint_error(error_message))
62 else:
63 gp.print_error_report(error_message)
64 return False
65
66 file_path = out_buf.rstrip("\n")
67
68 return file_path
69
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060070
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060071def dft(value, default):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060072 r"""
73 Return default if value is None. Otherwise, return value.
74
75 This is really just shorthand as shown below.
76
77 dft(value, default)
78
79 vs
80
81 default if value is None else value
82
83 Description of arguments:
84 value The value to be returned.
85 default The default value to return if value is
86 None.
87 """
88
89 return default if value is None else value
90
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060091
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060092def get_mod_global(var_name,
93 default=None,
94 mod_name="__main__"):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060095 r"""
96 Get module global variable value and return it.
97
98 If we are running in a robot environment, the behavior will default to
99 calling get_variable_value.
100
101 Description of arguments:
102 var_name The name of the variable whose value is
103 sought.
104 default The value to return if the global does not
105 exist.
106 mod_name The name of the module containing the
107 global variable.
108 """
109
110 if robot_env:
111 return BuiltIn().get_variable_value("${" + var_name + "}", default)
112
113 try:
114 module = sys.modules[mod_name]
115 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500116 gp.print_error_report("Programmer error - The mod_name passed to"
117 + " this function is invalid:\n"
118 + gp.sprint_var(mod_name))
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600119 raise ValueError('Programmer error.')
120
121 if default is None:
122 return getattr(module, var_name)
123 else:
124 return getattr(module, var_name, default)
125
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600126
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600127def global_default(var_value,
128 default=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600129 r"""
130 If var_value is not None, return it. Otherwise, return the global
131 variable of the same name, if it exists. If not, return default.
132
133 This is meant for use by functions needing help assigning dynamic default
134 values to their parms. Example:
135
136 def func1(parm1=None):
137
138 parm1 = global_default(parm1, 0)
139
140 Description of arguments:
141 var_value The value being evaluated.
142 default The value to be returned if var_value is
143 None AND the global variable of the same
144 name does not exist.
145 """
146
147 var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
148
149 return dft(var_value, get_mod_global(var_name, 0))
Michael Walsh7db77942017-01-10 11:37:06 -0600150
Michael Walsh7db77942017-01-10 11:37:06 -0600151
Michael Walsh7db77942017-01-10 11:37:06 -0600152def set_mod_global(var_value,
153 mod_name="__main__",
154 var_name=None):
Michael Walsh7db77942017-01-10 11:37:06 -0600155 r"""
156 Set a global variable for a given module.
157
158 Description of arguments:
159 var_value The value to set in the variable.
160 mod_name The name of the module whose variable is
161 to be set.
162 var_name The name of the variable to set. This
163 defaults to the name of the variable used
164 for var_value when calling this function.
165 """
166
167 try:
168 module = sys.modules[mod_name]
169 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500170 gp.print_error_report("Programmer error - The mod_name passed to"
171 + " this function is invalid:\n"
172 + gp.sprint_var(mod_name))
Michael Walsh7db77942017-01-10 11:37:06 -0600173 raise ValueError('Programmer error.')
174
175 if var_name is None:
176 var_name = gp.get_arg_name(None, 1, 2)
177
178 setattr(module, var_name, var_value)
179
Michael Walsh7db77942017-01-10 11:37:06 -0600180
Michael Walshde791732016-09-06 14:25:24 -0500181def my_parm_file(prop_file_path):
Michael Walshde791732016-09-06 14:25:24 -0500182 r"""
183 Read a properties file, put the keys/values into a dictionary and return
184 the dictionary.
185
186 The properties file must have the following format:
187 var_name<= or :>var_value
188 Comment lines (those beginning with a "#") and blank lines are allowed and
189 will be ignored. Leading and trailing single or double quotes will be
190 stripped from the value. E.g.
191 var1="This one"
192 Quotes are stripped so the resulting value for var1 is:
193 This one
194
195 Description of arguments:
Michael Walsh7423c012016-10-04 10:27:21 -0500196 prop_file_path The caller should pass the path to the
197 properties file.
Michael Walshde791732016-09-06 14:25:24 -0500198 """
199
200 # ConfigParser expects at least one section header in the file (or you
201 # get ConfigParser.MissingSectionHeaderError). Properties files don't
202 # need those so I'll write a dummy section header.
203
204 string_file = StringIO.StringIO()
205 # Write the dummy section header to the string file.
206 string_file.write('[dummysection]\n')
207 # Write the entire contents of the properties file to the string file.
208 string_file.write(open(prop_file_path).read())
209 # Rewind the string file.
210 string_file.seek(0, os.SEEK_SET)
211
212 # Create the ConfigParser object.
213 config_parser = ConfigParser.ConfigParser()
214 # Make the property names case-sensitive.
215 config_parser.optionxform = str
216 # Read the properties from the string file.
217 config_parser.readfp(string_file)
218 # Return the properties as a dictionary.
219 return dict(config_parser.items('dummysection'))
220
Michael Walsh7423c012016-10-04 10:27:21 -0500221
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600222def file_to_list(file_path,
223 newlines=0,
224 comments=1,
225 trim=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600226 r"""
227 Return the contents of a file as a list. Each element of the resulting
228 list is one line from the file.
229
230 Description of arguments:
231 file_path The path to the file (relative or
232 absolute).
233 newlines Include newlines from the file in the
234 results.
235 comments Include comment lines and blank lines in
236 the results. Comment lines are any that
237 begin with 0 or more spaces followed by
238 the pound sign ("#").
239 trim Trim white space from the beginning and
240 end of each line.
241 """
242
243 lines = []
244 file = open(file_path)
245 for line in file:
246 if not comments:
247 if re.match(r"[ ]*#|^$", line):
248 continue
249 if not newlines:
250 line = line.rstrip("\n")
251 if trim:
252 line = line.strip()
253 lines.append(line)
254
255 return lines
256
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600257
Michael Walsh7423c012016-10-04 10:27:21 -0500258def return_path_list():
Michael Walsh7423c012016-10-04 10:27:21 -0500259 r"""
260 This function will split the PATH environment variable into a PATH_LIST
261 and return it. Each element in the list will be normalized and have a
262 trailing slash added.
263 """
264
265 PATH_LIST = os.environ['PATH'].split(":")
266 PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
267
268 return PATH_LIST
269
Michael Walsh7db77942017-01-10 11:37:06 -0600270
Michael Walsh9fac55c2017-09-29 16:53:56 -0500271def escape_bash_quotes(buffer):
Michael Walsh9fac55c2017-09-29 16:53:56 -0500272 r"""
273 Escape quotes in string and return it.
274
275 The escape style implemented will be for use on the bash command line.
276
277 Example:
278 That's all.
279
280 Result:
281 That'\''s all.
282
283 The result may then be single quoted on a bash command. Example:
284
285 echo 'That'\''s all.'
286
287 Description of argument(s):
288 buffer The string whose quotes are to be escaped.
289 """
290
291 return re.sub("\'", "\'\\\'\'", buffer)
292
293
Michael Walsh7db77942017-01-10 11:37:06 -0600294def quote_bash_parm(parm):
Michael Walsh7db77942017-01-10 11:37:06 -0600295 r"""
296 Return the bash command line parm with single quotes if they are needed.
297
298 Description of arguments:
299 parm The string to be quoted.
300 """
301
302 # If any of these characters are found in the parm string, then the
303 # string should be quoted. This list is by no means complete and should
304 # be expanded as needed by the developer of this function.
305 bash_special_chars = set(' $')
306
307 if any((char in bash_special_chars) for char in parm):
308 return "'" + parm + "'"
309
310 return parm
311
Michael Walsh1c85bab2017-05-04 14:29:24 -0500312
Michael Walshf74b3e42018-01-10 11:11:54 -0600313def get_host_name_ip(host,
314 short_name=0):
Michael Walsh1c85bab2017-05-04 14:29:24 -0500315 r"""
316 Get the host name and the IP address for the given host and return them as
317 a tuple.
318
319 Description of argument(s):
Michael Walshd1b6c702017-05-30 17:54:30 -0500320 host The host name or IP address to be obtained.
Michael Walshf74b3e42018-01-10 11:11:54 -0600321 short_name Include the short host name in the
322 returned tuple, i.e. return host, ip and
323 short_host.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500324 """
325
Michael Walshf74b3e42018-01-10 11:11:54 -0600326 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500327 try:
328 host_ip = socket.gethostbyname(host)
329 except socket.gaierror as my_gaierror:
330 message = "Unable to obtain the host name for the following host:" +\
331 "\n" + gp.sprint_var(host)
332 gp.print_error_report(message)
333 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500334
Michael Walshf74b3e42018-01-10 11:11:54 -0600335 if short_name:
336 host_short_name = host_name.split(".")[0]
337 return host_name, host_ip, host_short_name
338 else:
339 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500340
Michael Walsheaa16852017-09-19 16:30:43 -0500341
342def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500343 r"""
344 Return true if pid represents an active pid and false otherwise.
345
346 Description of argument(s):
347 pid The pid whose status is being sought.
348 """
349
350 try:
351 os.kill(int(pid), 0)
352 except OSError as err:
353 if err.errno == errno.ESRCH:
354 # ESRCH == No such process
355 return False
356 elif err.errno == errno.EPERM:
357 # EPERM clearly means there's a process to deny access to
358 return True
359 else:
360 # According to "man 2 kill" possible error values are
361 # (EINVAL, EPERM, ESRCH)
362 raise
363
364 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500365
366
367def to_signed(number,
368 bit_width=gp.bit_length(long(sys.maxsize)) + 1):
Michael Walsh112c3592018-06-01 14:15:58 -0500369 r"""
370 Convert number to a signed number and return the result.
371
372 Examples:
373
374 With the following code:
375
376 var1 = 0xfffffffffffffff1
377 print_var(var1)
378 print_var(var1, 1)
379 var1 = to_signed(var1)
380 print_var(var1)
381 print_var(var1, 1)
382
383 The following is written to stdout:
384 var1: 18446744073709551601
385 var1: 0x00000000fffffffffffffff1
386 var1: -15
387 var1: 0xfffffffffffffff1
388
389 The same code but with var1 set to 0x000000000000007f produces the
390 following:
391 var1: 127
392 var1: 0x000000000000007f
393 var1: 127
394 var1: 0x000000000000007f
395
396 Description of argument(s):
397 number The number to be converted.
398 bit_width The number of bits that defines a complete
399 hex value. Typically, this would be a
400 multiple of 32.
401 """
402
403 if number < 0:
404 return number
405 neg_bit_mask = 2**(bit_width - 1)
406 if number & neg_bit_mask:
407 return ((2**bit_width) - number) * -1
408 else:
409 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500410
411
412def get_child_pids(quiet=1):
413
414 r"""
415 Get and return a list of pids representing all first-generation processes
416 that are the children of the current process.
417
418 Example:
419
420 children = get_child_pids()
421 print_var(children)
422
423 Output:
424 children:
425 children[0]: 9123
426
427 Description of argument(s):
428 quiet Display output to stdout detailing how
429 this child pids are obtained.
430 """
431
432 if psutil_imported:
433 # If "import psutil" worked, find child pids using psutil.
434 current_process = psutil.Process()
435 return [x.pid for x in current_process.children(recursive=False)]
436 else:
437 # Otherwise, find child pids using shell commands.
438 print_output = not quiet
439
440 ps_cmd_buf = "ps --no-headers --ppid " + str(os.getpid()) +\
441 " -o pid,args"
442 # Route the output of ps to a temporary file for later grepping.
443 # Avoid using " | grep" in the ps command string because it creates
444 # yet another process which is of no interest to the caller.
445 temp = tempfile.NamedTemporaryFile()
446 temp_file_path = temp.name
447 gc.shell_cmd(ps_cmd_buf + " > " + temp_file_path,
448 print_output=print_output)
449 # Sample contents of the temporary file:
450 # 30703 sleep 2
451 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args >
452 # /tmp/tmpqqorWY
453 # Use egrep to exclude the "ps" process itself from the results
454 # collected with the prior shell_cmd invocation. Only the other
455 # children are of interest to the caller. Use cut on the grep results
456 # to obtain only the pid column.
457 rc, output = \
458 gc.shell_cmd("egrep -v '" + re.escape(ps_cmd_buf) + "' "
459 + temp_file_path + " | cut -c1-5",
460 print_output=print_output)
461 # Split the output buffer by line into a list. Strip each element of
462 # extra spaces and convert each element to an integer.
463 return map(int, map(str.strip, filter(None, output.split("\n"))))