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