blob: d43aa209f896de0b5c5fd1e8ed72171dface3eb1 [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
Michael Walshdece16c2018-08-07 15:01:05 -050011import collections
12try:
13 import ConfigParser
14except ImportError:
15 import configparser
George Keishing3b7115a2018-08-02 10:48:17 -050016try:
Michael Walsh61f5e8f2018-08-03 11:16:00 -050017 import StringIO
George Keishing3b7115a2018-08-02 10:48:17 -050018except ImportError:
Michael Walshdece16c2018-08-07 15:01:05 -050019 import io
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060020import re
Michael Walsh1c85bab2017-05-04 14:29:24 -050021import socket
Michael Walsh3b621fe2018-07-24 16:27:53 -050022import tempfile
23try:
24 import psutil
25 psutil_imported = True
26except ImportError:
27 psutil_imported = False
Michael Walshde791732016-09-06 14:25:24 -050028
Michael Walsh7423c012016-10-04 10:27:21 -050029import gen_print as gp
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060030import gen_cmd as gc
31
Michael Walsh93a09f22017-11-13 15:34:46 -060032robot_env = gp.robot_env
33if robot_env:
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060034 from robot.libraries.BuiltIn import BuiltIn
Michael Walshdece16c2018-08-07 15:01:05 -050035 from robot.utils import DotDict
Michael Walsh7423c012016-10-04 10:27:21 -050036
Michael Walshde791732016-09-06 14:25:24 -050037
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060038def add_trailing_slash(dir_path):
Michael Walsh7db77942017-01-10 11:37:06 -060039 r"""
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060040 Add a trailing slash to the directory path if it doesn't already have one
41 and return it.
Michael Walsh7db77942017-01-10 11:37:06 -060042
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060043 Description of arguments:
44 dir_path A directory path.
Michael Walsh7db77942017-01-10 11:37:06 -060045 """
46
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060047 return os.path.normpath(dir_path) + os.path.sep
48
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060049
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060050def which(file_path):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060051 r"""
52 Find the full path of an executable file and return it.
53
54 The PATH environment variable dictates the results of this function.
55
56 Description of arguments:
57 file_path The relative file path (e.g. "my_file" or
58 "lib/my_file").
59 """
60
61 shell_rc, out_buf = gc.cmd_fnc_u("which " + file_path, quiet=1,
62 print_output=0, show_err=0)
63 if shell_rc != 0:
64 error_message = "Failed to find complete path for file \"" +\
65 file_path + "\".\n"
66 error_message += gp.sprint_var(shell_rc, 1)
67 error_message += out_buf
68 if robot_env:
69 BuiltIn().fail(gp.sprint_error(error_message))
70 else:
71 gp.print_error_report(error_message)
72 return False
73
74 file_path = out_buf.rstrip("\n")
75
76 return file_path
77
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060078
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060079def dft(value, default):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060080 r"""
81 Return default if value is None. Otherwise, return value.
82
83 This is really just shorthand as shown below.
84
85 dft(value, default)
86
87 vs
88
89 default if value is None else value
90
91 Description of arguments:
92 value The value to be returned.
93 default The default value to return if value is
94 None.
95 """
96
97 return default if value is None else value
98
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060099
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600100def get_mod_global(var_name,
101 default=None,
102 mod_name="__main__"):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600103 r"""
104 Get module global variable value and return it.
105
106 If we are running in a robot environment, the behavior will default to
107 calling get_variable_value.
108
109 Description of arguments:
110 var_name The name of the variable whose value is
111 sought.
112 default The value to return if the global does not
113 exist.
114 mod_name The name of the module containing the
115 global variable.
116 """
117
118 if robot_env:
119 return BuiltIn().get_variable_value("${" + var_name + "}", default)
120
121 try:
122 module = sys.modules[mod_name]
123 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500124 gp.print_error_report("Programmer error - The mod_name passed to"
125 + " this function is invalid:\n"
126 + gp.sprint_var(mod_name))
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600127 raise ValueError('Programmer error.')
128
129 if default is None:
130 return getattr(module, var_name)
131 else:
132 return getattr(module, var_name, default)
133
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600134
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600135def global_default(var_value,
136 default=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600137 r"""
138 If var_value is not None, return it. Otherwise, return the global
139 variable of the same name, if it exists. If not, return default.
140
141 This is meant for use by functions needing help assigning dynamic default
142 values to their parms. Example:
143
144 def func1(parm1=None):
145
146 parm1 = global_default(parm1, 0)
147
148 Description of arguments:
149 var_value The value being evaluated.
150 default The value to be returned if var_value is
151 None AND the global variable of the same
152 name does not exist.
153 """
154
155 var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
156
157 return dft(var_value, get_mod_global(var_name, 0))
Michael Walsh7db77942017-01-10 11:37:06 -0600158
Michael Walsh7db77942017-01-10 11:37:06 -0600159
Michael Walsh7db77942017-01-10 11:37:06 -0600160def set_mod_global(var_value,
161 mod_name="__main__",
162 var_name=None):
Michael Walsh7db77942017-01-10 11:37:06 -0600163 r"""
164 Set a global variable for a given module.
165
166 Description of arguments:
167 var_value The value to set in the variable.
168 mod_name The name of the module whose variable is
169 to be set.
170 var_name The name of the variable to set. This
171 defaults to the name of the variable used
172 for var_value when calling this function.
173 """
174
175 try:
176 module = sys.modules[mod_name]
177 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500178 gp.print_error_report("Programmer error - The mod_name passed to"
179 + " this function is invalid:\n"
180 + gp.sprint_var(mod_name))
Michael Walsh7db77942017-01-10 11:37:06 -0600181 raise ValueError('Programmer error.')
182
183 if var_name is None:
184 var_name = gp.get_arg_name(None, 1, 2)
185
186 setattr(module, var_name, var_value)
187
Michael Walsh7db77942017-01-10 11:37:06 -0600188
Michael Walshde791732016-09-06 14:25:24 -0500189def my_parm_file(prop_file_path):
Michael Walshde791732016-09-06 14:25:24 -0500190 r"""
191 Read a properties file, put the keys/values into a dictionary and return
192 the dictionary.
193
194 The properties file must have the following format:
195 var_name<= or :>var_value
196 Comment lines (those beginning with a "#") and blank lines are allowed and
197 will be ignored. Leading and trailing single or double quotes will be
198 stripped from the value. E.g.
199 var1="This one"
200 Quotes are stripped so the resulting value for var1 is:
201 This one
202
203 Description of arguments:
Michael Walsh7423c012016-10-04 10:27:21 -0500204 prop_file_path The caller should pass the path to the
205 properties file.
Michael Walshde791732016-09-06 14:25:24 -0500206 """
207
208 # ConfigParser expects at least one section header in the file (or you
209 # get ConfigParser.MissingSectionHeaderError). Properties files don't
210 # need those so I'll write a dummy section header.
211
Michael Walshdece16c2018-08-07 15:01:05 -0500212 try:
213 string_file = StringIO.StringIO()
214 except NameError:
215 string_file = io.StringIO()
216
Michael Walshde791732016-09-06 14:25:24 -0500217 # Write the dummy section header to the string file.
218 string_file.write('[dummysection]\n')
219 # Write the entire contents of the properties file to the string file.
220 string_file.write(open(prop_file_path).read())
221 # Rewind the string file.
222 string_file.seek(0, os.SEEK_SET)
223
224 # Create the ConfigParser object.
Michael Walshdece16c2018-08-07 15:01:05 -0500225 try:
226 config_parser = ConfigParser.ConfigParser()
227 except NameError:
228 config_parser = configparser.ConfigParser()
Michael Walshde791732016-09-06 14:25:24 -0500229 # Make the property names case-sensitive.
230 config_parser.optionxform = str
231 # Read the properties from the string file.
232 config_parser.readfp(string_file)
233 # Return the properties as a dictionary.
Michael Walshdece16c2018-08-07 15:01:05 -0500234 if robot_env:
235 return DotDict(config_parser.items('dummysection'))
236 else:
237 return collections.OrderedDict(config_parser.items('dummysection'))
Michael Walshde791732016-09-06 14:25:24 -0500238
Michael Walsh7423c012016-10-04 10:27:21 -0500239
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600240def file_to_list(file_path,
241 newlines=0,
242 comments=1,
243 trim=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600244 r"""
245 Return the contents of a file as a list. Each element of the resulting
246 list is one line from the file.
247
248 Description of arguments:
249 file_path The path to the file (relative or
250 absolute).
251 newlines Include newlines from the file in the
252 results.
253 comments Include comment lines and blank lines in
254 the results. Comment lines are any that
255 begin with 0 or more spaces followed by
256 the pound sign ("#").
257 trim Trim white space from the beginning and
258 end of each line.
259 """
260
261 lines = []
262 file = open(file_path)
263 for line in file:
264 if not comments:
265 if re.match(r"[ ]*#|^$", line):
266 continue
267 if not newlines:
268 line = line.rstrip("\n")
269 if trim:
270 line = line.strip()
271 lines.append(line)
272
273 return lines
274
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600275
Michael Walsh7423c012016-10-04 10:27:21 -0500276def return_path_list():
Michael Walsh7423c012016-10-04 10:27:21 -0500277 r"""
278 This function will split the PATH environment variable into a PATH_LIST
279 and return it. Each element in the list will be normalized and have a
280 trailing slash added.
281 """
282
283 PATH_LIST = os.environ['PATH'].split(":")
284 PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
285
286 return PATH_LIST
287
Michael Walsh7db77942017-01-10 11:37:06 -0600288
Michael Walsh9fac55c2017-09-29 16:53:56 -0500289def escape_bash_quotes(buffer):
Michael Walsh9fac55c2017-09-29 16:53:56 -0500290 r"""
291 Escape quotes in string and return it.
292
293 The escape style implemented will be for use on the bash command line.
294
295 Example:
296 That's all.
297
298 Result:
299 That'\''s all.
300
301 The result may then be single quoted on a bash command. Example:
302
303 echo 'That'\''s all.'
304
305 Description of argument(s):
306 buffer The string whose quotes are to be escaped.
307 """
308
309 return re.sub("\'", "\'\\\'\'", buffer)
310
311
Michael Walsh7db77942017-01-10 11:37:06 -0600312def quote_bash_parm(parm):
Michael Walsh7db77942017-01-10 11:37:06 -0600313 r"""
314 Return the bash command line parm with single quotes if they are needed.
315
316 Description of arguments:
317 parm The string to be quoted.
318 """
319
320 # If any of these characters are found in the parm string, then the
321 # string should be quoted. This list is by no means complete and should
322 # be expanded as needed by the developer of this function.
323 bash_special_chars = set(' $')
324
325 if any((char in bash_special_chars) for char in parm):
326 return "'" + parm + "'"
327
328 return parm
329
Michael Walsh1c85bab2017-05-04 14:29:24 -0500330
Michael Walshf74b3e42018-01-10 11:11:54 -0600331def get_host_name_ip(host,
332 short_name=0):
Michael Walsh1c85bab2017-05-04 14:29:24 -0500333 r"""
334 Get the host name and the IP address for the given host and return them as
335 a tuple.
336
337 Description of argument(s):
Michael Walshd1b6c702017-05-30 17:54:30 -0500338 host The host name or IP address to be obtained.
Michael Walshf74b3e42018-01-10 11:11:54 -0600339 short_name Include the short host name in the
340 returned tuple, i.e. return host, ip and
341 short_host.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500342 """
343
Michael Walshf74b3e42018-01-10 11:11:54 -0600344 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500345 try:
346 host_ip = socket.gethostbyname(host)
347 except socket.gaierror as my_gaierror:
348 message = "Unable to obtain the host name for the following host:" +\
349 "\n" + gp.sprint_var(host)
350 gp.print_error_report(message)
351 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500352
Michael Walshf74b3e42018-01-10 11:11:54 -0600353 if short_name:
354 host_short_name = host_name.split(".")[0]
355 return host_name, host_ip, host_short_name
356 else:
357 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500358
Michael Walsheaa16852017-09-19 16:30:43 -0500359
360def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500361 r"""
362 Return true if pid represents an active pid and false otherwise.
363
364 Description of argument(s):
365 pid The pid whose status is being sought.
366 """
367
368 try:
369 os.kill(int(pid), 0)
370 except OSError as err:
371 if err.errno == errno.ESRCH:
372 # ESRCH == No such process
373 return False
374 elif err.errno == errno.EPERM:
375 # EPERM clearly means there's a process to deny access to
376 return True
377 else:
378 # According to "man 2 kill" possible error values are
379 # (EINVAL, EPERM, ESRCH)
380 raise
381
382 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500383
384
385def to_signed(number,
Michael Walshdece16c2018-08-07 15:01:05 -0500386 bit_width=None):
Michael Walsh112c3592018-06-01 14:15:58 -0500387 r"""
388 Convert number to a signed number and return the result.
389
390 Examples:
391
392 With the following code:
393
394 var1 = 0xfffffffffffffff1
395 print_var(var1)
396 print_var(var1, 1)
397 var1 = to_signed(var1)
398 print_var(var1)
399 print_var(var1, 1)
400
401 The following is written to stdout:
402 var1: 18446744073709551601
403 var1: 0x00000000fffffffffffffff1
404 var1: -15
405 var1: 0xfffffffffffffff1
406
407 The same code but with var1 set to 0x000000000000007f produces the
408 following:
409 var1: 127
410 var1: 0x000000000000007f
411 var1: 127
412 var1: 0x000000000000007f
413
414 Description of argument(s):
415 number The number to be converted.
416 bit_width The number of bits that defines a complete
417 hex value. Typically, this would be a
418 multiple of 32.
419 """
420
Michael Walshdece16c2018-08-07 15:01:05 -0500421 if bit_width is None:
422 try:
423 bit_width = gp.bit_length(long(sys.maxsize)) + 1
424 except NameError:
425 bit_width = gp.bit_length(int(sys.maxsize)) + 1
426
Michael Walsh112c3592018-06-01 14:15:58 -0500427 if number < 0:
428 return number
429 neg_bit_mask = 2**(bit_width - 1)
430 if number & neg_bit_mask:
431 return ((2**bit_width) - number) * -1
432 else:
433 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500434
435
436def get_child_pids(quiet=1):
437
438 r"""
439 Get and return a list of pids representing all first-generation processes
440 that are the children of the current process.
441
442 Example:
443
444 children = get_child_pids()
445 print_var(children)
446
447 Output:
448 children:
449 children[0]: 9123
450
451 Description of argument(s):
452 quiet Display output to stdout detailing how
453 this child pids are obtained.
454 """
455
456 if psutil_imported:
457 # If "import psutil" worked, find child pids using psutil.
458 current_process = psutil.Process()
459 return [x.pid for x in current_process.children(recursive=False)]
460 else:
461 # Otherwise, find child pids using shell commands.
462 print_output = not quiet
463
464 ps_cmd_buf = "ps --no-headers --ppid " + str(os.getpid()) +\
465 " -o pid,args"
466 # Route the output of ps to a temporary file for later grepping.
467 # Avoid using " | grep" in the ps command string because it creates
468 # yet another process which is of no interest to the caller.
469 temp = tempfile.NamedTemporaryFile()
470 temp_file_path = temp.name
471 gc.shell_cmd(ps_cmd_buf + " > " + temp_file_path,
472 print_output=print_output)
473 # Sample contents of the temporary file:
474 # 30703 sleep 2
475 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args >
476 # /tmp/tmpqqorWY
477 # Use egrep to exclude the "ps" process itself from the results
478 # collected with the prior shell_cmd invocation. Only the other
479 # children are of interest to the caller. Use cut on the grep results
480 # to obtain only the pid column.
481 rc, output = \
482 gc.shell_cmd("egrep -v '" + re.escape(ps_cmd_buf) + "' "
483 + temp_file_path + " | cut -c1-5",
484 print_output=print_output)
485 # Split the output buffer by line into a list. Strip each element of
486 # extra spaces and convert each element to an integer.
487 return map(int, map(str.strip, filter(None, output.split("\n"))))