github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/integration-cli/docker_cli_attach_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os/exec"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/docker/docker/integration-cli/cli"
    16  	"gotest.tools/v3/assert"
    17  	"gotest.tools/v3/icmd"
    18  )
    19  
    20  const attachWait = 5 * time.Second
    21  
    22  type DockerCLIAttachSuite struct {
    23  	ds *DockerSuite
    24  }
    25  
    26  func (s *DockerCLIAttachSuite) TearDownTest(ctx context.Context, c *testing.T) {
    27  	s.ds.TearDownTest(ctx, c)
    28  }
    29  
    30  func (s *DockerCLIAttachSuite) OnTimeout(c *testing.T) {
    31  	s.ds.OnTimeout(c)
    32  }
    33  
    34  func (s *DockerCLIAttachSuite) TestAttachMultipleAndRestart(c *testing.T) {
    35  	endGroup := &sync.WaitGroup{}
    36  	startGroup := &sync.WaitGroup{}
    37  	endGroup.Add(3)
    38  	startGroup.Add(3)
    39  
    40  	cli.DockerCmd(c, "run", "--name", "attacher", "-d", "busybox", "/bin/sh", "-c", "while true; do sleep 1; echo hello; done")
    41  	cli.WaitRun(c, "attacher")
    42  
    43  	startDone := make(chan struct{})
    44  	endDone := make(chan struct{})
    45  
    46  	go func() {
    47  		endGroup.Wait()
    48  		close(endDone)
    49  	}()
    50  
    51  	go func() {
    52  		startGroup.Wait()
    53  		close(startDone)
    54  	}()
    55  
    56  	for i := 0; i < 3; i++ {
    57  		go func() {
    58  			cmd := exec.Command(dockerBinary, "attach", "attacher")
    59  
    60  			defer func() {
    61  				cmd.Wait()
    62  				endGroup.Done()
    63  			}()
    64  
    65  			out, err := cmd.StdoutPipe()
    66  			if err != nil {
    67  				c.Error(err)
    68  			}
    69  			defer out.Close()
    70  
    71  			if err := cmd.Start(); err != nil {
    72  				c.Error(err)
    73  			}
    74  
    75  			buf := make([]byte, 1024)
    76  
    77  			if _, err := out.Read(buf); err != nil && err != io.EOF {
    78  				c.Error(err)
    79  			}
    80  
    81  			startGroup.Done()
    82  
    83  			if !strings.Contains(string(buf), "hello") {
    84  				c.Errorf("unexpected output %s expected hello\n", string(buf))
    85  			}
    86  		}()
    87  	}
    88  
    89  	select {
    90  	case <-startDone:
    91  	case <-time.After(attachWait):
    92  		c.Fatalf("Attaches did not initialize properly")
    93  	}
    94  
    95  	cli.DockerCmd(c, "kill", "attacher")
    96  
    97  	select {
    98  	case <-endDone:
    99  	case <-time.After(attachWait):
   100  		c.Fatalf("Attaches did not finish properly")
   101  	}
   102  }
   103  
   104  func (s *DockerCLIAttachSuite) TestAttachTTYWithoutStdin(c *testing.T) {
   105  	// TODO: Figure out how to get this running again reliable on Windows.
   106  	// It works by accident at the moment. Sometimes. I've gone back to v1.13.0 and see the same.
   107  	// On Windows, docker run -d -ti busybox causes the container to exit immediately.
   108  	// Obviously a year back when I updated the test, that was not the case. However,
   109  	// with this, and the test racing with the tear-down which panic's, sometimes CI
   110  	// will just fail and `MISS` all the other tests. For now, disabling it. Will
   111  	// open an issue to track re-enabling this and root-causing the problem.
   112  	testRequires(c, DaemonIsLinux)
   113  	out := cli.DockerCmd(c, "run", "-d", "-ti", "busybox").Stdout()
   114  	id := strings.TrimSpace(out)
   115  	cli.WaitRun(c, id)
   116  
   117  	done := make(chan error, 1)
   118  	go func() {
   119  		defer close(done)
   120  
   121  		cmd := exec.Command(dockerBinary, "attach", id)
   122  		if _, err := cmd.StdinPipe(); err != nil {
   123  			done <- err
   124  			return
   125  		}
   126  
   127  		expected := "the input device is not a TTY"
   128  		if runtime.GOOS == "windows" {
   129  			expected += ".  If you are using mintty, try prefixing the command with 'winpty'"
   130  		}
   131  		result := icmd.RunCmd(icmd.Cmd{
   132  			Command: cmd.Args,
   133  			Env:     cmd.Env,
   134  			Dir:     cmd.Dir,
   135  			Stdin:   cmd.Stdin,
   136  			Stdout:  cmd.Stdout,
   137  		})
   138  		if result.Error == nil {
   139  			done <- fmt.Errorf("attach should have failed")
   140  			return
   141  		} else if !strings.Contains(result.Combined(), expected) {
   142  			done <- fmt.Errorf("attach failed with error %q: expected %q", out, expected)
   143  			return
   144  		}
   145  	}()
   146  
   147  	select {
   148  	case err := <-done:
   149  		assert.NilError(c, err)
   150  	case <-time.After(attachWait):
   151  		c.Fatal("attach is running but should have failed")
   152  	}
   153  }
   154  
   155  func (s *DockerCLIAttachSuite) TestAttachDisconnect(c *testing.T) {
   156  	testRequires(c, DaemonIsLinux)
   157  	out := cli.DockerCmd(c, "run", "-di", "busybox", "/bin/cat").Stdout()
   158  	id := strings.TrimSpace(out)
   159  
   160  	cmd := exec.Command(dockerBinary, "attach", id)
   161  	stdin, err := cmd.StdinPipe()
   162  	if err != nil {
   163  		c.Fatal(err)
   164  	}
   165  	defer stdin.Close()
   166  	stdout, err := cmd.StdoutPipe()
   167  	assert.NilError(c, err)
   168  	defer stdout.Close()
   169  	assert.Assert(c, cmd.Start() == nil)
   170  	defer func() {
   171  		cmd.Process.Kill()
   172  		cmd.Wait()
   173  	}()
   174  
   175  	_, err = stdin.Write([]byte("hello\n"))
   176  	assert.NilError(c, err)
   177  	out, err = bufio.NewReader(stdout).ReadString('\n')
   178  	assert.NilError(c, err)
   179  	assert.Equal(c, strings.TrimSpace(out), "hello")
   180  
   181  	assert.Assert(c, stdin.Close() == nil)
   182  
   183  	// Expect container to still be running after stdin is closed
   184  	running := inspectField(c, id, "State.Running")
   185  	assert.Equal(c, running, "true")
   186  }
   187  
   188  func (s *DockerCLIAttachSuite) TestAttachPausedContainer(c *testing.T) {
   189  	testRequires(c, IsPausable)
   190  	runSleepingContainer(c, "-d", "--name=test")
   191  	cli.DockerCmd(c, "pause", "test")
   192  
   193  	result := cli.Docker(cli.Args("attach", "test"))
   194  	result.Assert(c, icmd.Expected{
   195  		Error:    "exit status 1",
   196  		ExitCode: 1,
   197  		Err:      "You cannot attach to a paused container, unpause it first",
   198  	})
   199  }