cli.go 5.9 KB

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