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