gitee.com/bomy/docker.git@v1.13.1/pkg/integration/cmd/command.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os/exec" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/docker/docker/pkg/system" 15 "github.com/go-check/check" 16 ) 17 18 type testingT interface { 19 Fatalf(string, ...interface{}) 20 } 21 22 const ( 23 // None is a token to inform Result.Assert that the output should be empty 24 None string = "<NOTHING>" 25 ) 26 27 type lockedBuffer struct { 28 m sync.RWMutex 29 buf bytes.Buffer 30 } 31 32 func (buf *lockedBuffer) Write(b []byte) (int, error) { 33 buf.m.Lock() 34 defer buf.m.Unlock() 35 return buf.buf.Write(b) 36 } 37 38 func (buf *lockedBuffer) String() string { 39 buf.m.RLock() 40 defer buf.m.RUnlock() 41 return buf.buf.String() 42 } 43 44 // Result stores the result of running a command 45 type Result struct { 46 Cmd *exec.Cmd 47 ExitCode int 48 Error error 49 // Timeout is true if the command was killed because it ran for too long 50 Timeout bool 51 outBuffer *lockedBuffer 52 errBuffer *lockedBuffer 53 } 54 55 // Assert compares the Result against the Expected struct, and fails the test if 56 // any of the expcetations are not met. 57 func (r *Result) Assert(t testingT, exp Expected) { 58 err := r.Compare(exp) 59 if err == nil { 60 return 61 } 62 63 _, file, line, _ := runtime.Caller(1) 64 t.Fatalf("at %s:%d\n%s", filepath.Base(file), line, err.Error()) 65 } 66 67 // Compare returns an formatted error with the command, stdout, stderr, exit 68 // code, and any failed expectations 69 func (r *Result) Compare(exp Expected) error { 70 errors := []string{} 71 add := func(format string, args ...interface{}) { 72 errors = append(errors, fmt.Sprintf(format, args...)) 73 } 74 75 if exp.ExitCode != r.ExitCode { 76 add("ExitCode was %d expected %d", r.ExitCode, exp.ExitCode) 77 } 78 if exp.Timeout != r.Timeout { 79 if exp.Timeout { 80 add("Expected command to timeout") 81 } else { 82 add("Expected command to finish, but it hit the timeout") 83 } 84 } 85 if !matchOutput(exp.Out, r.Stdout()) { 86 add("Expected stdout to contain %q", exp.Out) 87 } 88 if !matchOutput(exp.Err, r.Stderr()) { 89 add("Expected stderr to contain %q", exp.Err) 90 } 91 switch { 92 // If a non-zero exit code is expected there is going to be an error. 93 // Don't require an error message as well as an exit code because the 94 // error message is going to be "exit status <code> which is not useful 95 case exp.Error == "" && exp.ExitCode != 0: 96 case exp.Error == "" && r.Error != nil: 97 add("Expected no error") 98 case exp.Error != "" && r.Error == nil: 99 add("Expected error to contain %q, but there was no error", exp.Error) 100 case exp.Error != "" && !strings.Contains(r.Error.Error(), exp.Error): 101 add("Expected error to contain %q", exp.Error) 102 } 103 104 if len(errors) == 0 { 105 return nil 106 } 107 return fmt.Errorf("%s\nFailures:\n%s\n", r, strings.Join(errors, "\n")) 108 } 109 110 func matchOutput(expected string, actual string) bool { 111 switch expected { 112 case None: 113 return actual == "" 114 default: 115 return strings.Contains(actual, expected) 116 } 117 } 118 119 func (r *Result) String() string { 120 var timeout string 121 if r.Timeout { 122 timeout = " (timeout)" 123 } 124 125 return fmt.Sprintf(` 126 Command: %s 127 ExitCode: %d%s, Error: %s 128 Stdout: %v 129 Stderr: %v 130 `, 131 strings.Join(r.Cmd.Args, " "), 132 r.ExitCode, 133 timeout, 134 r.Error, 135 r.Stdout(), 136 r.Stderr()) 137 } 138 139 // Expected is the expected output from a Command. This struct is compared to a 140 // Result struct by Result.Assert(). 141 type Expected struct { 142 ExitCode int 143 Timeout bool 144 Error string 145 Out string 146 Err string 147 } 148 149 // Success is the default expected result 150 var Success = Expected{} 151 152 // Stdout returns the stdout of the process as a string 153 func (r *Result) Stdout() string { 154 return r.outBuffer.String() 155 } 156 157 // Stderr returns the stderr of the process as a string 158 func (r *Result) Stderr() string { 159 return r.errBuffer.String() 160 } 161 162 // Combined returns the stdout and stderr combined into a single string 163 func (r *Result) Combined() string { 164 return r.outBuffer.String() + r.errBuffer.String() 165 } 166 167 // SetExitError sets Error and ExitCode based on Error 168 func (r *Result) SetExitError(err error) { 169 if err == nil { 170 return 171 } 172 r.Error = err 173 r.ExitCode = system.ProcessExitCode(err) 174 } 175 176 type matches struct{} 177 178 // Info returns the CheckerInfo 179 func (m *matches) Info() *check.CheckerInfo { 180 return &check.CheckerInfo{ 181 Name: "CommandMatches", 182 Params: []string{"result", "expected"}, 183 } 184 } 185 186 // Check compares a result against the expected 187 func (m *matches) Check(params []interface{}, names []string) (bool, string) { 188 result, ok := params[0].(*Result) 189 if !ok { 190 return false, fmt.Sprintf("result must be a *Result, not %T", params[0]) 191 } 192 expected, ok := params[1].(Expected) 193 if !ok { 194 return false, fmt.Sprintf("expected must be an Expected, not %T", params[1]) 195 } 196 197 err := result.Compare(expected) 198 if err == nil { 199 return true, "" 200 } 201 return false, err.Error() 202 } 203 204 // Matches is a gocheck.Checker for comparing a Result against an Expected 205 var Matches = &matches{} 206 207 // Cmd contains the arguments and options for a process to run as part of a test 208 // suite. 209 type Cmd struct { 210 Command []string 211 Timeout time.Duration 212 Stdin io.Reader 213 Stdout io.Writer 214 Dir string 215 Env []string 216 } 217 218 // RunCmd runs a command and returns a Result 219 func RunCmd(cmd Cmd) *Result { 220 result := StartCmd(cmd) 221 if result.Error != nil { 222 return result 223 } 224 return WaitOnCmd(cmd.Timeout, result) 225 } 226 227 // RunCommand parses a command line and runs it, returning a result 228 func RunCommand(command string, args ...string) *Result { 229 return RunCmd(Cmd{Command: append([]string{command}, args...)}) 230 } 231 232 // StartCmd starts a command, but doesn't wait for it to finish 233 func StartCmd(cmd Cmd) *Result { 234 result := buildCmd(cmd) 235 if result.Error != nil { 236 return result 237 } 238 result.SetExitError(result.Cmd.Start()) 239 return result 240 } 241 242 func buildCmd(cmd Cmd) *Result { 243 var execCmd *exec.Cmd 244 switch len(cmd.Command) { 245 case 1: 246 execCmd = exec.Command(cmd.Command[0]) 247 default: 248 execCmd = exec.Command(cmd.Command[0], cmd.Command[1:]...) 249 } 250 outBuffer := new(lockedBuffer) 251 errBuffer := new(lockedBuffer) 252 253 execCmd.Stdin = cmd.Stdin 254 execCmd.Dir = cmd.Dir 255 execCmd.Env = cmd.Env 256 if cmd.Stdout != nil { 257 execCmd.Stdout = io.MultiWriter(outBuffer, cmd.Stdout) 258 } else { 259 execCmd.Stdout = outBuffer 260 } 261 execCmd.Stderr = errBuffer 262 return &Result{ 263 Cmd: execCmd, 264 outBuffer: outBuffer, 265 errBuffer: errBuffer, 266 } 267 } 268 269 // WaitOnCmd waits for a command to complete. If timeout is non-nil then 270 // only wait until the timeout. 271 func WaitOnCmd(timeout time.Duration, result *Result) *Result { 272 if timeout == time.Duration(0) { 273 result.SetExitError(result.Cmd.Wait()) 274 return result 275 } 276 277 done := make(chan error, 1) 278 // Wait for command to exit in a goroutine 279 go func() { 280 done <- result.Cmd.Wait() 281 }() 282 283 select { 284 case <-time.After(timeout): 285 killErr := result.Cmd.Process.Kill() 286 if killErr != nil { 287 fmt.Printf("failed to kill (pid=%d): %v\n", result.Cmd.Process.Pid, killErr) 288 } 289 result.Timeout = true 290 case err := <-done: 291 result.SetExitError(err) 292 } 293 return result 294 }