github.com/moby/docker@v26.1.3+incompatible/integration-cli/cli/cli.go (about)

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