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