1#!/usr/bin/env python 2 3r""" 4This module provides many valuable functions such as my_parm_file. 5""" 6 7# sys and os are needed to get the program dir path and program name. 8import sys 9import errno 10import os 11import collections 12try: 13 import ConfigParser 14except ImportError: 15 import configparser 16try: 17 import StringIO 18except ImportError: 19 import io 20import re 21import socket 22import tempfile 23try: 24 import psutil 25 psutil_imported = True 26except ImportError: 27 psutil_imported = False 28 29import gen_print as gp 30import gen_cmd as gc 31 32robot_env = gp.robot_env 33if robot_env: 34 from robot.libraries.BuiltIn import BuiltIn 35 from robot.utils import DotDict 36 37 38def add_trailing_slash(dir_path): 39 r""" 40 Add a trailing slash to the directory path if it doesn't already have one 41 and return it. 42 43 Description of arguments: 44 dir_path A directory path. 45 """ 46 47 return os.path.normpath(dir_path) + os.path.sep 48 49 50def which(file_path): 51 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 78 79def dft(value, default): 80 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 99 100def get_mod_global(var_name, 101 default=None, 102 mod_name="__main__"): 103 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: 124 gp.print_error_report("Programmer error - The mod_name passed to" 125 + " this function is invalid:\n" 126 + gp.sprint_var(mod_name)) 127 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 134 135def global_default(var_value, 136 default=0): 137 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)) 158 159 160def set_mod_global(var_value, 161 mod_name="__main__", 162 var_name=None): 163 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: 178 gp.print_error_report("Programmer error - The mod_name passed to" 179 + " this function is invalid:\n" 180 + gp.sprint_var(mod_name)) 181 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 188 189def my_parm_file(prop_file_path): 190 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: 204 prop_file_path The caller should pass the path to the 205 properties file. 206 """ 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 212 try: 213 string_file = StringIO.StringIO() 214 except NameError: 215 string_file = io.StringIO() 216 217 # 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. 225 try: 226 config_parser = ConfigParser.ConfigParser() 227 except NameError: 228 config_parser = configparser.ConfigParser() 229 # 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. 234 if robot_env: 235 return DotDict(config_parser.items('dummysection')) 236 else: 237 return collections.OrderedDict(config_parser.items('dummysection')) 238 239 240def file_to_list(file_path, 241 newlines=0, 242 comments=1, 243 trim=0): 244 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 275 276def return_path_list(): 277 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 288 289def escape_bash_quotes(buffer): 290 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 312def quote_bash_parm(parm): 313 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 330 331def get_host_name_ip(host=None, 332 short_name=0): 333 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): 338 host The host name or IP address to be obtained. 339 short_name Include the short host name in the 340 returned tuple, i.e. return host, ip and 341 short_host. 342 """ 343 344 host = dft(host, socket.gethostname()) 345 host_name = socket.getfqdn(host) 346 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 353 354 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 359 360 361def pid_active(pid): 362 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 384 385 386def to_signed(number, 387 bit_width=None): 388 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 422 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 428 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 435 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")))) 489