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

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

Issue 1299493004: 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 "reflect"
48 "strings"
49 "time"
50
51 "github.com/skia-dev/glog"
52 )
53
54 // WriteLog implements the io.Writer interface and writes to the given log funct ion.
55 type WriteLog struct {
56 LogFunc func(format string, args ...interface{})
57 }
58
59 func (wl WriteLog) Write(p []byte) (n int, err error) {
60 wl.LogFunc("%s", string(p))
61 return len(p), nil
62 }
63
64 var (
65 WriteInfoLog = WriteLog{LogFunc: glog.Infof}
66 WriteErrorLog = WriteLog{LogFunc: glog.Errorf}
67 )
68
69 type Command struct {
70 // Name of the command, as passed to osexec.Command. Can be the path to a binary or the
71 // name of a command that osexec.Lookpath can find.
72 Name string
73 // Arguments of the command, not including Name.
74 Args []string
75 // The environment of the process. If nil, the current process's environ ment is used.
76 Env []string
77 // If Env is non-nil, adds the current process's PATH to Env.
78 InheritPath bool
79 // The working directory of the command. If nil, runs in the current pro cess's current
80 // directory.
81 Dir string
82 // See docs for osexec.Cmd.Stdin.
83 Stdin io.Reader
84 // If true, duplicates stdout of the command to WriteInfoLog.
85 LogStdout bool
86 // Sends the stdout of the command to this Writer, e.g. os.File or bytes .Buffer.
87 Stdout io.Writer
88 // If true, duplicates stderr of the command to WriteErrorLog.
89 LogStderr bool
90 // Sends the stderr of the command to this Writer, e.g. os.File or bytes .Buffer.
91 Stderr io.Writer
92 // Sends the combined stdout and stderr of the command to this Writer, i n addition to
93 // Stdout and Stderr. Only one goroutine will write at a time. Note: the Go runtime seems to
94 // combine stdout and stderr into one stream as long as LogStdout and Lo gStderr are false
95 // and Stdout and Stderr are nil. Otherwise, the stdout and stderr of th e command could be
96 // arbitrarily reordered when written to CombinedOutput.
97 CombinedOutput io.Writer
98 // Time limit to wait for the command to finish. (Starts when Wait is ca lled.) No limit if
99 // not specified.
100 Timeout time.Duration
101 }
102
103 // Divides commandLine at spaces; treats the first token as the program name and the other tokens
104 // as arguments. Note: don't expect this function to do anything smart with quot es or escaped
105 // spaces.
106 func ParseCommand(commandLine string) Command {
107 programAndArgs := strings.Split(commandLine, " ")
108 return Command{Name: programAndArgs[0], Args: programAndArgs[1:]}
109 }
110
111 // Given io.Writers or nils, return a single writer that writes to all, or nil i f no non-nil
112 // writers. Also checks for non-nil io.Writer containing a nil value.
113 // http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index. html#nil_in_nil_in_vals
114 func squashWriters(writers ...io.Writer) io.Writer {
115 nonNil := []io.Writer{}
116 for _, writer := range writers {
117 if writer != nil && !reflect.ValueOf(writer).IsNil() {
118 nonNil = append(nonNil, writer)
119 }
120 }
121 switch len(nonNil) {
122 case 0:
123 return nil
124 case 1:
125 return nonNil[0]
126 default:
127 return io.MultiWriter(nonNil...)
128 }
129 }
130
131 func createCmd(command *Command) *osexec.Cmd {
132 cmd := osexec.Command(command.Name, command.Args...)
133 if len(command.Env) != 0 {
134 cmd.Env = command.Env
135 if command.InheritPath {
136 cmd.Env = append(cmd.Env, "PATH="+os.Getenv("PATH"))
137 }
138 }
139 cmd.Dir = command.Dir
140 cmd.Stdin = command.Stdin
141 var stdoutLog io.Writer
142 if command.LogStdout {
143 stdoutLog = WriteInfoLog
144 }
145 cmd.Stdout = squashWriters(stdoutLog, command.Stdout, command.CombinedOu tput)
146 var stderrLog io.Writer
147 if command.LogStderr {
148 stderrLog = WriteErrorLog
149 }
150 cmd.Stderr = squashWriters(stderrLog, command.Stderr, command.CombinedOu tput)
151 return cmd
152 }
153
154 func start(cmd *osexec.Cmd) error {
155 if len(cmd.Env) == 0 {
156 glog.Infof("Executing %s", strings.Join(cmd.Args, " "))
157 } else {
158 glog.Infof("Executing %s with env %s",
159 strings.Join(cmd.Args, " "), strings.Join(cmd.Env, " "))
160 }
161 err := cmd.Start()
162 if err != nil {
163 glog.Errorf("Unable to start command %s: %s", strings.Join(cmd.A rgs, " "), err)
164 }
165 return err
166 }
167
168 func waitSimple(cmd *osexec.Cmd) error {
169 err := cmd.Wait()
170 if err != nil {
171 glog.Errorf("Command exited with %s: %s", err, strings.Join(cmd. Args, " "))
172 }
173 return err
174 }
175
176 func wait(command *Command, cmd *osexec.Cmd) error {
177 if command.Timeout == 0 {
178 return waitSimple(cmd)
179 }
180 done := make(chan error)
181 go func() {
182 done <- cmd.Wait()
183 }()
184 select {
185 case <-time.After(command.Timeout):
186 if err := cmd.Process.Kill(); err != nil {
187 return fmt.Errorf("Failed to kill timed out process: %s" , err)
188 }
189 <-done // allow goroutine to exit
190 glog.Errorf("Command killed since it took longer than %f secs", command.Timeout.Seconds())
191 return fmt.Errorf("Command killed since it took longer than %f s ecs", command.Timeout.Seconds())
192 case err := <-done:
193 if err != nil {
194 glog.Errorf("Command exited with %s: %s", err, strings.J oin(cmd.Args, " "))
195 }
196 return err
197 }
198 }
199
200 // Default value of Run.
201 func DefaultRun(command *Command) error {
202 cmd := createCmd(command)
203 if err := start(cmd); err != nil {
204 return err
205 }
206 return wait(command, cmd)
207 }
208
209 // Run runs command and waits for it to finish. If any failure, returns non-nil. If a timeout was
210 // specified, returns an error once the command has exceeded that timeout.
211 var Run func(command *Command) error = DefaultRun
212
213 // SetRunForTesting replaces the Run function with a test version so that comman ds don't actually
214 // run.
215 func SetRunForTesting(testRun func(command *Command) error) {
216 Run = testRun
217 }
218
219 // Run method is convenience for Run(command).
220 func (command *Command) Run() error {
221 return Run(command)
222 }
223
224 // RunSimple executes the given command line string; the command being run is ex pected to not care
225 // what its current working directory is. Returns the combined stdout and stderr . May also return
226 // an error if the command exited with a non-zero status or there is any other e rror.
227 func RunSimple(commandLine string) (string, error) {
228 command := ParseCommand(commandLine)
229 output := bytes.Buffer{}
230 command.CombinedOutput = &output
231 err := Run(&command)
232 result := string(output.Bytes())
233 glog.Infof("StdOut + StdErr: %s\n", result)
234 return result, err
235 }
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