1#!/usr/bin/env python3 2 3r""" 4This module provides functions which are useful to plug-ins call-point programs that wish to make external 5robot program calls. 6""" 7 8import importlib.util 9import os 10import re 11import subprocess 12import sys 13import time 14 15import gen_cmd as gc 16import gen_misc as gm 17import gen_print as gp 18import gen_valid as gv 19 20base_path = ( 21 os.path.dirname( 22 os.path.dirname(importlib.util.find_spec("gen_robot_print").origin) 23 ) 24 + os.sep 25) 26 27 28def init_robot_out_parms(extra_prefix=""): 29 r""" 30 Initialize robot output parms such as outputdir, output, etc. 31 32 This function will set global values for the following robot output parms. 33 34 outputdir, output, log, report, loglevel, consolecolors, consolemarkers 35 36 This function would typically be called prior to calling create_robot_cmd_string. 37 38 Description of argument(s): 39 extra_prefix An extra prefix to be appended to the default prefix for output file 40 names. 41 """ 42 43 gp.dprint_executing() 44 AUTOBOOT_OPENBMC_NICKNAME = gm.get_mod_global("AUTOBOOT_OPENBMC_NICKNAME") 45 46 # Set values for call to create_robot_cmd_string. 47 # Environment variable TMP_ROBOT_DIR_PATH can be set by the user to indicate that robot-generated output 48 # should initially be written to the specified temporary directory and then moved to the normal output 49 # location after completion. 50 outputdir = os.environ.get( 51 "TMP_ROBOT_DIR_PATH", 52 os.environ.get( 53 "STATUS_DIR_PATH", os.environ.get("HOME", ".") + "/status" 54 ), 55 ) 56 outputdir = gm.add_trailing_slash(outputdir) 57 seconds = time.time() 58 loc_time = time.localtime(seconds) 59 time_string = time.strftime("%y%m%d.%H%M%S", loc_time) 60 file_prefix = ( 61 AUTOBOOT_OPENBMC_NICKNAME + "." + extra_prefix + time_string + "." 62 ) 63 # Environment variable SAVE_STATUS_POLICY governs when robot-generated output files (e.g. the log.html) 64 # will be moved from TMP_ROBOT_DIR_PATH to FFDC_DIR_PATH. Valid values are "ALWAYS", "NEVER" and "FAIL". 65 SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS") 66 if SAVE_STATUS_POLICY == "NEVER": 67 output = "NONE" 68 log = "NONE" 69 report = "NONE" 70 else: 71 output = file_prefix + "output.xml" 72 log = file_prefix + "log.html" 73 report = file_prefix + "report.html" 74 loglevel = "TRACE" 75 consolecolors = "off" 76 consolemarkers = "off" 77 78 # Make create_robot_cmd_string values global. 79 gm.set_mod_global(outputdir) 80 gm.set_mod_global(output) 81 gm.set_mod_global(log) 82 gm.set_mod_global(report) 83 gm.set_mod_global(loglevel) 84 gm.set_mod_global(consolecolors) 85 gm.set_mod_global(consolemarkers) 86 87 return ( 88 outputdir, 89 output, 90 log, 91 report, 92 loglevel, 93 consolecolors, 94 consolemarkers, 95 ) 96 97 98def init_robot_test_base_dir_path(): 99 r""" 100 Initialize and validate the environment variable, ROBOT_TEST_BASE_DIR_PATH and set corresponding global 101 variable ROBOT_TEST_RUNNING_FROM_SB. 102 103 If ROBOT_TEST_BASE_DIR_PATH is already set, this function will merely validate it. This function will 104 also set environment variable ROBOT_TEST_RUNNING_FROM_SB when ROBOT_TEST_BASE_DIR_PATH is not pre-set. 105 """ 106 107 # ROBOT_TEST_BASE_DIR_PATH will be set as follows: 108 # This function will determine whether we are running in a user sandbox or from a standard apolloxxx 109 # environment. 110 # - User sandbox: 111 # If there is a <developer's home dir>/git/openbmc-test-automation/, ROBOT_TEST_BASE_DIR_PATH will be 112 # set to that path. Otherwise, we set it to <program dir path>/git/openbmc-test-automation/ 113 # - Not in user sandbox: 114 # ROBOT_TEST_BASE_DIR_PATH will be set to <program dir path>/git/openbmc-test-automation/ 115 116 ROBOT_TEST_BASE_DIR_PATH = os.environ.get("ROBOT_TEST_BASE_DIR_PATH", "") 117 ROBOT_TEST_RUNNING_FROM_SB = int( 118 os.environ.get("ROBOT_TEST_RUNNING_FROM_SB", "0") 119 ) 120 if ROBOT_TEST_BASE_DIR_PATH == "": 121 # ROBOT_TEST_BASE_DIR_PATH was not set by user/caller. 122 AUTOIPL_VERSION = os.environ.get("AUTOIPL_VERSION", "") 123 if AUTOIPL_VERSION == "": 124 ROBOT_TEST_BASE_DIR_PATH = base_path 125 else: 126 suffix = "git/openbmc-test-automation/" 127 128 # Determine whether we're running out of a developer sandbox or simply out of an apolloxxx/bin 129 # path. 130 shell_rc, out_buf = gc.shell_cmd( 131 "dirname $(which gen_print.py)", 132 quiet=(not debug), 133 print_output=0, 134 ) 135 executable_base_dir_path = os.path.realpath(out_buf.rstrip()) + "/" 136 apollo_dir_path = ( 137 os.environ["AUTO_BASE_PATH"] + AUTOIPL_VERSION + "/bin/" 138 ) 139 developer_home_dir_path = re.sub( 140 "/sandbox.*", "", executable_base_dir_path 141 ) 142 developer_home_dir_path = gm.add_trailing_slash( 143 developer_home_dir_path 144 ) 145 gp.dprint_vars( 146 executable_base_dir_path, 147 developer_home_dir_path, 148 apollo_dir_path, 149 ) 150 151 ROBOT_TEST_RUNNING_FROM_SB = 0 152 if executable_base_dir_path != apollo_dir_path: 153 ROBOT_TEST_RUNNING_FROM_SB = 1 154 gp.dprint_vars(ROBOT_TEST_RUNNING_FROM_SB) 155 ROBOT_TEST_BASE_DIR_PATH = developer_home_dir_path + suffix 156 if not os.path.isdir(ROBOT_TEST_BASE_DIR_PATH): 157 gp.dprint_timen( 158 "NOTE: Sandbox directory " 159 + ROBOT_TEST_BASE_DIR_PATH 160 + " does not" 161 + " exist." 162 ) 163 # Fall back to the apollo dir path. 164 ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix 165 else: 166 # Use to the apollo dir path. 167 ROBOT_TEST_BASE_DIR_PATH = apollo_dir_path + suffix 168 169 gv.valid_value(ROBOT_TEST_BASE_DIR_PATH) 170 gp.dprint_vars( 171 ROBOT_TEST_RUNNING_FROM_SB, 172 ROBOT_TEST_BASE_DIR_PATH, 173 ) 174 gv.valid_dir_path(ROBOT_TEST_BASE_DIR_PATH) 175 176 ROBOT_TEST_BASE_DIR_PATH = gm.add_trailing_slash(ROBOT_TEST_BASE_DIR_PATH) 177 gm.set_mod_global(ROBOT_TEST_BASE_DIR_PATH) 178 os.environ["ROBOT_TEST_BASE_DIR_PATH"] = ROBOT_TEST_BASE_DIR_PATH 179 180 gm.set_mod_global(ROBOT_TEST_RUNNING_FROM_SB) 181 os.environ["ROBOT_TEST_RUNNING_FROM_SB"] = str(ROBOT_TEST_RUNNING_FROM_SB) 182 183 184raw_robot_file_search_path = ( 185 "${ROBOT_TEST_BASE_DIR_PATH}:" 186 + "${ROBOT_TEST_BASE_DIR_PATH}tests:${ROBOT_TEST_BASE_DIR_PATH}extended:" 187 + "${ROBOT_TEST_BASE_DIR_PATH}scratch:${PATH}" 188) 189 190 191def init_robot_file_path(robot_file_path): 192 r""" 193 Determine full path name for the file path passed in robot_file_path and return it. 194 195 If robot_file_path contains a fully qualified path name, this function will verify that the file exists. 196 If robot_file_path contains a relative path, this function will search for the file and set 197 robot_file_path so that it contains the absolute path to the robot file. This function will search for 198 the robot file using the raw_robot_file_search_path (defined above). Note that if 199 ROBOT_TEST_BASE_DIR_PATH is not set, this function will call init_robot_test_base_dir_path to set it. 200 201 Description of arguments: 202 robot_file_path The absolute or relative path to a robot file. 203 """ 204 205 gv.valid_value(robot_file_path) 206 207 try: 208 if ROBOT_TEST_BASE_DIR_PATH is NONE: 209 init_robot_test_base_dir_path() 210 except NameError: 211 init_robot_test_base_dir_path() 212 213 if not re.match(r".*\.(robot|py)$", robot_file_path): 214 # No suffix so we'll assign one of "\.robot". 215 robot_file_path = robot_file_path + ".robot" 216 217 abs_path = 0 218 if robot_file_path[0:1] == "/": 219 abs_path = 1 220 221 gp.dprint_vars(abs_path, robot_file_path) 222 223 if not abs_path: 224 cmd_buf = 'echo -n "' + raw_robot_file_search_path + '"' 225 shell_rc, out_buf = gc.shell_cmd( 226 cmd_buf, quiet=(not debug), print_output=0 227 ) 228 robot_file_search_paths = out_buf 229 gp.dprint_var(robot_file_search_paths) 230 robot_file_search_paths_list = robot_file_search_paths.split(":") 231 for search_path in robot_file_search_paths_list: 232 search_path = gm.add_trailing_slash(search_path) 233 candidate_file_path = search_path + robot_file_path 234 gp.dprint_var(candidate_file_path) 235 if os.path.isfile(candidate_file_path): 236 gp.dprint_timen("Found full path to " + robot_file_path + ".") 237 robot_file_path = candidate_file_path 238 break 239 240 gp.dprint_var(robot_file_path) 241 gv.valid_file_path(robot_file_path) 242 243 return robot_file_path 244 245 246def get_robot_parm_names(): 247 r""" 248 Return a list containing all of the long parm names (e.g. --outputdir) supported by the robot program. 249 Double dashes are not included in the names returned. 250 """ 251 252 cmd_buf = ( 253 "robot -h | egrep " 254 + "'^([ ]\\-[a-zA-Z0-9])?[ ]+--[a-zA-Z0-9]+[ ]+' | sed -re" 255 + " s'/.*\\-\\-//g' -e s'/ .*//g' | sort -u" 256 ) 257 shell_rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0) 258 259 return out_buf.split("\n") 260 261 262def create_robot_cmd_string(robot_file_path, *parms): 263 r""" 264 Create a robot command string and return it. On failure, return an empty string. 265 266 Description of arguments: 267 robot_file_path The path to the robot file to be run. 268 parms The list of parms to be included in the command string. The name of each 269 variable in this list must be the same as the name of the corresponding 270 parm. This function figures out that name. This function is also able 271 to distinguish robot parms (e.g. --outputdir) from robot program parms 272 (all other parms which will be passed as "-v PARM_NAME:parm_value").. 273 274 Example: 275 276 The following call to this function... 277 cmd_buf = create_robot_cmd_string("tools/start_sol_console.robot", OPENBMC_HOST, quiet, test_mode, debug, 278 outputdir, output, log, report) 279 280 Would return a string something like this. 281 robot -v OPENBMC_HOST:beye6 -v quiet:0 -v test_mode:1 -v debug:1 282 --outputdir=/gsa/ausgsa/projects/a/status --output=beye6.OS_Console.output.xml 283 --log=beye6.OS_Console.log.html --report=beye6.OS_Console.report.html tools/start_sol_console.robot 284 """ 285 286 robot_file_path = init_robot_file_path(robot_file_path) 287 288 robot_parm_names = get_robot_parm_names() 289 290 robot_parm_list = [] 291 292 stack_frame = 2 293 ix = 2 294 for arg in parms: 295 parm = arg 296 parm = gm.quote_bash_parm(gm.escape_bash_quotes(str(parm))) 297 var_name = gp.get_arg_name(None, ix, stack_frame) 298 if var_name in robot_parm_names: 299 p_string = "--" + var_name + "=" + str(parm) 300 robot_parm_list.append(p_string) 301 else: 302 p_string = "-v " + var_name + ":" + str(parm) 303 robot_parm_list.append(p_string) 304 ix += 1 305 306 robot_cmd_buf = ( 307 "robot " + " ".join(robot_parm_list) + " " + robot_file_path 308 ) 309 310 return robot_cmd_buf 311 312 313# Global variables to aid in cleanup after running robot_cmd_fnc. 314gcr_last_robot_cmd_buf = "" 315gcr_last_robot_rc = 0 316 317 318def process_robot_output_files(robot_cmd_buf=None, robot_rc=None, gzip=None): 319 r""" 320 Process robot output files which can involve several operations: 321 - If the files are in a temporary location, using SAVE_STATUS_POLICY to decide whether to move them to a 322 permanent location or to delete them. 323 - Gzipping them. 324 325 Description of argument(s): 326 robot_cmd_buf The complete command string used to invoke robot. 327 robot_rc The return code from running the robot command string. 328 gzip Indicates whether robot-generated output should be gzipped. 329 """ 330 331 robot_cmd_buf = gm.dft(robot_cmd_buf, gcr_last_robot_cmd_buf) 332 robot_rc = gm.dft(robot_rc, gcr_last_robot_rc) 333 gzip = gm.dft(gzip, int(os.environ.get("GZIP_ROBOT", "1"))) 334 335 if robot_cmd_buf == "": 336 # This can legitimately occur if this function is called from an exit_function without the program 337 # having ever run robot_cmd_fnc. 338 return 339 340 SAVE_STATUS_POLICY = os.environ.get("SAVE_STATUS_POLICY", "ALWAYS") 341 gp.qprint_vars(SAVE_STATUS_POLICY) 342 343 # When SAVE_STATUS_POLICY is "NEVER" robot output files don't even get generated. 344 if SAVE_STATUS_POLICY == "NEVER": 345 return 346 347 # Compose file_list based on robot command buffer passed in. 348 robot_cmd_buf_dict = gc.parse_command_string(robot_cmd_buf) 349 outputdir = robot_cmd_buf_dict["outputdir"] 350 outputdir = gm.add_trailing_slash(outputdir) 351 file_list = ( 352 outputdir 353 + robot_cmd_buf_dict["output"] 354 + " " 355 + outputdir 356 + robot_cmd_buf_dict["log"] 357 + " " 358 + outputdir 359 + robot_cmd_buf_dict["report"] 360 ) 361 362 # Double checking that files are present. 363 shell_rc, out_buf = gc.shell_cmd( 364 "ls -1 " + file_list + " 2>/dev/null", show_err=0 365 ) 366 file_list = re.sub("\n", " ", out_buf.rstrip("\n")) 367 368 if file_list == "": 369 gp.qprint_timen( 370 "No robot output files were found in " + outputdir + "." 371 ) 372 return 373 gp.qprint_var(robot_rc, gp.hexa()) 374 if SAVE_STATUS_POLICY == "FAIL" and robot_rc == 0: 375 gp.qprint_timen( 376 "The call to robot produced no failures." 377 + " Deleting robot output files." 378 ) 379 gc.shell_cmd("rm -rf " + file_list) 380 return 381 382 if gzip: 383 gc.shell_cmd("gzip -f " + file_list) 384 # Update the values in file_list. 385 file_list = re.sub(" ", ".gz ", file_list) + ".gz" 386 387 # It TMP_ROBOT_DIR_PATH is set, it means the caller wanted the robot output initially directed to 388 # TMP_ROBOT_DIR_PATH but later moved to FFDC_DIR_PATH. Otherwise, we're done. 389 390 if os.environ.get("TMP_ROBOT_DIR_PATH", "") == "": 391 return 392 393 # We're directing these to the FFDC dir path so that they'll be subjected to FFDC cleanup. 394 target_dir_path = os.environ.get( 395 "FFDC_DIR_PATH", os.environ.get("HOME", ".") + "/ffdc" 396 ) 397 target_dir_path = gm.add_trailing_slash(target_dir_path) 398 399 targ_file_list = [ 400 re.sub(".*/", target_dir_path, x) for x in file_list.split(" ") 401 ] 402 403 gc.shell_cmd( 404 "mv " + file_list + " " + target_dir_path + " >/dev/null", time_out=600 405 ) 406 407 gp.qprint_timen("New robot log file locations:") 408 gp.qprintn("\n".join(targ_file_list)) 409 410 411def robot_cmd_fnc( 412 robot_cmd_buf, 413 robot_jail=os.environ.get("ROBOT_JAIL", ""), 414 quiet=None, 415 test_mode=0, 416): 417 r""" 418 Run the robot command string. 419 420 This function will set the various PATH variables correctly so that you are running the proper version of 421 all imported files, etc. 422 423 Description of argument(s): 424 robot_cmd_buf The complete robot command string. 425 robot_jail Indicates that this is to run in "robot jail" meaning without visibility 426 to any apolloxxx import files, programs, etc. 427 test_mode If test_mode is set, this function will not actually run the command. 428 """ 429 430 quiet = int(gm.dft(quiet, gp.get_stack_var("quiet", 0))) 431 gv.valid_value(robot_cmd_buf) 432 433 # Set global variables to aid in cleanup with process_robot_output_files. 434 global gcr_last_robot_cmd_buf 435 global gcr_last_robot_rc 436 gcr_last_robot_cmd_buf = robot_cmd_buf 437 438 # Get globals set by init_robot_test_base_dir_path(). 439 module = sys.modules["__main__"] 440 try: 441 ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") 442 except NameError: 443 init_robot_test_base_dir_path() 444 ROBOT_TEST_BASE_DIR_PATH = getattr(module, "ROBOT_TEST_BASE_DIR_PATH") 445 446 ROBOT_TEST_RUNNING_FROM_SB = gm.get_mod_global( 447 "ROBOT_TEST_RUNNING_FROM_SB" 448 ) 449 450 if robot_jail == "": 451 if ROBOT_TEST_RUNNING_FROM_SB: 452 robot_jail = 0 453 else: 454 robot_jail = 1 455 456 robot_jail = int(robot_jail) 457 ROBOT_JAIL = os.environ.get("ROBOT_JAIL", "") 458 gp.dprint_vars( 459 ROBOT_TEST_BASE_DIR_PATH, 460 ROBOT_TEST_RUNNING_FROM_SB, 461 ROBOT_JAIL, 462 robot_jail, 463 ) 464 465 # Save PATH and PYTHONPATH to be restored later. 466 os.environ["SAVED_PYTHONPATH"] = os.environ.get("PYTHONPATH", "") 467 os.environ["SAVED_PATH"] = os.environ.get("PATH", "") 468 469 if robot_jail: 470 # Make sure required programs like python and robot can be found in the new restricted PATH. 471 required_programs = "python robot" 472 # It is expected that there will be a "python" program in the tool base bin path which is really a 473 # link to select_version. Ditto for "robot". Call each with the --print_only option to get the 474 # paths to the "real" programs. 475 cmd_buf = ( 476 "for program in " 477 + required_programs 478 + " ; do dirname $(${program} --print_only) ; done 2>/dev/null" 479 ) 480 rc, out_buf = gc.shell_cmd(cmd_buf, quiet=1, print_output=0) 481 PYTHONPATH = ROBOT_TEST_BASE_DIR_PATH + "lib" 482 NEW_PATH_LIST = [ROBOT_TEST_BASE_DIR_PATH + "bin"] 483 NEW_PATH_LIST.extend(list(set(out_buf.rstrip("\n").split("\n")))) 484 NEW_PATH_LIST.extend( 485 [ 486 "/usr/local/sbin", 487 "/usr/local/bin", 488 "/usr/sbin", 489 "/usr/bin", 490 "/sbin", 491 "/bin", 492 ] 493 ) 494 PATH = ":".join(NEW_PATH_LIST) 495 else: 496 PYTHONPATH = ( 497 os.environ.get("PYTHONPATH", "") 498 + ":" 499 + ROBOT_TEST_BASE_DIR_PATH 500 + "lib" 501 ) 502 PATH = ( 503 os.environ.get("PATH", "") + ":" + ROBOT_TEST_BASE_DIR_PATH + "bin" 504 ) 505 506 os.environ["PYTHONPATH"] = PYTHONPATH 507 os.environ["PATH"] = PATH 508 gp.dprint_vars(PATH, PYTHONPATH) 509 510 os.environ["FFDC_DIR_PATH_STYLE"] = os.environ.get( 511 "FFDC_DIR_PATH_STYLE", "1" 512 ) 513 gp.qpissuing(robot_cmd_buf, test_mode) 514 if test_mode: 515 os.environ["PATH"] = os.environ.get("SAVED_PATH", "") 516 os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") 517 return True 518 519 if quiet: 520 DEVNULL = open(os.devnull, "wb") 521 stdout = DEVNULL 522 else: 523 stdout = None 524 sub_proc = subprocess.Popen(robot_cmd_buf, stdout=stdout, shell=True) 525 sub_proc.communicate() 526 shell_rc = sub_proc.returncode 527 os.environ["PATH"] = os.environ.get("SAVED_PATH", "") 528 os.environ["PYTHONPATH"] = os.environ.get("SAVED_PYTHONPATH", "") 529 gcr_last_robot_rc = shell_rc 530 process_robot_output_files() 531 if shell_rc != 0: 532 gp.print_var(shell_rc, gp.hexa()) 533 return False 534 535 return True 536