command.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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) *Result {
  49. err := r.Compare(exp)
  50. if err == nil {
  51. return r
  52. }
  53. _, file, line, ok := runtime.Caller(1)
  54. if ok {
  55. t.Fatalf("at %s:%d - %s", filepath.Base(file), line, err.Error())
  56. } else {
  57. t.Fatalf("(no file/line info) - %s", err.Error())
  58. }
  59. return nil
  60. }
  61. // Compare returns a formatted error with the command, stdout, stderr, exit
  62. // code, and any failed expectations
  63. func (r *Result) Compare(exp Expected) error {
  64. errors := []string{}
  65. add := func(format string, args ...interface{}) {
  66. errors = append(errors, fmt.Sprintf(format, args...))
  67. }
  68. if exp.ExitCode != r.ExitCode {
  69. add("ExitCode was %d expected %d", r.ExitCode, exp.ExitCode)
  70. }
  71. if exp.Timeout != r.Timeout {
  72. if exp.Timeout {
  73. add("Expected command to timeout")
  74. } else {
  75. add("Expected command to finish, but it hit the timeout")
  76. }
  77. }
  78. if !matchOutput(exp.Out, r.Stdout()) {
  79. add("Expected stdout to contain %q", exp.Out)
  80. }
  81. if !matchOutput(exp.Err, r.Stderr()) {
  82. add("Expected stderr to contain %q", exp.Err)
  83. }
  84. switch {
  85. // If a non-zero exit code is expected there is going to be an error.
  86. // Don't require an error message as well as an exit code because the
  87. // error message is going to be "exit status <code> which is not useful
  88. case exp.Error == "" && exp.ExitCode != 0:
  89. case exp.Error == "" && r.Error != nil:
  90. add("Expected no error")
  91. case exp.Error != "" && r.Error == nil:
  92. add("Expected error to contain %q, but there was no error", exp.Error)
  93. case exp.Error != "" && !strings.Contains(r.Error.Error(), exp.Error):
  94. add("Expected error to contain %q", exp.Error)
  95. }
  96. if len(errors) == 0 {
  97. return nil
  98. }
  99. return fmt.Errorf("%s\nFailures:\n%s\n", r, strings.Join(errors, "\n"))
  100. }
  101. func matchOutput(expected string, actual string) bool {
  102. switch expected {
  103. case None:
  104. return actual == ""
  105. default:
  106. return strings.Contains(actual, expected)
  107. }
  108. }
  109. func (r *Result) String() string {
  110. var timeout string
  111. if r.Timeout {
  112. timeout = " (timeout)"
  113. }
  114. return fmt.Sprintf(`
  115. Command: %s
  116. ExitCode: %d%s
  117. Error: %v
  118. Stdout: %v
  119. Stderr: %v
  120. `,
  121. strings.Join(r.Cmd.Args, " "),
  122. r.ExitCode,
  123. timeout,
  124. r.Error,
  125. r.Stdout(),
  126. r.Stderr())
  127. }
  128. // Expected is the expected output from a Command. This struct is compared to a
  129. // Result struct by Result.Assert().
  130. type Expected struct {
  131. ExitCode int
  132. Timeout bool
  133. Error string
  134. Out string
  135. Err string
  136. }
  137. // Success is the default expected result
  138. var Success = Expected{}
  139. // Stdout returns the stdout of the process as a string
  140. func (r *Result) Stdout() string {
  141. return r.outBuffer.String()
  142. }
  143. // Stderr returns the stderr of the process as a string
  144. func (r *Result) Stderr() string {
  145. return r.errBuffer.String()
  146. }
  147. // Combined returns the stdout and stderr combined into a single string
  148. func (r *Result) Combined() string {
  149. return r.outBuffer.String() + r.errBuffer.String()
  150. }
  151. // SetExitError sets Error and ExitCode based on Error
  152. func (r *Result) SetExitError(err error) {
  153. if err == nil {
  154. return
  155. }
  156. r.Error = err
  157. r.ExitCode = system.ProcessExitCode(err)
  158. }
  159. type matches struct{}
  160. // Info returns the CheckerInfo
  161. func (m *matches) Info() *check.CheckerInfo {
  162. return &check.CheckerInfo{
  163. Name: "CommandMatches",
  164. Params: []string{"result", "expected"},
  165. }
  166. }
  167. // Check compares a result against the expected
  168. func (m *matches) Check(params []interface{}, names []string) (bool, string) {
  169. result, ok := params[0].(*Result)
  170. if !ok {
  171. return false, fmt.Sprintf("result must be a *Result, not %T", params[0])
  172. }
  173. expected, ok := params[1].(Expected)
  174. if !ok {
  175. return false, fmt.Sprintf("expected must be an Expected, not %T", params[1])
  176. }
  177. err := result.Compare(expected)
  178. if err == nil {
  179. return true, ""
  180. }
  181. return false, err.Error()
  182. }
  183. // Matches is a gocheck.Checker for comparing a Result against an Expected
  184. var Matches = &matches{}
  185. // Cmd contains the arguments and options for a process to run as part of a test
  186. // suite.
  187. type Cmd struct {
  188. Command []string
  189. Timeout time.Duration
  190. Stdin io.Reader
  191. Stdout io.Writer
  192. Dir string
  193. Env []string
  194. }
  195. // Command create a simple Cmd with the specified command and arguments
  196. func Command(command string, args ...string) Cmd {
  197. return Cmd{Command: append([]string{command}, args...)}
  198. }
  199. // RunCmd runs a command and returns a Result
  200. func RunCmd(cmd Cmd, cmdOperators ...func(*Cmd)) *Result {
  201. for _, op := range cmdOperators {
  202. op(&cmd)
  203. }
  204. result := StartCmd(cmd)
  205. if result.Error != nil {
  206. return result
  207. }
  208. return WaitOnCmd(cmd.Timeout, result)
  209. }
  210. // RunCommand parses a command line and runs it, returning a result
  211. func RunCommand(command string, args ...string) *Result {
  212. return RunCmd(Command(command, args...))
  213. }
  214. // StartCmd starts a command, but doesn't wait for it to finish
  215. func StartCmd(cmd Cmd) *Result {
  216. result := buildCmd(cmd)
  217. if result.Error != nil {
  218. return result
  219. }
  220. result.SetExitError(result.Cmd.Start())
  221. return result
  222. }
  223. func buildCmd(cmd Cmd) *Result {
  224. var execCmd *exec.Cmd
  225. switch len(cmd.Command) {
  226. case 1:
  227. execCmd = exec.Command(cmd.Command[0])
  228. default:
  229. execCmd = exec.Command(cmd.Command[0], cmd.Command[1:]...)
  230. }
  231. outBuffer := new(lockedBuffer)
  232. errBuffer := new(lockedBuffer)
  233. execCmd.Stdin = cmd.Stdin
  234. execCmd.Dir = cmd.Dir
  235. execCmd.Env = cmd.Env
  236. if cmd.Stdout != nil {
  237. execCmd.Stdout = io.MultiWriter(outBuffer, cmd.Stdout)
  238. } else {
  239. execCmd.Stdout = outBuffer
  240. }
  241. execCmd.Stderr = errBuffer
  242. return &Result{
  243. Cmd: execCmd,
  244. outBuffer: outBuffer,
  245. errBuffer: errBuffer,
  246. }
  247. }
  248. // WaitOnCmd waits for a command to complete. If timeout is non-nil then
  249. // only wait until the timeout.
  250. func WaitOnCmd(timeout time.Duration, result *Result) *Result {
  251. if timeout == time.Duration(0) {
  252. result.SetExitError(result.Cmd.Wait())
  253. return result
  254. }
  255. done := make(chan error, 1)
  256. // Wait for command to exit in a goroutine
  257. go func() {
  258. done <- result.Cmd.Wait()
  259. }()
  260. select {
  261. case <-time.After(timeout):
  262. killErr := result.Cmd.Process.Kill()
  263. if killErr != nil {
  264. fmt.Printf("failed to kill (pid=%d): %v\n", result.Cmd.Process.Pid, killErr)
  265. }
  266. result.Timeout = true
  267. case err := <-done:
  268. result.SetExitError(err)
  269. }
  270. return result
  271. }