Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(374)

Side by Side Diff: go/exec/exec.go

Issue 1295043002: Revert of Add a library for running external commands, providing timeouts and test injection. (Closed) Base URL: https://skia.googlesource.com/buildbot@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ct/go/util/util.go ('k') | go/exec/exec_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 A wrapper around the os/exec package that supports timeouts and testing.
3
4 Example usage:
5
6 Simple command with argument:
7 err := Run(&Command{
8 Name: "touch",
9 Args: []string{file},
10 })
11
12 More complicated example:
13 output := bytes.Buffer{}
14 err := Run(&Command{
15 Name: "make",
16 Args: []string{"all"},
17 // Set environment:
18 Env: []string{fmt.Sprintf("GOPATH=%s", projectGoPath)},
19 // Set working directory:
20 Dir: projectDir,
21 // Capture output:
22 CombinedOutput: &output,
23 // Set a timeout:
24 Timeout: 10*time.Minute,
25 })
26
27 Inject a Run function for testing:
28 var actualCommand *Command
29 SetRunForTesting(func(command *Command) error {
30 actualCommand = command
31 return nil
32 })
33 defer SetRunForTesting(DefaultRun)
34 TestCodeCallingRun()
35 expect.Equal(t, "touch", actualCommand.Name)
36 expect.Equal(t, 1, len(actualCommand.Args))
37 expect.Equal(t, file, actualCommand.Args[0])
38 */
39 package exec
40
41 import (
42 "bytes"
43 "fmt"
44 "io"
45 "os"
46 osexec "os/exec"
47 "strings"
48 "time"
49
50 "github.com/skia-dev/glog"
51 )
52
53 // WriteLog implements the io.Writer interface and writes to the given log funct ion.
54 type WriteLog struct {
55 LogFunc func(format string, args ...interface{})
56 }
57
58 func (wl WriteLog) Write(p []byte) (n int, err error) {
59 wl.LogFunc("%s", string(p))
60 return len(p), nil
61 }
62
63 var (
64 WriteInfoLog = WriteLog{LogFunc: glog.Infof}
65 WriteErrorLog = WriteLog{LogFunc: glog.Errorf}
66 )
67
68 type Command struct {
69 // Name of the command, as passed to osexec.Command. Can be the path to a binary or the
70 // name of a command that osexec.Lookpath can find.
71 Name string
72 // Arguments of the command, not including Name.
73 Args []string
74 // The environment of the process. If nil, the current process's environ ment is used.
75 Env []string
76 // If Env is non-nil, adds the current process's PATH to Env.
77 InheritPath bool
78 // The working directory of the command. If nil, runs in the current pro cess's current
79 // directory.
80 Dir string
81 // See docs for osexec.Cmd.Stdin.
82 Stdin io.Reader
83 // If true, duplicates stdout of the command to WriteInfoLog.
84 LogStdout bool
85 // Sends the stdout of the command to this Writer, e.g. os.File or bytes .Buffer.
86 Stdout io.Writer
87 // If true, duplicates stderr of the command to WriteErrorLog.
88 LogStderr bool
89 // Sends the stderr of the command to this Writer, e.g. os.File or bytes .Buffer.
90 Stderr io.Writer
91 // Sends the combined stdout and stderr of the command to this Writer, i n addition to
92 // Stdout and Stderr. Only one goroutine will write at a time. Note: the Go runtime seems to
93 // combine stdout and stderr into one stream as long as LogStdout and Lo gStderr are false
94 // and Stdout and Stderr are nil. Otherwise, the stdout and stderr of th e command could be
95 // arbitrarily reordered when written to CombinedOutput.
96 CombinedOutput io.Writer
97 // Time limit to wait for the command to finish. (Starts when Wait is ca lled.) No limit if
98 // not specified.
99 Timeout time.Duration
100 }
101
102 // Divides commandLine at spaces; treats the first token as the program name and the other tokens
103 // as arguments. Note: don't expect this function to do anything smart with quot es or escaped
104 // spaces.
105 func ParseCommand(commandLine string) Command {
106 programAndArgs := strings.Split(commandLine, " ")
107 return Command{Name: programAndArgs[0], Args: programAndArgs[1:]}
108 }
109
110 // Given io.Writers or nils, return a single writer that writes to all, or nil i f no non-nil
111 // writers. Does not handle non-nil interface containing a nil value.
112 // http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index. html#nil_in_nil_in_vals
113 func squashWriters(writers ...io.Writer) io.Writer {
114 nonNil := []io.Writer{}
115 for _, writer := range writers {
116 if writer != nil {
117 nonNil = append(nonNil, writer)
118 }
119 }
120 switch len(nonNil) {
121 case 0:
122 return nil
123 case 1:
124 return nonNil[0]
125 default:
126 return io.MultiWriter(nonNil...)
127 }
128 }
129
130 func createCmd(command *Command) *osexec.Cmd {
131 cmd := osexec.Command(command.Name, command.Args...)
132 if len(command.Env) != 0 {
133 cmd.Env = command.Env
134 if command.InheritPath {
135 cmd.Env = append(cmd.Env, "PATH="+os.Getenv("PATH"))
136 }
137 }
138 cmd.Dir = command.Dir
139 cmd.Stdin = command.Stdin
140 var stdoutLog io.Writer
141 if command.LogStdout {
142 stdoutLog = WriteInfoLog
143 }
144 cmd.Stdout = squashWriters(stdoutLog, command.Stdout, command.CombinedOu tput)
145 var stderrLog io.Writer
146 if command.LogStderr {
147 stderrLog = WriteErrorLog
148 }
149 cmd.Stderr = squashWriters(stderrLog, command.Stderr, command.CombinedOu tput)
150 return cmd
151 }
152
153 func start(cmd *osexec.Cmd) error {
154 if len(cmd.Env) == 0 {
155 glog.Infof("Executing %s", strings.Join(cmd.Args, " "))
156 } else {
157 glog.Infof("Executing %s with env %s",
158 strings.Join(cmd.Args, " "), strings.Join(cmd.Env, " "))
159 }
160 err := cmd.Start()
161 if err != nil {
162 glog.Errorf("Unable to start command %s: %s", strings.Join(cmd.A rgs, " "), err)
163 }
164 return err
165 }
166
167 func waitSimple(cmd *osexec.Cmd) error {
168 err := cmd.Wait()
169 if err != nil {
170 glog.Errorf("Command exited with %s: %s", err, strings.Join(cmd. Args, " "))
171 }
172 return err
173 }
174
175 func wait(command *Command, cmd *osexec.Cmd) error {
176 if command.Timeout == 0 {
177 return waitSimple(cmd)
178 }
179 done := make(chan error)
180 go func() {
181 done <- cmd.Wait()
182 }()
183 select {
184 case <-time.After(command.Timeout):
185 if err := cmd.Process.Kill(); err != nil {
186 return fmt.Errorf("Failed to kill timed out process: %s" , err)
187 }
188 <-done // allow goroutine to exit
189 glog.Errorf("Command killed since it took longer than %f secs", command.Timeout.Seconds())
190 return fmt.Errorf("Command killed since it took longer than %f s ecs", command.Timeout.Seconds())
191 case err := <-done:
192 if err != nil {
193 glog.Errorf("Command exited with %s: %s", err, strings.J oin(cmd.Args, " "))
194 }
195 return err
196 }
197 }
198
199 // Default value of Run.
200 func DefaultRun(command *Command) error {
201 cmd := createCmd(command)
202 if err := start(cmd); err != nil {
203 return err
204 }
205 return wait(command, cmd)
206 }
207
208 // Run runs command and waits for it to finish. If any failure, returns non-nil. If a timeout was
209 // specified, returns an error once the command has exceeded that timeout.
210 var Run func(command *Command) error = DefaultRun
211
212 // SetRunForTesting replaces the Run function with a test version so that comman ds don't actually
213 // run.
214 func SetRunForTesting(testRun func(command *Command) error) {
215 Run = testRun
216 }
217
218 // Run method is convenience for Run(command).
219 func (command *Command) Run() error {
220 return Run(command)
221 }
222
223 // RunSimple executes the given command line string; the command being run is ex pected to not care
224 // what its current working directory is. Returns the combined stdout and stderr . May also return
225 // an error if the command exited with a non-zero status or there is any other e rror.
226 func RunSimple(commandLine string) (string, error) {
227 command := ParseCommand(commandLine)
228 output := bytes.Buffer{}
229 command.CombinedOutput = &output
230 err := Run(&command)
231 result := string(output.Bytes())
232 glog.Infof("StdOut + StdErr: %s\n", result)
233 return result, err
234 }
OLDNEW
« no previous file with comments | « ct/go/util/util.go ('k') | go/exec/exec_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698