package container import ( "testing" "time" "github.com/moby/moby/api/types" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/network" "github.com/moby/moby/client" systemutil "github.com/moby/moby/v2/integration/internal/system" "github.com/moby/moby/v2/internal/testutil" "github.com/moby/moby/v2/internal/testutil/daemon" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/poll" "gotest.tools/v3/skip" ) func TestAttach(t *testing.T) { ctx := setupTest(t) apiClient := testEnv.APIClient() tests := []struct { doc string tty bool expectedMediaType string }{ { doc: "without TTY", expectedMediaType: types.MediaTypeMultiplexedStream, }, { doc: "with TTY", tty: true, expectedMediaType: types.MediaTypeRawStream, }, } for _, tc := range tests { t.Run(tc.doc, func(t *testing.T) { t.Parallel() ctx := testutil.StartSpan(ctx, t) resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{ Config: &container.Config{ Image: "busybox", Cmd: []string{"echo", "hello"}, Tty: tc.tty, }, HostConfig: &container.HostConfig{}, NetworkingConfig: &network.NetworkingConfig{}, }) assert.NilError(t, err) attach, err := apiClient.ContainerAttach(ctx, resp.ID, client.ContainerAttachOptions{ Stdout: true, Stderr: true, }) assert.NilError(t, err) mediaType, ok := attach.MediaType() assert.Check(t, ok) assert.Check(t, is.Equal(mediaType, tc.expectedMediaType)) }) } } // Regression test for #37182 func TestAttachDisconnectLeak(t *testing.T) { skip.If(t, testEnv.DaemonInfo.OSType != "linux", "Bug still exists on Windows") t.Parallel() ctx := testutil.StartSpan(baseContext, t) // Use a new daemon to make sure stuff from other tests isn't affecting the // goroutine count. d := daemon.New(t) defer d.Cleanup(t) d.StartWithBusybox(ctx, t, "--iptables=false", "--ip6tables=false") apiClient := d.NewClientT(t) resp, err := apiClient.ContainerCreate(ctx, client.ContainerCreateOptions{ Config: &container.Config{ Image: "busybox", Cmd: []string{"/bin/sh", "-c", "while true; usleep 100000; done"}, }, HostConfig: &container.HostConfig{}, NetworkingConfig: &network.NetworkingConfig{}, }) assert.NilError(t, err) cID := resp.ID defer apiClient.ContainerRemove(ctx, cID, client.ContainerRemoveOptions{ Force: true, }) nGoroutines := systemutil.WaitForStableGoroutineCount(ctx, t, apiClient) attach, err := apiClient.ContainerAttach(ctx, cID, client.ContainerAttachOptions{ Stdout: true, }) assert.NilError(t, err) defer attach.Close() poll.WaitOn(t, func(_ poll.LogT) poll.Result { count := systemutil.WaitForStableGoroutineCount(ctx, t, apiClient) if count > nGoroutines { return poll.Success() } return poll.Continue("waiting for goroutines to increase from %d, current: %d", nGoroutines, count) }, poll.WithTimeout(time.Minute), ) attach.Close() poll.WaitOn(t, systemutil.CheckGoroutineCount(ctx, apiClient, nGoroutines), poll.WithTimeout(time.Minute)) }