Selaa lähdekoodia

Merge pull request #1277 from dotcloud/add_commands_unit_tests

* Tests: Reimplement old Commands unit tests in order to insure behavior
Guillaume J. Charmes 12 vuotta sitten
vanhempi
commit
6ae3305040
2 muutettua tiedostoa jossa 225 lisäystä ja 2 poistoa
  1. 3 1
      commands.go
  2. 222 1
      commands_test.go

+ 3 - 1
commands.go

@@ -1409,7 +1409,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
 	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
 		if config.Tty {
 		if config.Tty {
 			if err := cli.monitorTtySize(runResult.ID); err != nil {
 			if err := cli.monitorTtySize(runResult.ID); err != nil {
-				return err
+				utils.Debugf("Error monitoring TTY size: %s\n", err)
 			}
 			}
 		}
 		}
 
 
@@ -1580,6 +1580,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
 
 
 	receiveStdout := utils.Go(func() error {
 	receiveStdout := utils.Go(func() error {
 		_, err := io.Copy(out, br)
 		_, err := io.Copy(out, br)
+		utils.Debugf("[hijack] End of stdout")
 		return err
 		return err
 	})
 	})
 
 
@@ -1594,6 +1595,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
 	sendStdin := utils.Go(func() error {
 	sendStdin := utils.Go(func() error {
 		if in != nil {
 		if in != nil {
 			io.Copy(rwc, in)
 			io.Copy(rwc, in)
+			utils.Debugf("[hijack] End of stdin")
 		}
 		}
 		if tcpc, ok := rwc.(*net.TCPConn); ok {
 		if tcpc, ok := rwc.(*net.TCPConn); ok {
 			if err := tcpc.CloseWrite(); err != nil {
 			if err := tcpc.CloseWrite(); err != nil {

+ 222 - 1
commands_test.go

@@ -73,7 +73,7 @@ func TestRunHostname(t *testing.T) {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
 	}()
 	}()
-	utils.Debugf("--")
+
 	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
 	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
 		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
 		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
 		if err != nil {
 		if err != nil {
@@ -90,6 +90,157 @@ func TestRunHostname(t *testing.T) {
 
 
 }
 }
 
 
+func TestRunExit(t *testing.T) {
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	c1 := make(chan struct{})
+	go func() {
+		cli.CmdRun("-i", unitTestImageID, "/bin/cat")
+		close(c1)
+	}()
+
+	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	container := globalRuntime.List()[0]
+
+	// Closing /bin/cat stdin, expect it to exit
+	if err := stdin.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	// as the process exited, CmdRun must finish and unblock. Wait for it
+	setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
+		<-c1
+
+		go func() {
+			cli.CmdWait(container.ID)
+		}()
+
+		if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	// Make sure that the client has been disconnected
+	setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
+		// Expecting pipe i/o error, just check that read does not block
+		stdin.Read([]byte{})
+	})
+
+	// Cleanup pipes
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+		t.Fatal(err)
+	}
+}
+
+// Expected behaviour: the process dies when the client disconnects
+func TestRunDisconnect(t *testing.T) {
+
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	c1 := make(chan struct{})
+	go func() {
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
+		// fact that CmdRun returns.
+		cli.CmdRun("-i", unitTestImageID, "/bin/cat")
+		close(c1)
+	}()
+
+	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	// Close pipes (simulate disconnect)
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+		t.Fatal(err)
+	}
+
+	// as the pipes are close, we expect the process to die,
+	// therefore CmdRun to unblock. Wait for CmdRun
+	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
+		<-c1
+	})
+
+	// Client disconnect after run -i should cause stdin to be closed, which should
+	// cause /bin/cat to exit.
+	setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
+		container := globalRuntime.List()[0]
+		container.Wait()
+		if container.State.Running {
+			t.Fatalf("/bin/cat is still running after closing stdin")
+		}
+	})
+}
+
+// Expected behaviour: the process dies when the client disconnects
+func TestRunDisconnectTty(t *testing.T) {
+
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	c1 := make(chan struct{})
+	go func() {
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
+		// fact that CmdRun returns.
+		if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
+			utils.Debugf("Error CmdRun: %s\n", err)
+		}
+
+		close(c1)
+	}()
+
+	setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
+		for {
+			// Client disconnect after run -i should keep stdin out in TTY mode
+			l := globalRuntime.List()
+			if len(l) == 1 && l[0].State.Running {
+				break
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	})
+
+	// Client disconnect after run -i should keep stdin out in TTY mode
+	container := globalRuntime.List()[0]
+
+	setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	// Close pipes (simulate disconnect)
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+		t.Fatal(err)
+	}
+
+	// In tty mode, we expect the process to stay alive even after client's stdin closes.
+	// Do not wait for run to finish
+
+	// Give some time to monitor to do his thing
+	container.WaitTimeout(500 * time.Millisecond)
+	if !container.State.Running {
+		t.Fatalf("/bin/cat should  still be running after closing stdin (tty mode)")
+	}
+}
+
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
 // then detach from it and print the container id.
 // then detach from it and print the container id.
@@ -157,3 +308,73 @@ func TestRunAttachStdin(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+// Expected behaviour, the process stays alive when the client disconnects
+func TestAttachDisconnect(t *testing.T) {
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	go func() {
+		// Start a process in daemon mode
+		if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
+			utils.Debugf("Error CmdRun: %s\n", err)
+		}
+	}()
+
+	setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
+		if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
+		for {
+			l := globalRuntime.List()
+			if len(l) == 1 && l[0].State.Running {
+				break
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	})
+
+	container := globalRuntime.List()[0]
+
+	// Attach to it
+	c1 := make(chan struct{})
+	go func() {
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
+		// fact that CmdAttach returns.
+		cli.CmdAttach(container.ID)
+		close(c1)
+	}()
+
+	setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
+			t.Fatal(err)
+		}
+	})
+	// Close pipes (client disconnects)
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+		t.Fatal(err)
+	}
+
+	// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
+	setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
+		<-c1
+	})
+
+	// We closed stdin, expect /bin/cat to still be running
+	// Wait a little bit to make sure container.monitor() did his thing
+	err := container.WaitTimeout(500 * time.Millisecond)
+	if err == nil || !container.State.Running {
+		t.Fatalf("/bin/cat is not running after closing stdin")
+	}
+
+	// Try to avoid the timeoout in destroy. Best effort, don't check error
+	cStdin, _ := container.StdinPipe()
+	cStdin.Close()
+	container.Wait()
+}