github.com/moby/docker@v26.1.3+incompatible/integration/internal/container/exec.go (about)

     1  package container
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"testing"
     7  
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/client"
    10  )
    11  
    12  // ExecResult represents a result returned from Exec()
    13  type ExecResult struct {
    14  	ExitCode  int
    15  	outBuffer *bytes.Buffer
    16  	errBuffer *bytes.Buffer
    17  }
    18  
    19  // Stdout returns stdout output of a command run by Exec()
    20  func (res ExecResult) Stdout() string {
    21  	return res.outBuffer.String()
    22  }
    23  
    24  // Stderr returns stderr output of a command run by Exec()
    25  func (res ExecResult) Stderr() string {
    26  	return res.errBuffer.String()
    27  }
    28  
    29  // Combined returns combined stdout and stderr output of a command run by Exec()
    30  func (res ExecResult) Combined() string {
    31  	return res.outBuffer.String() + res.errBuffer.String()
    32  }
    33  
    34  // AssertSuccess fails the test and stops execution if the command exited with a
    35  // nonzero status code.
    36  func (res ExecResult) AssertSuccess(t testing.TB) {
    37  	t.Helper()
    38  	if res.ExitCode != 0 {
    39  		t.Logf("expected exit code 0, got %d", res.ExitCode)
    40  		t.Logf("stdout: %s", res.Stdout())
    41  		t.Logf("stderr: %s", res.Stderr())
    42  		t.FailNow()
    43  	}
    44  }
    45  
    46  // Exec executes a command inside a container, returning the result
    47  // containing stdout, stderr, and exit code. Note:
    48  //   - this is a synchronous operation;
    49  //   - cmd stdin is closed.
    50  func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []string, ops ...func(*types.ExecConfig)) (ExecResult, error) {
    51  	// prepare exec
    52  	execConfig := types.ExecConfig{
    53  		AttachStdout: true,
    54  		AttachStderr: true,
    55  		Cmd:          cmd,
    56  	}
    57  
    58  	for _, op := range ops {
    59  		op(&execConfig)
    60  	}
    61  
    62  	cresp, err := apiClient.ContainerExecCreate(ctx, id, execConfig)
    63  	if err != nil {
    64  		return ExecResult{}, err
    65  	}
    66  	execID := cresp.ID
    67  
    68  	// run it, with stdout/stderr attached
    69  	aresp, err := apiClient.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
    70  	if err != nil {
    71  		return ExecResult{}, err
    72  	}
    73  
    74  	// read the output
    75  	s, err := demultiplexStreams(ctx, aresp)
    76  	if err != nil {
    77  		return ExecResult{}, err
    78  	}
    79  
    80  	// get the exit code
    81  	iresp, err := apiClient.ContainerExecInspect(ctx, execID)
    82  	if err != nil {
    83  		return ExecResult{}, err
    84  	}
    85  
    86  	return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &s.stdout, errBuffer: &s.stderr}, nil
    87  }
    88  
    89  // ExecT calls Exec() and aborts the test if an error occurs.
    90  func ExecT(ctx context.Context, t testing.TB, apiClient client.APIClient, id string, cmd []string, ops ...func(*types.ExecConfig)) ExecResult {
    91  	t.Helper()
    92  	res, err := Exec(ctx, apiClient, id, cmd, ops...)
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  	return res
    97  }