blob: 4b7780a84b98a198c42135a7eef9c72cf5bacbea [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 Walsh74427232018-08-31 12:54:24 -0500331def get_host_name_ip(host=None,
Michael Walshf74b3e42018-01-10 11:11:54 -0600332 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 Walsh74427232018-08-31 12:54:24 -0500344 host = dft(host, socket.gethostname())
Michael Walshf74b3e42018-01-10 11:11:54 -0600345 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500346 try:
347 host_ip = socket.gethostbyname(host)
348 except socket.gaierror as my_gaierror:
349 message = "Unable to obtain the host name for the following host:" +\
350 "\n" + gp.sprint_var(host)
351 gp.print_error_report(message)
352 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500353
Michael Walshf74b3e42018-01-10 11:11:54 -0600354 if short_name:
355 host_short_name = host_name.split(".")[0]
356 return host_name, host_ip, host_short_name
357 else:
358 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500359
Michael Walsheaa16852017-09-19 16:30:43 -0500360
361def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500362 r"""
363 Return true if pid represents an active pid and false otherwise.
364
365 Description of argument(s):
366 pid The pid whose status is being sought.
367 """
368
369 try:
370 os.kill(int(pid), 0)
371 except OSError as err:
372 if err.errno == errno.ESRCH:
373 # ESRCH == No such process
374 return False
375 elif err.errno == errno.EPERM:
376 # EPERM clearly means there's a process to deny access to
377 return True
378 else:
379 # According to "man 2 kill" possible error values are
380 # (EINVAL, EPERM, ESRCH)
381 raise
382
383 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500384
385
386def to_signed(number,
Michael Walshdece16c2018-08-07 15:01:05 -0500387 bit_width=None):
Michael Walsh112c3592018-06-01 14:15:58 -0500388 r"""
389 Convert number to a signed number and return the result.
390
391 Examples:
392
393 With the following code:
394
395 var1 = 0xfffffffffffffff1
396 print_var(var1)
397 print_var(var1, 1)
398 var1 = to_signed(var1)
399 print_var(var1)
400 print_var(var1, 1)
401
402 The following is written to stdout:
403 var1: 18446744073709551601
404 var1: 0x00000000fffffffffffffff1
405 var1: -15
406 var1: 0xfffffffffffffff1
407
408 The same code but with var1 set to 0x000000000000007f produces the
409 following:
410 var1: 127
411 var1: 0x000000000000007f
412 var1: 127
413 var1: 0x000000000000007f
414
415 Description of argument(s):
416 number The number to be converted.
417 bit_width The number of bits that defines a complete
418 hex value. Typically, this would be a
419 multiple of 32.
420 """
421
Michael Walshdece16c2018-08-07 15:01:05 -0500422 if bit_width is None:
423 try:
424 bit_width = gp.bit_length(long(sys.maxsize)) + 1
425 except NameError:
426 bit_width = gp.bit_length(int(sys.maxsize)) + 1
427
Michael Walsh112c3592018-06-01 14:15:58 -0500428 if number < 0:
429 return number
430 neg_bit_mask = 2**(bit_width - 1)
431 if number & neg_bit_mask:
432 return ((2**bit_width) - number) * -1
433 else:
434 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500435
436
437def get_child_pids(quiet=1):
438
439 r"""
440 Get and return a list of pids representing all first-generation processes
441 that are the children of the current process.
442
443 Example:
444
445 children = get_child_pids()
446 print_var(children)
447
448 Output:
449 children:
450 children[0]: 9123
451
452 Description of argument(s):
453 quiet Display output to stdout detailing how
454 this child pids are obtained.
455 """
456
457 if psutil_imported:
458 # If "import psutil" worked, find child pids using psutil.
459 current_process = psutil.Process()
460 return [x.pid for x in current_process.children(recursive=False)]
461 else:
462 # Otherwise, find child pids using shell commands.
463 print_output = not quiet
464
465 ps_cmd_buf = "ps --no-headers --ppid " + str(os.getpid()) +\
466 " -o pid,args"
467 # Route the output of ps to a temporary file for later grepping.
468 # Avoid using " | grep" in the ps command string because it creates
469 # yet another process which is of no interest to the caller.
470 temp = tempfile.NamedTemporaryFile()
471 temp_file_path = temp.name
472 gc.shell_cmd(ps_cmd_buf + " > " + temp_file_path,
473 print_output=print_output)
474 # Sample contents of the temporary file:
475 # 30703 sleep 2
476 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args >
477 # /tmp/tmpqqorWY
478 # Use egrep to exclude the "ps" process itself from the results
479 # collected with the prior shell_cmd invocation. Only the other
480 # children are of interest to the caller. Use cut on the grep results
481 # to obtain only the pid column.
482 rc, output = \
483 gc.shell_cmd("egrep -v '" + re.escape(ps_cmd_buf) + "' "
484 + temp_file_path + " | cut -c1-5",
485 print_output=print_output)
486 # Split the output buffer by line into a list. Strip each element of
487 # extra spaces and convert each element to an integer.
488 return map(int, map(str.strip, filter(None, output.split("\n"))))