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