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