cli.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. package cli
  2. import (
  3. "fmt"
  4. "io"
  5. "strings"
  6. "time"
  7. "github.com/docker/docker/integration-cli/daemon"
  8. "github.com/docker/docker/integration-cli/environment"
  9. "github.com/gotestyourself/gotestyourself/icmd"
  10. "github.com/pkg/errors"
  11. )
  12. var testEnv *environment.Execution
  13. // SetTestEnvironment sets a static test environment
  14. // TODO: decouple this package from environment
  15. func SetTestEnvironment(env *environment.Execution) {
  16. testEnv = env
  17. }
  18. // CmdOperator defines functions that can modify a command
  19. type CmdOperator func(*icmd.Cmd) func()
  20. type testingT interface {
  21. Fatal(args ...interface{})
  22. Fatalf(string, ...interface{})
  23. }
  24. // DockerCmd executes the specified docker command and expect a success
  25. func DockerCmd(t testingT, args ...string) *icmd.Result {
  26. return Docker(Args(args...)).Assert(t, icmd.Success)
  27. }
  28. // BuildCmd executes the specified docker build command and expect a success
  29. func BuildCmd(t testingT, name string, cmdOperators ...CmdOperator) *icmd.Result {
  30. return Docker(Build(name), cmdOperators...).Assert(t, icmd.Success)
  31. }
  32. // InspectCmd executes the specified docker inspect command and expect a success
  33. func InspectCmd(t testingT, name string, cmdOperators ...CmdOperator) *icmd.Result {
  34. return Docker(Inspect(name), cmdOperators...).Assert(t, icmd.Success)
  35. }
  36. // WaitRun will wait for the specified container to be running, maximum 5 seconds.
  37. func WaitRun(t testingT, name string, cmdOperators ...CmdOperator) {
  38. WaitForInspectResult(t, name, "{{.State.Running}}", "true", 5*time.Second, cmdOperators...)
  39. }
  40. // WaitExited will wait for the specified container to state exit, subject
  41. // to a maximum time limit in seconds supplied by the caller
  42. func WaitExited(t testingT, name string, timeout time.Duration, cmdOperators ...CmdOperator) {
  43. WaitForInspectResult(t, name, "{{.State.Status}}", "exited", timeout, cmdOperators...)
  44. }
  45. // WaitRestart will wait for the specified container to restart once
  46. func WaitRestart(t testingT, name string, timeout time.Duration, cmdOperators ...CmdOperator) {
  47. WaitForInspectResult(t, name, "{{.RestartCount}}", "1", timeout, cmdOperators...)
  48. }
  49. // WaitForInspectResult waits for the specified expression to be equals to the specified expected string in the given time.
  50. func WaitForInspectResult(t testingT, name, expr, expected string, timeout time.Duration, cmdOperators ...CmdOperator) {
  51. after := time.After(timeout)
  52. args := []string{"inspect", "-f", expr, name}
  53. for {
  54. result := Docker(Args(args...), cmdOperators...)
  55. if result.Error != nil {
  56. if !strings.Contains(strings.ToLower(result.Stderr()), "no such") {
  57. t.Fatalf("error executing docker inspect: %v\n%s",
  58. result.Stderr(), result.Stdout())
  59. }
  60. select {
  61. case <-after:
  62. t.Fatal(result.Error)
  63. default:
  64. time.Sleep(10 * time.Millisecond)
  65. continue
  66. }
  67. }
  68. out := strings.TrimSpace(result.Stdout())
  69. if out == expected {
  70. break
  71. }
  72. select {
  73. case <-after:
  74. t.Fatalf("condition \"%q == %q\" not true in time (%v)", out, expected, timeout)
  75. default:
  76. }
  77. time.Sleep(100 * time.Millisecond)
  78. }
  79. }
  80. // Docker executes the specified docker command
  81. func Docker(cmd icmd.Cmd, cmdOperators ...CmdOperator) *icmd.Result {
  82. for _, op := range cmdOperators {
  83. deferFn := op(&cmd)
  84. if deferFn != nil {
  85. defer deferFn()
  86. }
  87. }
  88. appendDocker(&cmd)
  89. if err := validateArgs(cmd.Command...); err != nil {
  90. return &icmd.Result{
  91. Error: err,
  92. }
  93. }
  94. return icmd.RunCmd(cmd)
  95. }
  96. // validateArgs is a checker to ensure tests are not running commands which are
  97. // not supported on platforms. Specifically on Windows this is 'busybox top'.
  98. func validateArgs(args ...string) error {
  99. if testEnv.OSType != "windows" {
  100. return nil
  101. }
  102. foundBusybox := -1
  103. for key, value := range args {
  104. if strings.ToLower(value) == "busybox" {
  105. foundBusybox = key
  106. }
  107. if (foundBusybox != -1) && (key == foundBusybox+1) && (strings.ToLower(value) == "top") {
  108. return errors.New("cannot use 'busybox top' in tests on Windows. Use runSleepingContainer()")
  109. }
  110. }
  111. return nil
  112. }
  113. // Build executes the specified docker build command
  114. func Build(name string) icmd.Cmd {
  115. return icmd.Command("build", "-t", name)
  116. }
  117. // Inspect executes the specified docker inspect command
  118. func Inspect(name string) icmd.Cmd {
  119. return icmd.Command("inspect", name)
  120. }
  121. // Format sets the specified format with --format flag
  122. func Format(format string) func(*icmd.Cmd) func() {
  123. return func(cmd *icmd.Cmd) func() {
  124. cmd.Command = append(
  125. []string{cmd.Command[0]},
  126. append([]string{"--format", fmt.Sprintf("{{%s}}", format)}, cmd.Command[1:]...)...,
  127. )
  128. return nil
  129. }
  130. }
  131. func appendDocker(cmd *icmd.Cmd) {
  132. cmd.Command = append([]string{testEnv.DockerBinary()}, cmd.Command...)
  133. }
  134. // Args build an icmd.Cmd struct from the specified arguments
  135. func Args(args ...string) icmd.Cmd {
  136. switch len(args) {
  137. case 0:
  138. return icmd.Cmd{}
  139. case 1:
  140. return icmd.Command(args[0])
  141. default:
  142. return icmd.Command(args[0], args[1:]...)
  143. }
  144. }
  145. // Daemon points to the specified daemon
  146. func Daemon(d *daemon.Daemon) func(*icmd.Cmd) func() {
  147. return func(cmd *icmd.Cmd) func() {
  148. cmd.Command = append([]string{"--host", d.Sock()}, cmd.Command...)
  149. return nil
  150. }
  151. }
  152. // WithTimeout sets the timeout for the command to run
  153. func WithTimeout(timeout time.Duration) func(cmd *icmd.Cmd) func() {
  154. return func(cmd *icmd.Cmd) func() {
  155. cmd.Timeout = timeout
  156. return nil
  157. }
  158. }
  159. // WithEnvironmentVariables sets the specified environment variables for the command to run
  160. func WithEnvironmentVariables(envs ...string) func(cmd *icmd.Cmd) func() {
  161. return func(cmd *icmd.Cmd) func() {
  162. cmd.Env = envs
  163. return nil
  164. }
  165. }
  166. // WithFlags sets the specified flags for the command to run
  167. func WithFlags(flags ...string) func(*icmd.Cmd) func() {
  168. return func(cmd *icmd.Cmd) func() {
  169. cmd.Command = append(cmd.Command, flags...)
  170. return nil
  171. }
  172. }
  173. // InDir sets the folder in which the command should be executed
  174. func InDir(path string) func(*icmd.Cmd) func() {
  175. return func(cmd *icmd.Cmd) func() {
  176. cmd.Dir = path
  177. return nil
  178. }
  179. }
  180. // WithStdout sets the standard output writer of the command
  181. func WithStdout(writer io.Writer) func(*icmd.Cmd) func() {
  182. return func(cmd *icmd.Cmd) func() {
  183. cmd.Stdout = writer
  184. return nil
  185. }
  186. }
  187. // WithStdin sets the standard input reader for the command
  188. func WithStdin(stdin io.Reader) func(*icmd.Cmd) func() {
  189. return func(cmd *icmd.Cmd) func() {
  190. cmd.Stdin = stdin
  191. return nil
  192. }
  193. }