blob: 009c1c63ad757beb56ff533898864414047117d7 [file] [log] [blame]
Michael Walshc3b512e2017-02-20 15:59:01 -06001#!/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
Michael Walshf41fac82017-08-02 15:05:24 -05009import collections
Michael Walshc3b512e2017-02-20 15:59:01 -060010
Michael Walshc3b512e2017-02-20 15:59:01 -060011import gen_print as gp
12import gen_valid as gv
13import gen_misc as gm
Michael Walshd6901502017-11-14 12:58:37 -060014
15robot_env = gp.robot_env
16
Michael Walshafc53a22017-04-12 15:52:28 -050017if robot_env:
18 import gen_robot_print as grp
Michael Walshc3b512e2017-02-20 15:59:01 -060019
20
Michael Walshc3b512e2017-02-20 15:59:01 -060021def cmd_fnc(cmd_buf,
22 quiet=None,
23 test_mode=None,
Michael Walshafc53a22017-04-12 15:52:28 -050024 debug=0,
Michael Walshc3b512e2017-02-20 15:59:01 -060025 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -050026 show_err=1,
27 return_stderr=0):
Michael Walshc3b512e2017-02-20 15:59:01 -060028
29 r"""
Michael Walshcfe9fed2017-09-12 17:13:10 -050030 Run the given command in a shell and return the shell return code and the
31 output.
Michael Walshc3b512e2017-02-20 15:59:01 -060032
33 Description of arguments:
34 cmd_buf The command string to be run in a shell.
35 quiet Indicates whether this function should run
Michael Walshcfe9fed2017-09-12 17:13:10 -050036 the print_issuing() function which prints
37 "Issuing: <cmd string>" to stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060038 test_mode If test_mode is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050039 not actually run the command. If
40 print_output is set, it will print
41 "(test_mode) Issuing: <cmd string>" to
42 stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060043 debug If debug is set, this function will print
44 extra debug info.
45 print_output If this is set, this function will print
Michael Walshcfe9fed2017-09-12 17:13:10 -050046 the stdout/stderr generated by the shell
47 command.
Michael Walshc3b512e2017-02-20 15:59:01 -060048 show_err If show_err is set, this function will
Michael Walshcfe9fed2017-09-12 17:13:10 -050049 print a standardized error report if the
50 shell command returns non-zero.
51 return_stderr If return_stderr is set, this function
52 will process the stdout and stderr streams
53 from the shell command separately. It
54 will also return stderr in addition to the
55 return code and the stdout.
Michael Walshc3b512e2017-02-20 15:59:01 -060056 """
57
Michael Walshcfe9fed2017-09-12 17:13:10 -050058 # Determine default values.
Michael Walshc3b512e2017-02-20 15:59:01 -060059 quiet = int(gm.global_default(quiet, 0))
60 test_mode = int(gm.global_default(test_mode, 0))
Michael Walshc3b512e2017-02-20 15:59:01 -060061
62 if debug:
Michael Walshafc53a22017-04-12 15:52:28 -050063 gp.print_vars(cmd_buf, quiet, test_mode, debug)
Michael Walshc3b512e2017-02-20 15:59:01 -060064
65 err_msg = gv.svalid_value(cmd_buf)
66 if err_msg != "":
67 raise ValueError(err_msg)
68
69 if not quiet:
Michael Walshafc53a22017-04-12 15:52:28 -050070 gp.pissuing(cmd_buf, test_mode)
Michael Walshc3b512e2017-02-20 15:59:01 -060071
72 if test_mode:
Michael Walshcfe9fed2017-09-12 17:13:10 -050073 if return_stderr:
74 return 0, "", ""
75 else:
76 return 0, ""
77
78 if return_stderr:
79 err_buf = ""
80 stderr = subprocess.PIPE
81 else:
82 stderr = subprocess.STDOUT
Michael Walshc3b512e2017-02-20 15:59:01 -060083
84 sub_proc = subprocess.Popen(cmd_buf,
85 bufsize=1,
86 shell=True,
87 stdout=subprocess.PIPE,
Michael Walshcfe9fed2017-09-12 17:13:10 -050088 stderr=stderr)
Michael Walshc3b512e2017-02-20 15:59:01 -060089 out_buf = ""
Michael Walshcfe9fed2017-09-12 17:13:10 -050090 if return_stderr:
91 for line in sub_proc.stderr:
92 err_buf += line
93 if not print_output:
94 continue
95 if robot_env:
96 grp.rprint(line)
97 else:
98 sys.stdout.write(line)
Michael Walshc3b512e2017-02-20 15:59:01 -060099 for line in sub_proc.stdout:
100 out_buf += line
101 if not print_output:
102 continue
103 if robot_env:
104 grp.rprint(line)
105 else:
106 sys.stdout.write(line)
107 if print_output and not robot_env:
108 sys.stdout.flush()
109 sub_proc.communicate()
110 shell_rc = sub_proc.returncode
111 if shell_rc != 0 and show_err:
Michael Walshcfe9fed2017-09-12 17:13:10 -0500112 err_msg = "The prior command failed.\n" + gp.sprint_var(shell_rc, 1)
113 if not print_output:
114 err_msg += "out_buf:\n" + out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600115
Michael Walshcfe9fed2017-09-12 17:13:10 -0500116 if robot_env:
117 grp.rprint_error_report(err_msg)
118 else:
119 gp.print_error_report(err_msg)
120
121 if return_stderr:
122 return shell_rc, out_buf, err_buf
123 else:
124 return shell_rc, out_buf
Michael Walshc3b512e2017-02-20 15:59:01 -0600125
Michael Walshc3b512e2017-02-20 15:59:01 -0600126
Michael Walshc3b512e2017-02-20 15:59:01 -0600127def cmd_fnc_u(cmd_buf,
128 quiet=None,
129 debug=None,
130 print_output=1,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500131 show_err=1,
132 return_stderr=0):
Michael Walshc3b512e2017-02-20 15:59:01 -0600133
134 r"""
135 Call cmd_fnc with test_mode=0. See cmd_fnc (above) for details.
136
137 Note the "u" in "cmd_fnc_u" stands for "unconditional".
138 """
139
140 return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug,
Michael Walshcfe9fed2017-09-12 17:13:10 -0500141 print_output=print_output, show_err=show_err,
142 return_stderr=return_stderr)
Michael Walshc3b512e2017-02-20 15:59:01 -0600143
Michael Walshf41fac82017-08-02 15:05:24 -0500144
Michael Walshf41fac82017-08-02 15:05:24 -0500145def parse_command_string(command_string):
146
147 r"""
148 Parse a bash command-line command string and return the result as a
149 dictionary of parms.
150
151 This can be useful for answering questions like "What did the user specify
152 as the value for parm x in the command string?".
153
154 This function expects the command string to follow the following posix
155 conventions:
156 - Short parameters:
157 -<parm name><space><arg value>
158 - Long parameters:
159 --<parm name>=<arg value>
160
161 The first item in the string will be considered to be the command. All
162 values not conforming to the specifications above will be considered
163 positional parms. If there are multiple parms with the same name, they
164 will be put into a list (see illustration below where "-v" is specified
165 multiple times).
166
167 Description of argument(s):
168 command_string The complete command string including all
169 parameters and arguments.
170
171 Sample input:
172
173 robot_cmd_buf: robot -v
174 OPENBMC_HOST:dummy1 -v keyword_string:'Set Auto Reboot no' -v
175 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v
176 quiet:0 -v test_mode:0 -v debug:0
177 --outputdir='/home/user1/status/children/'
178 --output=dummy1.Auto_reboot.170802.124544.output.xml
179 --log=dummy1.Auto_reboot.170802.124544.log.html
180 --report=dummy1.Auto_reboot.170802.124544.report.html
181 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
182
183 Sample output:
184
185 robot_cmd_buf_dict:
186 robot_cmd_buf_dict[command]: robot
187 robot_cmd_buf_dict[v]:
188 robot_cmd_buf_dict[v][0]: OPENBMC_HOST:dummy1
189 robot_cmd_buf_dict[v][1]: keyword_string:Set Auto
190 Reboot no
191 robot_cmd_buf_dict[v][2]:
192 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot
193 robot_cmd_buf_dict[v][3]: quiet:0
194 robot_cmd_buf_dict[v][4]: test_mode:0
195 robot_cmd_buf_dict[v][5]: debug:0
196 robot_cmd_buf_dict[outputdir]:
197 /home/user1/status/children/
198 robot_cmd_buf_dict[output]:
199 dummy1.Auto_reboot.170802.124544.output.xml
200 robot_cmd_buf_dict[log]:
201 dummy1.Auto_reboot.170802.124544.log.html
202 robot_cmd_buf_dict[report]:
203 dummy1.Auto_reboot.170802.124544.report.html
204 robot_cmd_buf_dict[positional]:
205 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot
206 """
207
208 # We want the parms in the string broken down the way bash would do it,
209 # so we'll call upon bash to do that by creating a simple inline bash
210 # function.
211 bash_func_def = "function parse { for parm in \"${@}\" ; do" +\
212 " echo $parm ; done ; }"
213
214 rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string,
215 quiet=1, print_output=0)
216 command_string_list = outbuf.rstrip("\n").split("\n")
217
218 command_string_dict = collections.OrderedDict()
219 ix = 1
220 command_string_dict['command'] = command_string_list[0]
221 while ix < len(command_string_list):
222 if command_string_list[ix].startswith("--"):
223 key, value = command_string_list[ix].split("=")
224 key = key.lstrip("-")
225 elif command_string_list[ix].startswith("-"):
226 key = command_string_list[ix].lstrip("-")
227 ix += 1
228 try:
229 value = command_string_list[ix]
230 except IndexError:
231 value = ""
232 else:
233 key = 'positional'
234 value = command_string_list[ix]
235 if key in command_string_dict:
236 if type(command_string_dict[key]) is str:
237 command_string_dict[key] = [command_string_dict[key]]
238 command_string_dict[key].append(value)
239 else:
240 command_string_dict[key] = value
241 ix += 1
242
243 return command_string_dict