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 }