github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/client/container_wait_test.go (about)

     1  package client // import "github.com/docker/docker/client"
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"net/http"
    11  	"strings"
    12  	"syscall"
    13  	"testing"
    14  	"testing/iotest"
    15  	"time"
    16  
    17  	"github.com/docker/docker/api/types/container"
    18  	"github.com/docker/docker/errdefs"
    19  	"github.com/pkg/errors"
    20  	"gotest.tools/v3/assert"
    21  	is "gotest.tools/v3/assert/cmp"
    22  )
    23  
    24  func TestContainerWaitError(t *testing.T) {
    25  	client := &Client{
    26  		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
    27  	}
    28  	resultC, errC := client.ContainerWait(context.Background(), "nothing", "")
    29  	select {
    30  	case result := <-resultC:
    31  		t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
    32  	case err := <-errC:
    33  		assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
    34  	}
    35  }
    36  
    37  // TestContainerWaitConnectionError verifies that connection errors occurring
    38  // during API-version negotiation are not shadowed by API-version errors.
    39  //
    40  // Regression test for https://github.com/docker/cli/issues/4890
    41  func TestContainerWaitConnectionError(t *testing.T) {
    42  	client, err := NewClientWithOpts(WithAPIVersionNegotiation(), WithHost("tcp://no-such-host.invalid"))
    43  	assert.NilError(t, err)
    44  
    45  	resultC, errC := client.ContainerWait(context.Background(), "nothing", "")
    46  	select {
    47  	case result := <-resultC:
    48  		t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
    49  	case err := <-errC:
    50  		assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
    51  	}
    52  }
    53  
    54  func TestContainerWait(t *testing.T) {
    55  	expectedURL := "/containers/container_id/wait"
    56  	client := &Client{
    57  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
    58  			if !strings.HasPrefix(req.URL.Path, expectedURL) {
    59  				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
    60  			}
    61  			b, err := json.Marshal(container.WaitResponse{
    62  				StatusCode: 15,
    63  			})
    64  			if err != nil {
    65  				return nil, err
    66  			}
    67  			return &http.Response{
    68  				StatusCode: http.StatusOK,
    69  				Body:       io.NopCloser(bytes.NewReader(b)),
    70  			}, nil
    71  		}),
    72  	}
    73  
    74  	resultC, errC := client.ContainerWait(context.Background(), "container_id", "")
    75  	select {
    76  	case err := <-errC:
    77  		t.Fatal(err)
    78  	case result := <-resultC:
    79  		if result.StatusCode != 15 {
    80  			t.Fatalf("expected a status code equal to '15', got %d", result.StatusCode)
    81  		}
    82  	}
    83  }
    84  
    85  func TestContainerWaitProxyInterrupt(t *testing.T) {
    86  	expectedURL := "/v1.30/containers/container_id/wait"
    87  	msg := "copying response body from Docker: unexpected EOF"
    88  	client := &Client{
    89  		version: "1.30",
    90  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
    91  			if !strings.HasPrefix(req.URL.Path, expectedURL) {
    92  				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
    93  			}
    94  			return &http.Response{
    95  				StatusCode: http.StatusOK,
    96  				Body:       io.NopCloser(strings.NewReader(msg)),
    97  			}, nil
    98  		}),
    99  	}
   100  
   101  	resultC, errC := client.ContainerWait(context.Background(), "container_id", "")
   102  	select {
   103  	case err := <-errC:
   104  		if !strings.Contains(err.Error(), msg) {
   105  			t.Fatalf("Expected: %s, Actual: %s", msg, err.Error())
   106  		}
   107  	case result := <-resultC:
   108  		t.Fatalf("Unexpected result: %v", result)
   109  	}
   110  }
   111  
   112  func TestContainerWaitProxyInterruptLong(t *testing.T) {
   113  	expectedURL := "/v1.30/containers/container_id/wait"
   114  	msg := strings.Repeat("x", containerWaitErrorMsgLimit*5)
   115  	client := &Client{
   116  		version: "1.30",
   117  		client: newMockClient(func(req *http.Request) (*http.Response, error) {
   118  			if !strings.HasPrefix(req.URL.Path, expectedURL) {
   119  				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
   120  			}
   121  			return &http.Response{
   122  				StatusCode: http.StatusOK,
   123  				Body:       io.NopCloser(strings.NewReader(msg)),
   124  			}, nil
   125  		}),
   126  	}
   127  
   128  	resultC, errC := client.ContainerWait(context.Background(), "container_id", "")
   129  	select {
   130  	case err := <-errC:
   131  		// LimitReader limiting isn't exact, because of how the Readers do chunking.
   132  		if len(err.Error()) > containerWaitErrorMsgLimit*2 {
   133  			t.Fatalf("Expected error to be limited around %d, actual length: %d", containerWaitErrorMsgLimit, len(err.Error()))
   134  		}
   135  	case result := <-resultC:
   136  		t.Fatalf("Unexpected result: %v", result)
   137  	}
   138  }
   139  
   140  func TestContainerWaitErrorHandling(t *testing.T) {
   141  	for _, test := range []struct {
   142  		name string
   143  		rdr  io.Reader
   144  		exp  error
   145  	}{
   146  		{name: "invalid json", rdr: strings.NewReader(`{]`), exp: errors.New("{]")},
   147  		{name: "context canceled", rdr: iotest.ErrReader(context.Canceled), exp: context.Canceled},
   148  		{name: "context deadline exceeded", rdr: iotest.ErrReader(context.DeadlineExceeded), exp: context.DeadlineExceeded},
   149  		{name: "connection reset", rdr: iotest.ErrReader(syscall.ECONNRESET), exp: syscall.ECONNRESET},
   150  	} {
   151  		t.Run(test.name, func(t *testing.T) {
   152  			ctx, cancel := context.WithCancel(context.Background())
   153  			defer cancel()
   154  
   155  			client := &Client{
   156  				version: "1.30",
   157  				client: newMockClient(func(req *http.Request) (*http.Response, error) {
   158  					return &http.Response{
   159  						StatusCode: http.StatusOK,
   160  						Body:       io.NopCloser(test.rdr),
   161  					}, nil
   162  				}),
   163  			}
   164  			resultC, errC := client.ContainerWait(ctx, "container_id", "")
   165  			select {
   166  			case err := <-errC:
   167  				if err.Error() != test.exp.Error() {
   168  					t.Fatalf("ContainerWait() errC = %v; want %v", err, test.exp)
   169  				}
   170  				return
   171  			case result := <-resultC:
   172  				t.Fatalf("expected to not get a wait result, got %d", result.StatusCode)
   173  				return
   174  			}
   175  			// Unexpected - we should not reach this line
   176  		})
   177  	}
   178  }
   179  
   180  func ExampleClient_ContainerWait_withTimeout() {
   181  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   182  	defer cancel()
   183  
   184  	client, _ := NewClientWithOpts(FromEnv)
   185  	_, errC := client.ContainerWait(ctx, "container_id", "")
   186  	if err := <-errC; err != nil {
   187  		log.Fatal(err)
   188  	}
   189  }