command.go 6.7 KB

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