1#!/usr/bin/env python 2 3r""" 4This module provides many valuable print functions such as sprint_var, 5sprint_time, sprint_error, sprint_call_stack. 6""" 7 8import sys 9import os 10import time 11import inspect 12import re 13import grp 14import socket 15import argparse 16import __builtin__ 17import logging 18import collections 19 20try: 21 robot_env = 1 22 from robot.utils import DotDict 23 from robot.utils import NormalizedDict 24 from robot.libraries.BuiltIn import BuiltIn 25 # Having access to the robot libraries alone does not indicate that we 26 # are in a robot environment. The following try block should confirm that. 27 try: 28 var_value = BuiltIn().get_variable_value("${SUITE_NAME}", "") 29 except: 30 robot_env = 0 31except ImportError: 32 robot_env = 0 33 34import gen_arg as ga 35 36# Setting these variables for use both inside this module and by programs 37# importing this module. 38pgm_file_path = sys.argv[0] 39pgm_name = os.path.basename(pgm_file_path) 40pgm_dir_path = re.sub("/" + pgm_name, "", pgm_file_path) + "/" 41 42 43# Some functions (e.g. sprint_pgm_header) have need of a program name value 44# that looks more like a valid variable name. Therefore, we'll swap odd 45# characters like "." out for underscores. 46pgm_name_var_name = pgm_name.replace(".", "_") 47 48# Initialize global values used as defaults by print_time, print_var, etc. 49col1_indent = 0 50 51# Calculate default column width for print_var functions based on environment 52# variable settings. The objective is to make the variable values line up 53# nicely with the time stamps. 54col1_width = 29 55 56NANOSECONDS = os.environ.get('NANOSECONDS', '1') 57 58 59if NANOSECONDS == "1": 60 col1_width = col1_width + 7 61 62SHOW_ELAPSED_TIME = os.environ.get('SHOW_ELAPSED_TIME', '1') 63 64if SHOW_ELAPSED_TIME == "1": 65 if NANOSECONDS == "1": 66 col1_width = col1_width + 14 67 else: 68 col1_width = col1_width + 7 69 70# Initialize some time variables used in module functions. 71start_time = time.time() 72sprint_time_last_seconds = start_time 73 74# The user can set environment variable "GEN_PRINT_DEBUG" to get debug output 75# from this module. 76gen_print_debug = int(os.environ.get('GEN_PRINT_DEBUG', 0)) 77 78 79############################################################################### 80def sprint_func_name(stack_frame_ix=None): 81 82 r""" 83 Return the function name associated with the indicated stack frame. 84 85 Description of arguments: 86 stack_frame_ix The index of the stack frame whose 87 function name should be returned. If the 88 caller does not specify a value, this 89 function will set the value to 1 which is 90 the index of the caller's stack frame. If 91 the caller is the wrapper function 92 "print_func_name", this function will bump 93 it up by 1. 94 """ 95 96 # If user specified no stack_frame_ix, we'll set it to a proper default 97 # value. 98 if stack_frame_ix is None: 99 func_name = sys._getframe().f_code.co_name 100 caller_func_name = sys._getframe(1).f_code.co_name 101 if func_name[1:] == caller_func_name: 102 stack_frame_ix = 2 103 else: 104 stack_frame_ix = 1 105 106 func_name = sys._getframe(stack_frame_ix).f_code.co_name 107 108 return func_name 109 110############################################################################### 111 112 113# get_arg_name is not a print function per se. I have included it in this 114# module because it is used by sprint_var which is found in this module. 115############################################################################### 116def get_arg_name(var, 117 arg_num=1, 118 stack_frame_ix=1): 119 120 r""" 121 Return the "name" of an argument passed to a function. This could be a 122 literal or a variable name. 123 124 Description of arguments: 125 var The variable whose name you want returned. 126 arg_num The arg number (1 through n) whose name 127 you wish to have returned. This value 128 should not exceed the number of arguments 129 allowed by the target function. 130 stack_frame_ix The stack frame index of the target 131 function. This value must be 1 or 132 greater. 1 would indicate get_arg_name's 133 stack frame. 2 would be the caller of 134 get_arg_name's stack frame, etc. 135 136 Example 1: 137 138 my_var = "mike" 139 var_name = get_arg_name(my_var) 140 141 In this example, var_name will receive the value "my_var". 142 143 Example 2: 144 145 def test1(var): 146 # Getting the var name of the first arg to this function, test1. 147 # Note, in this case, it doesn't matter what you pass as the first arg 148 # to get_arg_name since it is the caller's variable name that matters. 149 dummy = 1 150 arg_num = 1 151 stack_frame = 2 152 var_name = get_arg_name(dummy, arg_num, stack_frame) 153 154 # Mainline... 155 156 another_var = "whatever" 157 test1(another_var) 158 159 In this example, var_name will be set to "another_var". 160 161 """ 162 163 # Note: I wish to avoid recursion so I refrain from calling any function 164 # that calls this function (i.e. sprint_var, valid_value, etc.). 165 166 # The user can set environment variable "GET_ARG_NAME_DEBUG" to get debug 167 # output from this function. 168 local_debug = int(os.environ.get('GET_ARG_NAME_DEBUG', 0)) 169 # In addition to GET_ARG_NAME_DEBUG, the user can set environment 170 # variable "GET_ARG_NAME_SHOW_SOURCE" to have this function include source 171 # code in the debug output. 172 local_debug_show_source = int( 173 os.environ.get('GET_ARG_NAME_SHOW_SOURCE', 0)) 174 175 if arg_num < 1: 176 print_error("Programmer error - Variable \"arg_num\" has an invalid" + 177 " value of \"" + str(arg_num) + "\". The value must be" + 178 " an integer that is greater than 0.\n") 179 # What is the best way to handle errors? Raise exception? I'll 180 # revisit later. 181 return 182 if stack_frame_ix < 1: 183 print_error("Programmer error - Variable \"stack_frame_ix\" has an" + 184 " invalid value of \"" + str(stack_frame_ix) + "\". The" + 185 " value must be an integer that is greater than or equal" + 186 " to 1.\n") 187 return 188 189 if local_debug: 190 debug_indent = 2 191 print("") 192 print_dashes(0, 120) 193 print(sprint_func_name() + "() parms:") 194 print_varx("var", var, 0, debug_indent) 195 print_varx("arg_num", arg_num, 0, debug_indent) 196 print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent) 197 print("") 198 print_call_stack(debug_indent, 2) 199 200 for count in range(0, 2): 201 try: 202 frame, filename, cur_line_no, function_name, lines, index = \ 203 inspect.stack()[stack_frame_ix] 204 except IndexError: 205 print_error("Programmer error - The caller has asked for" + 206 " information about the stack frame at index \"" + 207 str(stack_frame_ix) + "\". However, the stack" + 208 " only contains " + str(len(inspect.stack())) + 209 " entries. Therefore the stack frame index is out" + 210 " of range.\n") 211 return 212 if filename != "<string>": 213 break 214 # filename of "<string>" may mean that the function in question was 215 # defined dynamically and therefore its code stack is inaccessible. 216 # This may happen with functions like "rqprint_var". In this case, 217 # we'll increment the stack_frame_ix and try again. 218 stack_frame_ix += 1 219 if local_debug: 220 print("Adjusted stack_frame_ix...") 221 print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent) 222 223 called_func_name = sprint_func_name(stack_frame_ix) 224 225 module = inspect.getmodule(frame) 226 227 # Though I would expect inspect.getsourcelines(frame) to get all module 228 # source lines if the frame is "<module>", it doesn't do that. Therefore, 229 # for this special case, I will do inspect.getsourcelines(module). 230 if function_name == "<module>": 231 source_lines, source_line_num =\ 232 inspect.getsourcelines(module) 233 line_ix = cur_line_no - source_line_num - 1 234 else: 235 source_lines, source_line_num =\ 236 inspect.getsourcelines(frame) 237 line_ix = cur_line_no - source_line_num 238 239 if local_debug: 240 print("\n Variables retrieved from inspect.stack() function:") 241 print_varx("frame", frame, 0, debug_indent + 2) 242 print_varx("filename", filename, 0, debug_indent + 2) 243 print_varx("cur_line_no", cur_line_no, 0, debug_indent + 2) 244 print_varx("function_name", function_name, 0, debug_indent + 2) 245 print_varx("lines", lines, 0, debug_indent + 2) 246 print_varx("index", index, 0, debug_indent + 2) 247 print_varx("source_line_num", source_line_num, 0, debug_indent) 248 print_varx("line_ix", line_ix, 0, debug_indent) 249 if local_debug_show_source: 250 print_varx("source_lines", source_lines, 0, debug_indent) 251 print_varx("called_func_name", called_func_name, 0, debug_indent) 252 253 # Get a list of all functions defined for the module. Note that this 254 # doesn't work consistently when _run_exitfuncs is at the top of the stack 255 # (i.e. if we're running an exit function). I've coded a work-around 256 # below for this deficiency. 257 all_functions = inspect.getmembers(module, inspect.isfunction) 258 259 # Get called_func_id by searching for our function in the list of all 260 # functions. 261 called_func_id = None 262 for func_name, function in all_functions: 263 if func_name == called_func_name: 264 called_func_id = id(function) 265 break 266 # NOTE: The only time I've found that called_func_id can't be found is 267 # when we're running from an exit function. 268 269 # Look for other functions in module with matching id. 270 aliases = set([called_func_name]) 271 for func_name, function in all_functions: 272 if func_name == called_func_name: 273 continue 274 func_id = id(function) 275 if func_id == called_func_id: 276 aliases.add(func_name) 277 278 # In most cases, my general purpose code above will find all aliases. 279 # However, for the odd case (i.e. running from exit function), I've added 280 # code to handle pvar, qpvar, dpvar, etc. aliases explicitly since they 281 # are defined in this module and used frequently. 282 # pvar is an alias for print_var. 283 aliases.add(re.sub("print_var", "pvar", called_func_name)) 284 285 func_regex = ".*(" + '|'.join(aliases) + ")[ ]*\(" 286 287 # Search backward through source lines looking for the calling function 288 # name. 289 found = False 290 for start_line_ix in range(line_ix, 0, -1): 291 # Skip comment lines. 292 if re.match(r"[ ]*#", source_lines[start_line_ix]): 293 continue 294 if re.match(func_regex, source_lines[start_line_ix]): 295 found = True 296 break 297 if not found: 298 print_error("Programmer error - Could not find the source line with" + 299 " a reference to function \"" + called_func_name + "\".\n") 300 return 301 302 # Search forward through the source lines looking for a line whose 303 # indentation is the same or less than the start line. The end of our 304 # composite line should be the line preceding that line. 305 start_indent = len(source_lines[start_line_ix]) -\ 306 len(source_lines[start_line_ix].lstrip(' ')) 307 end_line_ix = line_ix 308 for end_line_ix in range(line_ix + 1, len(source_lines)): 309 if source_lines[end_line_ix].strip() == "": 310 continue 311 line_indent = len(source_lines[end_line_ix]) -\ 312 len(source_lines[end_line_ix].lstrip(' ')) 313 if line_indent <= start_indent: 314 end_line_ix -= 1 315 break 316 317 # Join the start line through the end line into a composite line. 318 composite_line = ''.join(map(str.strip, 319 source_lines[start_line_ix:end_line_ix + 1])) 320 321 # arg_list_etc = re.sub(".*" + called_func_name, "", composite_line) 322 arg_list_etc = "(" + re.sub(func_regex, "", composite_line) 323 if local_debug: 324 print_varx("aliases", aliases, 0, debug_indent) 325 print_varx("func_regex", func_regex, 0, debug_indent) 326 print_varx("start_line_ix", start_line_ix, 0, debug_indent) 327 print_varx("end_line_ix", end_line_ix, 0, debug_indent) 328 print_varx("composite_line", composite_line, 0, debug_indent) 329 print_varx("arg_list_etc", arg_list_etc, 0, debug_indent) 330 331 # Parse arg list... 332 # Initialize... 333 nest_level = -1 334 arg_ix = 0 335 args_list = [""] 336 for ix in range(0, len(arg_list_etc)): 337 char = arg_list_etc[ix] 338 # Set the nest_level based on whether we've encounted a parenthesis. 339 if char == "(": 340 nest_level += 1 341 if nest_level == 0: 342 continue 343 elif char == ")": 344 nest_level -= 1 345 if nest_level < 0: 346 break 347 348 # If we reach a comma at base nest level, we are done processing an 349 # argument so we increment arg_ix and initialize a new args_list entry. 350 if char == "," and nest_level == 0: 351 arg_ix += 1 352 args_list.append("") 353 continue 354 355 # For any other character, we append it it to the current arg list 356 # entry. 357 args_list[arg_ix] += char 358 359 # Trim whitespace from each list entry. 360 args_list = [arg.strip() for arg in args_list] 361 362 if arg_num > len(args_list): 363 print_error("Programmer error - The caller has asked for the name of" + 364 " argument number \"" + str(arg_num) + "\" but there " + 365 "were only \"" + str(len(args_list)) + "\" args used:\n" + 366 sprint_varx("args_list", args_list)) 367 return 368 369 argument = args_list[arg_num - 1] 370 371 if local_debug: 372 print_varx("args_list", args_list, 0, debug_indent) 373 print_varx("argument", argument, 0, debug_indent) 374 print_dashes(0, 120) 375 376 return argument 377 378############################################################################### 379 380 381############################################################################### 382def sprint_time(buffer=""): 383 384 r""" 385 Return the time in the following format. 386 387 Example: 388 389 The following python code... 390 391 sys.stdout.write(sprint_time()) 392 sys.stdout.write("Hi.\n") 393 394 Will result in the following type of output: 395 396 #(CDT) 2016/07/08 15:25:35 - Hi. 397 398 Example: 399 400 The following python code... 401 402 sys.stdout.write(sprint_time("Hi.\n")) 403 404 Will result in the following type of output: 405 406 #(CDT) 2016/08/03 17:12:05 - Hi. 407 408 The following environment variables will affect the formatting as 409 described: 410 NANOSECONDS This will cause the time stamps to be 411 precise to the microsecond (Yes, it 412 probably should have been named 413 MICROSECONDS but the convention was set 414 long ago so we're sticking with it). 415 Example of the output when environment 416 variable NANOSECONDS=1. 417 418 #(CDT) 2016/08/03 17:16:25.510469 - Hi. 419 420 SHOW_ELAPSED_TIME This will cause the elapsed time to be 421 included in the output. This is the 422 amount of time that has elapsed since the 423 last time this function was called. The 424 precision of the elapsed time field is 425 also affected by the value of the 426 NANOSECONDS environment variable. Example 427 of the output when environment variable 428 NANOSECONDS=0 and SHOW_ELAPSED_TIME=1. 429 430 #(CDT) 2016/08/03 17:17:40 - 0 - Hi. 431 432 Example of the output when environment variable NANOSECONDS=1 and 433 SHOW_ELAPSED_TIME=1. 434 435 #(CDT) 2016/08/03 17:18:47.317339 - 0.000046 - Hi. 436 437 Description of arguments. 438 buffer This will be appended to the formatted 439 time string. 440 """ 441 442 global NANOSECONDS 443 global SHOW_ELAPSED_TIME 444 global sprint_time_last_seconds 445 446 seconds = time.time() 447 loc_time = time.localtime(seconds) 448 nanoseconds = "%0.6f" % seconds 449 pos = nanoseconds.find(".") 450 nanoseconds = nanoseconds[pos:] 451 452 time_string = time.strftime("#(%Z) %Y/%m/%d %H:%M:%S", loc_time) 453 if NANOSECONDS == "1": 454 time_string = time_string + nanoseconds 455 456 if SHOW_ELAPSED_TIME == "1": 457 cur_time_seconds = seconds 458 math_string = "%9.9f" % cur_time_seconds + " - " + "%9.9f" % \ 459 sprint_time_last_seconds 460 elapsed_seconds = eval(math_string) 461 if NANOSECONDS == "1": 462 elapsed_seconds = "%11.6f" % elapsed_seconds 463 else: 464 elapsed_seconds = "%4i" % elapsed_seconds 465 sprint_time_last_seconds = cur_time_seconds 466 time_string = time_string + " - " + elapsed_seconds 467 468 return time_string + " - " + buffer 469 470############################################################################### 471 472 473############################################################################### 474def sprint_timen(buffer=""): 475 476 r""" 477 Append a line feed to the buffer, pass it to sprint_time and return the 478 result. 479 """ 480 481 return sprint_time(buffer + "\n") 482 483############################################################################### 484 485 486############################################################################### 487def sprint_error(buffer=""): 488 489 r""" 490 Return a standardized error string. This includes: 491 - A time stamp 492 - The "**ERROR**" string 493 - The caller's buffer string. 494 495 Example: 496 497 The following python code... 498 499 print(sprint_error("Oops.\n")) 500 501 Will result in the following type of output: 502 503 #(CDT) 2016/08/03 17:12:05 - **ERROR** Oops. 504 505 Description of arguments. 506 buffer This will be appended to the formatted 507 error string. 508 """ 509 510 return sprint_time() + "**ERROR** " + buffer 511 512############################################################################### 513 514 515############################################################################### 516def sprint_varx(var_name, 517 var_value, 518 hex=0, 519 loc_col1_indent=col1_indent, 520 loc_col1_width=col1_width, 521 trailing_char="\n"): 522 523 r""" 524 Print the var name/value passed to it. If the caller lets loc_col1_width 525 default, the printing lines up nicely with output generated by the 526 print_time functions. 527 528 Note that the sprint_var function (defined below) can be used to call this 529 function so that the programmer does not need to pass the var_name. 530 sprint_var will figure out the var_name. The sprint_var function is the 531 one that would normally be used by the general user. 532 533 For example, the following python code: 534 535 first_name = "Mike" 536 print_time("Doing this...\n") 537 print_varx("first_name", first_name) 538 print_time("Doing that...\n") 539 540 Will generate output like this: 541 542 #(CDT) 2016/08/10 17:34:42.847374 - 0.001285 - Doing this... 543 first_name: Mike 544 #(CDT) 2016/08/10 17:34:42.847510 - 0.000136 - Doing that... 545 546 This function recognizes several complex types of data such as dict, list 547 or tuple. 548 549 For example, the following python code: 550 551 my_dict = dict(one=1, two=2, three=3) 552 print_var(my_dict) 553 554 Will generate the following output: 555 556 my_dict: 557 my_dict[three]: 3 558 my_dict[two]: 2 559 my_dict[one]: 1 560 561 Description of arguments. 562 var_name The name of the variable to be printed. 563 var_value The value of the variable to be printed. 564 hex This indicates that the value should be 565 printed in hex format. It is the user's 566 responsibility to ensure that a var_value 567 contains a valid hex number. For string 568 var_values, this will be interpreted as 569 show_blanks which means that blank values 570 will be printed as "<blank>". For dict 571 var_values, this will be interpreted as 572 terse format where keys are not repeated 573 in the output. 574 loc_col1_indent The number of spaces to indent the output. 575 loc_col1_width The width of the output column containing 576 the variable name. The default value of 577 this is adjusted so that the var_value 578 lines up with text printed via the 579 print_time function. 580 trailing_char The character to be used at the end of the 581 returned string. The default value is a 582 line feed. 583 """ 584 585 # Determine the type 586 if type(var_value) in (int, float, bool, str, unicode) \ 587 or var_value is None: 588 # The data type is simple in the sense that it has no subordinate 589 # parts. 590 # Adjust loc_col1_width. 591 loc_col1_width = loc_col1_width - loc_col1_indent 592 # See if the user wants the output in hex format. 593 if hex: 594 if type(var_value) not in (int, long): 595 value_format = "%s" 596 if var_value == "": 597 var_value = "<blank>" 598 else: 599 value_format = "0x%08x" 600 else: 601 value_format = "%s" 602 format_string = "%" + str(loc_col1_indent) + "s%-" \ 603 + str(loc_col1_width) + "s" + value_format + trailing_char 604 if value_format == "0x%08x": 605 return format_string % ("", str(var_name) + ":", 606 var_value & 0xffffffff) 607 else: 608 return format_string % ("", str(var_name) + ":", var_value) 609 elif type(var_value) is type: 610 return sprint_varx(var_name, str(var_value).split("'")[1], hex, 611 loc_col1_indent, loc_col1_width, trailing_char) 612 else: 613 # The data type is complex in the sense that it has subordinate parts. 614 format_string = "%" + str(loc_col1_indent) + "s%s\n" 615 buffer = format_string % ("", var_name + ":") 616 loc_col1_indent += 2 617 try: 618 length = len(var_value) 619 except TypeError: 620 length = 0 621 ix = 0 622 loc_trailing_char = "\n" 623 type_is_dict = 0 624 if type(var_value) is dict: 625 type_is_dict = 1 626 try: 627 if type(var_value) is collections.OrderedDict: 628 type_is_dict = 1 629 except AttributeError: 630 pass 631 try: 632 if type(var_value) is DotDict: 633 type_is_dict = 1 634 except NameError: 635 pass 636 try: 637 if type(var_value) is NormalizedDict: 638 type_is_dict = 1 639 except NameError: 640 pass 641 if type_is_dict: 642 for key, value in var_value.iteritems(): 643 ix += 1 644 if ix == length: 645 loc_trailing_char = trailing_char 646 if hex: 647 # Since hex is being used as a format type, we want it 648 # turned off when processing integer dictionary values so 649 # it is not interpreted as a hex indicator. 650 loc_hex = not (type(value) is int) 651 buffer += sprint_varx("[" + key + "]", value, 652 loc_hex, loc_col1_indent, 653 loc_col1_width, 654 loc_trailing_char) 655 else: 656 buffer += sprint_varx(var_name + "[" + key + "]", value, 657 hex, loc_col1_indent, loc_col1_width, 658 loc_trailing_char) 659 elif type(var_value) in (list, tuple, set): 660 for key, value in enumerate(var_value): 661 ix += 1 662 if ix == length: 663 loc_trailing_char = trailing_char 664 buffer += sprint_varx(var_name + "[" + str(key) + "]", value, 665 hex, loc_col1_indent, loc_col1_width, 666 loc_trailing_char) 667 elif type(var_value) is argparse.Namespace: 668 for key in var_value.__dict__: 669 ix += 1 670 if ix == length: 671 loc_trailing_char = trailing_char 672 cmd_buf = "buffer += sprint_varx(var_name + \".\" + str(key)" \ 673 + ", var_value." + key + ", hex, loc_col1_indent," \ 674 + " loc_col1_width, loc_trailing_char)" 675 exec(cmd_buf) 676 else: 677 var_type = type(var_value).__name__ 678 func_name = sys._getframe().f_code.co_name 679 var_value = "<" + var_type + " type not supported by " + \ 680 func_name + "()>" 681 value_format = "%s" 682 loc_col1_indent -= 2 683 # Adjust loc_col1_width. 684 loc_col1_width = loc_col1_width - loc_col1_indent 685 format_string = "%" + str(loc_col1_indent) + "s%-" \ 686 + str(loc_col1_width) + "s" + value_format + trailing_char 687 return format_string % ("", str(var_name) + ":", var_value) 688 689 return buffer 690 691 return "" 692 693############################################################################### 694 695 696############################################################################### 697def sprint_var(*args): 698 699 r""" 700 Figure out the name of the first argument for you and then call 701 sprint_varx with it. Therefore, the following 2 calls are equivalent: 702 sprint_varx("var1", var1) 703 sprint_var(var1) 704 """ 705 706 # Get the name of the first variable passed to this function. 707 stack_frame = 2 708 caller_func_name = sprint_func_name(2) 709 if caller_func_name.endswith("print_var"): 710 stack_frame += 1 711 var_name = get_arg_name(None, 1, stack_frame) 712 return sprint_varx(var_name, *args) 713 714############################################################################### 715 716 717############################################################################### 718def sprint_vars(*args): 719 720 r""" 721 Sprint the values of one or more variables. 722 723 Description of args: 724 args: 725 If the first argument is an integer, it will be interpreted to be the 726 "indent" value. 727 If the second argument is an integer, it will be interpreted to be the 728 "col1_width" value. 729 If the third argument is an integer, it will be interpreted to be the 730 "hex" value. 731 All remaining parms are considered variable names which are to be 732 sprinted. 733 """ 734 735 if len(args) == 0: 736 return 737 738 # Get the name of the first variable passed to this function. 739 stack_frame = 2 740 caller_func_name = sprint_func_name(2) 741 if caller_func_name.endswith("print_vars"): 742 stack_frame += 1 743 744 parm_num = 1 745 746 # Create list from args (which is a tuple) so that it can be modified. 747 args_list = list(args) 748 749 var_name = get_arg_name(None, parm_num, stack_frame) 750 # See if parm 1 is to be interpreted as "indent". 751 try: 752 if type(int(var_name)) is int: 753 indent = int(var_name) 754 args_list.pop(0) 755 parm_num += 1 756 except ValueError: 757 indent = 0 758 759 var_name = get_arg_name(None, parm_num, stack_frame) 760 # See if parm 1 is to be interpreted as "col1_width". 761 try: 762 if type(int(var_name)) is int: 763 loc_col1_width = int(var_name) 764 args_list.pop(0) 765 parm_num += 1 766 except ValueError: 767 loc_col1_width = col1_width 768 769 var_name = get_arg_name(None, parm_num, stack_frame) 770 # See if parm 1 is to be interpreted as "hex". 771 try: 772 if type(int(var_name)) is int: 773 hex = int(var_name) 774 args_list.pop(0) 775 parm_num += 1 776 except ValueError: 777 hex = 0 778 779 buffer = "" 780 for var_value in args_list: 781 var_name = get_arg_name(None, parm_num, stack_frame) 782 buffer += sprint_varx(var_name, var_value, hex, indent, loc_col1_width) 783 parm_num += 1 784 785 return buffer 786 787############################################################################### 788 789 790############################################################################### 791def lprint_varx(var_name, 792 var_value, 793 hex=0, 794 loc_col1_indent=col1_indent, 795 loc_col1_width=col1_width, 796 log_level=getattr(logging, 'INFO')): 797 798 r""" 799 Send sprint_varx output to logging. 800 """ 801 802 logging.log(log_level, sprint_varx(var_name, var_value, hex, 803 loc_col1_indent, loc_col1_width, "")) 804 805############################################################################### 806 807 808############################################################################### 809def lprint_var(*args): 810 811 r""" 812 Figure out the name of the first argument for you and then call 813 lprint_varx with it. Therefore, the following 2 calls are equivalent: 814 lprint_varx("var1", var1) 815 lprint_var(var1) 816 """ 817 818 # Get the name of the first variable passed to this function. 819 stack_frame = 2 820 caller_func_name = sprint_func_name(2) 821 if caller_func_name.endswith("print_var"): 822 stack_frame += 1 823 var_name = get_arg_name(None, 1, stack_frame) 824 lprint_varx(var_name, *args) 825 826############################################################################### 827 828 829############################################################################### 830def sprint_dashes(indent=col1_indent, 831 width=80, 832 line_feed=1, 833 char="-"): 834 835 r""" 836 Return a string of dashes to the caller. 837 838 Description of arguments: 839 indent The number of characters to indent the 840 output. 841 width The width of the string of dashes. 842 line_feed Indicates whether the output should end 843 with a line feed. 844 char The character to be repeated in the output 845 string. 846 """ 847 848 width = int(width) 849 buffer = " " * int(indent) + char * width 850 if line_feed: 851 buffer += "\n" 852 853 return buffer 854 855############################################################################### 856 857 858############################################################################### 859def sindent(text="", 860 indent=0): 861 862 r""" 863 Pre-pend the specified number of characters to the text string (i.e. 864 indent it) and return it. 865 866 Description of arguments: 867 text The string to be indented. 868 indent The number of characters to indent the 869 string. 870 """ 871 872 format_string = "%" + str(indent) + "s%s" 873 buffer = format_string % ("", text) 874 875 return buffer 876 877############################################################################### 878 879 880############################################################################### 881def sprint_call_stack(indent=0, 882 stack_frame_ix=0): 883 884 r""" 885 Return a call stack report for the given point in the program with line 886 numbers, function names and function parameters and arguments. 887 888 Sample output: 889 890 ------------------------------------------------------------------------- 891 Python function call stack 892 893 Line # Function name and arguments 894 ------ ------------------------------------------------------------------ 895 424 sprint_call_stack () 896 4 print_call_stack () 897 31 func1 (last_name = 'walsh', first_name = 'mikey') 898 59 /tmp/scr5.py 899 ------------------------------------------------------------------------- 900 901 Description of arguments: 902 indent The number of characters to indent each 903 line of output. 904 stack_frame_ix The index of the first stack frame which 905 is to be returned. 906 """ 907 908 buffer = "" 909 buffer += sprint_dashes(indent) 910 buffer += sindent("Python function call stack\n\n", indent) 911 buffer += sindent("Line # Function name and arguments\n", indent) 912 buffer += sprint_dashes(indent, 6, 0) + " " + sprint_dashes(0, 73) 913 914 # Grab the current program stack. 915 current_stack = inspect.stack() 916 917 # Process each frame in turn. 918 format_string = "%6s %s\n" 919 ix = 0 920 for stack_frame in current_stack: 921 if ix < stack_frame_ix: 922 ix += 1 923 continue 924 # I want the line number shown to be the line where you find the line 925 # shown. 926 try: 927 line_num = str(current_stack[ix + 1][2]) 928 except IndexError: 929 line_num = "" 930 func_name = str(stack_frame[3]) 931 if func_name == "?": 932 # "?" is the name used when code is not in a function. 933 func_name = "(none)" 934 935 if func_name == "<module>": 936 # If the func_name is the "main" program, we simply get the 937 # command line call string. 938 func_and_args = ' '.join(sys.argv) 939 else: 940 # Get the program arguments. 941 arg_vals = inspect.getargvalues(stack_frame[0]) 942 function_parms = arg_vals[0] 943 frame_locals = arg_vals[3] 944 945 args_list = [] 946 for arg_name in function_parms: 947 # Get the arg value from frame locals. 948 arg_value = frame_locals[arg_name] 949 args_list.append(arg_name + " = " + repr(arg_value)) 950 args_str = "(" + ', '.join(map(str, args_list)) + ")" 951 952 # Now we need to print this in a nicely-wrapped way. 953 func_and_args = func_name + " " + args_str 954 955 buffer += sindent(format_string % (line_num, func_and_args), indent) 956 ix += 1 957 958 buffer += sprint_dashes(indent) 959 960 return buffer 961 962############################################################################### 963 964 965############################################################################### 966def sprint_executing(stack_frame_ix=None): 967 968 r""" 969 Print a line indicating what function is executing and with what parameter 970 values. This is useful for debugging. 971 972 Sample output: 973 974 #(CDT) 2016/08/25 17:54:27 - Executing: func1 (x = 1) 975 976 Description of arguments: 977 stack_frame_ix The index of the stack frame whose 978 function info should be returned. If the 979 caller does not specify a value, this 980 function will set the value to 1 which is 981 the index of the caller's stack frame. If 982 the caller is the wrapper function 983 "print_executing", this function will bump 984 it up by 1. 985 """ 986 987 # If user wants default stack_frame_ix. 988 if stack_frame_ix is None: 989 func_name = sys._getframe().f_code.co_name 990 caller_func_name = sys._getframe(1).f_code.co_name 991 if caller_func_name.endswith(func_name[1:]): 992 stack_frame_ix = 2 993 else: 994 stack_frame_ix = 1 995 996 stack_frame = inspect.stack()[stack_frame_ix] 997 998 func_name = str(stack_frame[3]) 999 if func_name == "?": 1000 # "?" is the name used when code is not in a function. 1001 func_name = "(none)" 1002 1003 if func_name == "<module>": 1004 # If the func_name is the "main" program, we simply get the command 1005 # line call string. 1006 func_and_args = ' '.join(sys.argv) 1007 else: 1008 # Get the program arguments. 1009 arg_vals = inspect.getargvalues(stack_frame[0]) 1010 function_parms = arg_vals[0] 1011 frame_locals = arg_vals[3] 1012 1013 args_list = [] 1014 for arg_name in function_parms: 1015 # Get the arg value from frame locals. 1016 arg_value = frame_locals[arg_name] 1017 args_list.append(arg_name + " = " + repr(arg_value)) 1018 args_str = "(" + ', '.join(map(str, args_list)) + ")" 1019 1020 # Now we need to print this in a nicely-wrapped way. 1021 func_and_args = func_name + " " + args_str 1022 1023 return sprint_time() + "Executing: " + func_and_args + "\n" 1024 1025############################################################################### 1026 1027 1028############################################################################### 1029def sprint_pgm_header(indent=0, 1030 linefeed=1): 1031 1032 r""" 1033 Return a standardized header that programs should print at the beginning 1034 of the run. It includes useful information like command line, pid, 1035 userid, program parameters, etc. 1036 1037 Description of arguments: 1038 indent The number of characters to indent each 1039 line of output. 1040 linefeed Indicates whether a line feed be included 1041 at the beginning and end of the report. 1042 """ 1043 1044 loc_col1_width = col1_width + indent 1045 1046 buffer = "" 1047 if linefeed: 1048 buffer = "\n" 1049 1050 if robot_env: 1051 suite_name = BuiltIn().get_variable_value("${suite_name}") 1052 buffer += sindent(sprint_time("Running test suite \"" + suite_name + 1053 "\".\n"), indent) 1054 1055 buffer += sindent(sprint_time() + "Running " + pgm_name + ".\n", indent) 1056 buffer += sindent(sprint_time() + "Program parameter values, etc.:\n\n", 1057 indent) 1058 buffer += sprint_varx("command_line", ' '.join(sys.argv), 0, indent, 1059 loc_col1_width) 1060 # We want the output to show a customized name for the pid and pgid but 1061 # we want it to look like a valid variable name. Therefore, we'll use 1062 # pgm_name_var_name which was set when this module was imported. 1063 buffer += sprint_varx(pgm_name_var_name + "_pid", os.getpid(), 0, indent, 1064 loc_col1_width) 1065 buffer += sprint_varx(pgm_name_var_name + "_pgid", os.getpgrp(), 0, indent, 1066 loc_col1_width) 1067 userid_num = str(os.geteuid()) 1068 try: 1069 username = os.getlogin() 1070 except OSError: 1071 if userid_num == "0": 1072 username = "root" 1073 else: 1074 username = "?" 1075 buffer += sprint_varx("uid", userid_num + " (" + username + 1076 ")", 0, indent, loc_col1_width) 1077 buffer += sprint_varx("gid", str(os.getgid()) + " (" + 1078 str(grp.getgrgid(os.getgid()).gr_name) + ")", 0, 1079 indent, loc_col1_width) 1080 buffer += sprint_varx("host_name", socket.gethostname(), 0, indent, 1081 loc_col1_width) 1082 try: 1083 DISPLAY = os.environ['DISPLAY'] 1084 except KeyError: 1085 DISPLAY = "" 1086 buffer += sprint_varx("DISPLAY", DISPLAY, 0, indent, 1087 loc_col1_width) 1088 # I want to add code to print caller's parms. 1089 1090 # __builtin__.arg_obj is created by the get_arg module function, 1091 # gen_get_options. 1092 try: 1093 buffer += ga.sprint_args(__builtin__.arg_obj, indent) 1094 except AttributeError: 1095 pass 1096 1097 if robot_env: 1098 # Get value of global parm_list. 1099 parm_list = BuiltIn().get_variable_value("${parm_list}") 1100 1101 for parm in parm_list: 1102 parm_value = BuiltIn().get_variable_value("${" + parm + "}") 1103 buffer += sprint_varx(parm, parm_value, 0, indent, loc_col1_width) 1104 1105 # Setting global program_pid. 1106 BuiltIn().set_global_variable("${program_pid}", os.getpid()) 1107 1108 if linefeed: 1109 buffer += "\n" 1110 1111 return buffer 1112 1113############################################################################### 1114 1115 1116############################################################################### 1117def sprint_error_report(error_text="\n", 1118 indent=2, 1119 format=None): 1120 1121 r""" 1122 Return a string with a standardized report which includes the caller's 1123 error text, the call stack and the program header. 1124 1125 Description of args: 1126 error_text The error text to be included in the 1127 report. The caller should include any 1128 needed linefeeds. 1129 indent The number of characters to indent each 1130 line of output. 1131 format Long or short format. Long includes 1132 extras like lines of dashes, call stack, 1133 etc. 1134 """ 1135 1136 # Process input. 1137 indent = int(indent) 1138 if format is None: 1139 if robot_env: 1140 format = 'short' 1141 else: 1142 format = 'long' 1143 error_text = error_text.rstrip('\n') + '\n' 1144 1145 if format == 'short': 1146 return sprint_error(error_text) 1147 1148 buffer = "" 1149 buffer += sprint_dashes(width=120, char="=") 1150 buffer += sprint_error(error_text) 1151 buffer += "\n" 1152 # Calling sprint_call_stack with stack_frame_ix of 0 causes it to show 1153 # itself and this function in the call stack. This is not helpful to a 1154 # debugger and is therefore clutter. We will adjust the stack_frame_ix to 1155 # hide that information. 1156 stack_frame_ix = 2 1157 caller_func_name = sprint_func_name(2) 1158 if caller_func_name.endswith("print_error_report"): 1159 stack_frame_ix += 1 1160 if not robot_env: 1161 buffer += sprint_call_stack(indent, stack_frame_ix) 1162 buffer += sprint_pgm_header(indent) 1163 buffer += sprint_dashes(width=120, char="=") 1164 1165 return buffer 1166 1167############################################################################### 1168 1169 1170############################################################################### 1171def sprint_issuing(cmd_buf, 1172 test_mode=0): 1173 1174 r""" 1175 Return a line indicating a command that the program is about to execute. 1176 1177 Sample output for a cmd_buf of "ls" 1178 1179 #(CDT) 2016/08/25 17:57:36 - Issuing: ls 1180 1181 Description of args: 1182 cmd_buf The command to be executed by caller. 1183 test_mode With test_mode set, your output will look 1184 like this: 1185 1186 #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls 1187 1188 """ 1189 1190 buffer = sprint_time() 1191 if test_mode: 1192 buffer += "(test_mode) " 1193 buffer += "Issuing: " + cmd_buf + "\n" 1194 1195 return buffer 1196 1197############################################################################### 1198 1199 1200############################################################################### 1201def sprint_pgm_footer(): 1202 1203 r""" 1204 Return a standardized footer that programs should print at the end of the 1205 program run. It includes useful information like total run time, etc. 1206 """ 1207 1208 buffer = "\n" + sprint_time() + "Finished running " + pgm_name + ".\n\n" 1209 1210 total_time = time.time() - start_time 1211 total_time_string = "%0.6f" % total_time 1212 1213 buffer += sprint_varx(pgm_name_var_name + "_runtime", total_time_string) 1214 buffer += "\n" 1215 1216 return buffer 1217 1218############################################################################### 1219 1220 1221############################################################################### 1222def sprint(buffer=""): 1223 1224 r""" 1225 Simply return the user's buffer. This function is used by the qprint and 1226 dprint functions defined dynamically below, i.e. it would not normally be 1227 called for general use. 1228 1229 Description of arguments. 1230 buffer This will be returned to the caller. 1231 """ 1232 1233 return str(buffer) 1234 1235############################################################################### 1236 1237 1238############################################################################### 1239def sprintn(buffer=""): 1240 1241 r""" 1242 Simply return the user's buffer with a line feed. This function is used 1243 by the qprint and dprint functions defined dynamically below, i.e. it 1244 would not normally be called for general use. 1245 1246 Description of arguments. 1247 buffer This will be returned to the caller. 1248 """ 1249 1250 buffer = str(buffer) + "\n" 1251 1252 return buffer 1253 1254############################################################################### 1255 1256 1257############################################################################### 1258def gp_debug_print(buffer): 1259 1260 r""" 1261 Print buffer to stdout only if gen_print_debug is set. 1262 1263 This function is intended for use only by other functions in this module. 1264 1265 Description of arguments: 1266 buffer The string to be printed. 1267 """ 1268 1269 if not gen_print_debug: 1270 return 1271 1272 if robot_env: 1273 BuiltIn().log_to_console(buffer) 1274 else: 1275 print(buffer) 1276 1277############################################################################### 1278 1279 1280############################################################################### 1281def get_var_value(var_value=None, 1282 default=1, 1283 var_name=None): 1284 1285 r""" 1286 Return either var_value, the corresponding global value or default. 1287 1288 If var_value is not None, it will simply be returned. 1289 1290 If var_value is None, this function will return the corresponding global 1291 value of the variable in question. 1292 1293 Note: For global values, if we are in a robot environment, 1294 get_variable_value will be used. Otherwise, the __builtin__ version of 1295 the variable is returned (which are set by gen_arg.py functions). 1296 1297 If there is no global value associated with the variable, default is 1298 returned. 1299 1300 This function is useful for other functions in setting default values for 1301 parameters. 1302 1303 Example use: 1304 1305 def my_func(quiet=None): 1306 1307 quiet = int(get_var_value(quiet, 0)) 1308 1309 Example calls to my_func(): 1310 1311 In the following example, the caller is explicitly asking to have quiet be 1312 set to 1. 1313 1314 my_func(quiet=1) 1315 1316 In the following example, quiet will be set to the global value of quiet, 1317 if defined, or to 0 (the default). 1318 1319 my_func() 1320 1321 Description of arguments: 1322 var_value The value to be returned (if not equal to 1323 None). 1324 default The value that is returned if var_value is 1325 None and there is no corresponding global 1326 value defined. 1327 var_name The name of the variable whose value is to 1328 be returned. Under most circumstances, 1329 this value need not be provided. This 1330 function can figure out the name of the 1331 variable passed as var_value. One 1332 exception to this would be if this 1333 function is called directly from a .robot 1334 file. 1335 """ 1336 1337 if var_value is not None: 1338 return var_value 1339 1340 if var_name is None: 1341 var_name = get_arg_name(None, 1, 2) 1342 1343 if robot_env: 1344 var_value = BuiltIn().get_variable_value("${" + var_name + "}", 1345 default) 1346 else: 1347 var_value = getattr(__builtin__, var_name, default) 1348 1349 return var_value 1350 1351############################################################################### 1352 1353 1354# hidden_text is a list of passwords which are to be replaced with asterisks 1355# by print functions defined in this module. 1356hidden_text = [] 1357# password_regex is created based on the contents of hidden_text. 1358password_regex = "" 1359 1360 1361############################################################################### 1362def register_passwords(*args): 1363 1364 r""" 1365 Register one or more passwords which are to be hidden in output produced 1366 by the print functions in this module. 1367 1368 Note: Blank password values are NOT registered. They are simply ignored. 1369 1370 Description of argument(s): 1371 args One or more password values. If a given 1372 password value is already registered, this 1373 function will simply do nothing. 1374 """ 1375 1376 global hidden_text 1377 global password_regex 1378 1379 for password in args: 1380 if password == "": 1381 break 1382 if password in hidden_text: 1383 break 1384 1385 # Place the password into the hidden_text list. 1386 hidden_text.append(password) 1387 # Create a corresponding password regular expression. Escape regex 1388 # special characters too. 1389 password_regex = '(' +\ 1390 '|'.join([re.escape(x) for x in hidden_text]) + ')' 1391 1392############################################################################### 1393 1394 1395############################################################################### 1396def replace_passwords(buffer): 1397 1398 r""" 1399 Return the buffer but with all registered passwords replaced by a string 1400 of asterisks. 1401 1402 1403 Description of argument(s): 1404 buffer The string to be returned but with 1405 passwords replaced. 1406 """ 1407 1408 global password_regex 1409 1410 if int(os.environ.get("DEBUG_SHOW_PASSWORDS", "0")): 1411 return buffer 1412 1413 if password_regex == "": 1414 # No passwords to replace. 1415 return buffer 1416 1417 return re.sub(password_regex, "********", buffer) 1418 1419############################################################################### 1420 1421 1422############################################################################### 1423# In the following section of code, we will dynamically create print versions 1424# for each of the sprint functions defined above. So, for example, where we 1425# have an sprint_time() function defined above that returns the time to the 1426# caller in a string, we will create a corresponding print_time() function 1427# that will print that string directly to stdout. 1428 1429# It can be complicated to follow what's being created by the exec statements 1430# below. Here is an example of the print_time() function that will be created: 1431 1432# def print_time(*args): 1433# s_func = getattr(sys.modules[__name__], "sprint_time") 1434# sys.stdout.write(s_func(*args)) 1435# sys.stdout.flush() 1436 1437# Here are comments describing the 3 lines in the body of the created function. 1438# Create a reference to the "s" version of the given function in s_func (e.g. 1439# if this function name is print_time, we want s_funcname to be "sprint_time"). 1440# Call the "s" version of this function passing it all of our arguments. 1441# Write the result to stdout. 1442 1443# func_names contains a list of all print functions which should be created 1444# from their sprint counterparts. 1445func_names = ['print_time', 'print_timen', 'print_error', 'print_varx', 1446 'print_var', 'print_vars', 'print_dashes', 'indent', 1447 'print_call_stack', 'print_func_name', 'print_executing', 1448 'print_pgm_header', 'print_issuing', 'print_pgm_footer', 1449 'print_error_report', 'print', 'printn'] 1450 1451# stderr_func_names is a list of functions whose output should go to stderr 1452# rather than stdout. 1453stderr_func_names = ['print_error', 'print_error_report'] 1454 1455gp_debug_print("robot_env: " + str(robot_env)) 1456for func_name in func_names: 1457 gp_debug_print("func_name: " + func_name) 1458 if func_name in stderr_func_names: 1459 output_stream = "stderr" 1460 else: 1461 output_stream = "stdout" 1462 1463 func_def_line = "def " + func_name + "(*args):" 1464 s_func_line = " s_func = getattr(sys.modules[__name__], \"s" +\ 1465 func_name + "\")" 1466 # Generate the code to do the printing. 1467 if robot_env: 1468 func_print_lines = \ 1469 [ 1470 " BuiltIn().log_to_console(replace_passwords" + 1471 "(s_func(*args))," 1472 " stream='" + output_stream + "'," 1473 " no_newline=True)" 1474 ] 1475 else: 1476 func_print_lines = \ 1477 [ 1478 " sys." + output_stream + 1479 ".write(replace_passwords(s_func(*args)))", 1480 " sys." + output_stream + ".flush()" 1481 ] 1482 1483 # Create an array containing the lines of the function we wish to create. 1484 func_def = [func_def_line, s_func_line] + func_print_lines 1485 # We don't want to try to redefine the "print" function, thus the if 1486 # statement. 1487 if func_name != "print": 1488 pgm_definition_string = '\n'.join(func_def) 1489 gp_debug_print(pgm_definition_string) 1490 exec(pgm_definition_string) 1491 1492 # Insert a blank line which will be overwritten by the next several 1493 # definitions. 1494 func_def.insert(1, "") 1495 1496 # Define the "q" (i.e. quiet) version of the given print function. 1497 func_def[0] = "def q" + func_name + "(*args):" 1498 func_def[1] = " if int(get_var_value(None, 0, \"quiet\")): return" 1499 pgm_definition_string = '\n'.join(func_def) 1500 gp_debug_print(pgm_definition_string) 1501 exec(pgm_definition_string) 1502 1503 # Define the "d" (i.e. debug) version of the given print function. 1504 func_def[0] = "def d" + func_name + "(*args):" 1505 func_def[1] = " if not int(get_var_value(None, 0, \"debug\")): return" 1506 pgm_definition_string = '\n'.join(func_def) 1507 gp_debug_print(pgm_definition_string) 1508 exec(pgm_definition_string) 1509 1510 # Define the "l" (i.e. log) version of the given print function. 1511 func_def_line = "def l" + func_name + "(*args):" 1512 func_print_lines = \ 1513 [ 1514 " logging.log(getattr(logging, 'INFO'), s_func(*args))" 1515 ] 1516 1517 func_def = [func_def_line, s_func_line] + func_print_lines 1518 if func_name != "print_varx" and func_name != "print_var": 1519 pgm_definition_string = '\n'.join(func_def) 1520 gp_debug_print(pgm_definition_string) 1521 exec(pgm_definition_string) 1522 1523 if func_name == "print" or func_name == "printn": 1524 gp_debug_print("") 1525 continue 1526 1527 # Create abbreviated aliases (e.g. spvar is an alias for sprint_var). 1528 alias = re.sub("print_", "p", func_name) 1529 prefixes = ["", "s", "q", "d", "l"] 1530 for prefix in prefixes: 1531 pgm_definition_string = prefix + alias + " = " + prefix + func_name 1532 gp_debug_print(pgm_definition_string) 1533 exec(pgm_definition_string) 1534 1535 gp_debug_print("") 1536 1537############################################################################### 1538