xref: /openbmc/openbmc-test-automation/lib/gen_cmd.py (revision 77ab16012bb148c73298314a0a84cc68c4b9ecbe)
1#!/usr/bin/env python
2
3r"""
4This module provides command execution functions such as cmd_fnc and cmd_fnc_u.
5"""
6
7import sys
8import subprocess
9import collections
10
11import gen_print as gp
12import gen_valid as gv
13import gen_misc as gm
14
15robot_env = gp.robot_env
16
17if robot_env:
18    import gen_robot_print as grp
19    from robot.libraries.BuiltIn import BuiltIn
20
21
22def cmd_fnc(cmd_buf,
23            quiet=None,
24            test_mode=None,
25            debug=0,
26            print_output=1,
27            show_err=1,
28            return_stderr=0,
29            ignore_err=1):
30    r"""
31    Run the given command in a shell and return the shell return code and the
32    output.
33
34    Description of arguments:
35    cmd_buf                         The command string to be run in a shell.
36    quiet                           Indicates whether this function should run
37                                    the print_issuing() function which prints
38                                    "Issuing: <cmd string>" to stdout.
39    test_mode                       If test_mode is set, this function will
40                                    not actually run the command.  If
41                                    print_output is set, it will print
42                                    "(test_mode) Issuing: <cmd string>" to
43                                    stdout.
44    debug                           If debug is set, this function will print
45                                    extra debug info.
46    print_output                    If this is set, this function will print
47                                    the stdout/stderr generated by the shell
48                                    command.
49    show_err                        If show_err is set, this function will
50                                    print a standardized error report if the
51                                    shell command returns non-zero.
52    return_stderr                   If return_stderr is set, this function
53                                    will process the stdout and stderr streams
54                                    from the shell command separately.  It
55                                    will also return stderr in addition to the
56                                    return code and the stdout.
57    """
58
59    # Determine default values.
60    quiet = int(gm.global_default(quiet, 0))
61    test_mode = int(gm.global_default(test_mode, 0))
62
63    if debug:
64        gp.print_vars(cmd_buf, quiet, test_mode, debug)
65
66    err_msg = gv.svalid_value(cmd_buf)
67    if err_msg != "":
68        raise ValueError(err_msg)
69
70    if not quiet:
71        gp.pissuing(cmd_buf, test_mode)
72
73    if test_mode:
74        if return_stderr:
75            return 0, "", ""
76        else:
77            return 0, ""
78
79    if return_stderr:
80        err_buf = ""
81        stderr = subprocess.PIPE
82    else:
83        stderr = subprocess.STDOUT
84
85    sub_proc = subprocess.Popen(cmd_buf,
86                                bufsize=1,
87                                shell=True,
88                                stdout=subprocess.PIPE,
89                                stderr=stderr)
90    out_buf = ""
91    if return_stderr:
92        for line in sub_proc.stderr:
93            err_buf += line
94            if not print_output:
95                continue
96            if robot_env:
97                grp.rprint(line)
98            else:
99                sys.stdout.write(line)
100    for line in sub_proc.stdout:
101        out_buf += line
102        if not print_output:
103            continue
104        if robot_env:
105            grp.rprint(line)
106        else:
107            sys.stdout.write(line)
108    if print_output and not robot_env:
109        sys.stdout.flush()
110    sub_proc.communicate()
111    shell_rc = sub_proc.returncode
112    if shell_rc != 0:
113        err_msg = "The prior shell command failed.\n"
114        err_msg += gp.sprint_var(shell_rc, 1)
115        if not print_output:
116            err_msg += "out_buf:\n" + out_buf
117
118        if show_err:
119            if robot_env:
120                grp.rprint_error_report(err_msg)
121            else:
122                gp.print_error_report(err_msg)
123        if not ignore_err:
124            if robot_env:
125                BuiltIn().fail(err_msg)
126            else:
127                raise ValueError(err_msg)
128
129    if return_stderr:
130        return shell_rc, out_buf, err_buf
131    else:
132        return shell_rc, out_buf
133
134
135def cmd_fnc_u(cmd_buf,
136              quiet=None,
137              debug=None,
138              print_output=1,
139              show_err=1,
140              return_stderr=0,
141              ignore_err=1):
142    r"""
143    Call cmd_fnc with test_mode=0.  See cmd_fnc (above) for details.
144
145    Note the "u" in "cmd_fnc_u" stands for "unconditional".
146    """
147
148    return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug,
149                   print_output=print_output, show_err=show_err,
150                   return_stderr=return_stderr, ignore_err=ignore_err)
151
152
153def parse_command_string(command_string):
154    r"""
155    Parse a bash command-line command string and return the result as a
156    dictionary of parms.
157
158    This can be useful for answering questions like "What did the user specify
159    as the value for parm x in the command string?".
160
161    This function expects the command string to follow the following posix
162    conventions:
163    - Short parameters:
164      -<parm name><space><arg value>
165    - Long parameters:
166      --<parm name>=<arg value>
167
168    The first item in the string will be considered to be the command.  All
169    values not conforming to the specifications above will be considered
170    positional parms.  If there are multiple parms with the same name, they
171    will be put into a list (see illustration below where "-v" is specified
172    multiple times).
173
174    Description of argument(s):
175    command_string                  The complete command string including all
176                                    parameters and arguments.
177
178    Sample input:
179
180    robot_cmd_buf:                                    robot -v
181    OPENBMC_HOST:dummy1 -v keyword_string:'Set Auto Reboot  no' -v
182    lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v
183    quiet:0 -v test_mode:0 -v debug:0
184    --outputdir='/home/user1/status/children/'
185    --output=dummy1.Auto_reboot.170802.124544.output.xml
186    --log=dummy1.Auto_reboot.170802.124544.log.html
187    --report=dummy1.Auto_reboot.170802.124544.report.html
188    /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
189
190    Sample output:
191
192    robot_cmd_buf_dict:
193      robot_cmd_buf_dict[command]:                    robot
194      robot_cmd_buf_dict[v]:
195        robot_cmd_buf_dict[v][0]:                     OPENBMC_HOST:dummy1
196        robot_cmd_buf_dict[v][1]:                     keyword_string:Set Auto
197        Reboot no
198        robot_cmd_buf_dict[v][2]:
199        lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot
200        robot_cmd_buf_dict[v][3]:                     quiet:0
201        robot_cmd_buf_dict[v][4]:                     test_mode:0
202        robot_cmd_buf_dict[v][5]:                     debug:0
203      robot_cmd_buf_dict[outputdir]:
204      /home/user1/status/children/
205      robot_cmd_buf_dict[output]:
206      dummy1.Auto_reboot.170802.124544.output.xml
207      robot_cmd_buf_dict[log]:
208      dummy1.Auto_reboot.170802.124544.log.html
209      robot_cmd_buf_dict[report]:
210      dummy1.Auto_reboot.170802.124544.report.html
211      robot_cmd_buf_dict[positional]:
212      /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
213    """
214
215    # We want the parms in the string broken down the way bash would do it,
216    # so we'll call upon bash to do that by creating a simple inline bash
217    # function.
218    bash_func_def = "function parse { for parm in \"${@}\" ; do" +\
219        " echo $parm ; done ; }"
220
221    rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string,
222                           quiet=1, print_output=0)
223    command_string_list = outbuf.rstrip("\n").split("\n")
224
225    command_string_dict = collections.OrderedDict()
226    ix = 1
227    command_string_dict['command'] = command_string_list[0]
228    while ix < len(command_string_list):
229        if command_string_list[ix].startswith("--"):
230            key, value = command_string_list[ix].split("=")
231            key = key.lstrip("-")
232        elif command_string_list[ix].startswith("-"):
233            key = command_string_list[ix].lstrip("-")
234            ix += 1
235            try:
236                value = command_string_list[ix]
237            except IndexError:
238                value = ""
239        else:
240            key = 'positional'
241            value = command_string_list[ix]
242        if key in command_string_dict:
243            if type(command_string_dict[key]) is str:
244                command_string_dict[key] = [command_string_dict[key]]
245            command_string_dict[key].append(value)
246        else:
247            command_string_dict[key] = value
248        ix += 1
249
250    return command_string_dict
251