Browse code

Implement regression test for stdin attach

Guillaume J. Charmes authored on 2013/06/25 10:22:02
Showing 5 changed files
... ...
@@ -660,7 +660,20 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
660 660
 	if err != nil {
661 661
 		return err
662 662
 	}
663
-	defer in.Close()
663
+	defer func() {
664
+		if tcpc, ok := in.(*net.TCPConn); ok {
665
+			tcpc.CloseWrite()
666
+		} else {
667
+			in.Close()
668
+		}
669
+	}()
670
+	defer func() {
671
+		if tcpc, ok := out.(*net.TCPConn); ok {
672
+			tcpc.CloseWrite()
673
+		} else if closer, ok := out.(io.Closer); ok {
674
+			closer.Close()
675
+		}
676
+	}()
664 677
 
665 678
 	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
666 679
 	if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
... ...
@@ -1279,8 +1279,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1279 1279
 	}
1280 1280
 
1281 1281
 	if !config.AttachStdout && !config.AttachStderr {
1282
-		fmt.Fprintf(cli.out, "%s\n", runResult.ID)
1282
+		// Make this asynchrone in order to let the client write to stdin before having to read the ID
1283
+		go fmt.Fprintf(cli.out, "%s\n", runResult.ID)
1283 1284
 	}
1285
+
1284 1286
 	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
1285 1287
 		if config.Tty {
1286 1288
 			if err := cli.monitorTtySize(runResult.ID); err != nil {
... ...
@@ -1301,6 +1303,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1301 1301
 		if config.AttachStderr {
1302 1302
 			v.Set("stderr", "1")
1303 1303
 		}
1304
+
1304 1305
 		if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil {
1305 1306
 			utils.Debugf("Error hijack: %s", err)
1306 1307
 			return err
... ...
@@ -1466,6 +1469,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
1466 1466
 		}
1467 1467
 		defer term.RestoreTerminal(cli.terminalFd, oldState)
1468 1468
 	}
1469
+
1469 1470
 	sendStdin := utils.Go(func() error {
1470 1471
 		if in != nil {
1471 1472
 			io.Copy(rwc, in)
... ...
@@ -3,8 +3,9 @@ package docker
3 3
 import (
4 4
 	"bufio"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/utils"
6 7
 	"io"
7
-	_ "io/ioutil"
8
+	"io/ioutil"
8 9
 	"strings"
9 10
 	"testing"
10 11
 	"time"
... ...
@@ -59,20 +60,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
59 59
 }
60 60
 
61 61
 /*TODO
62
-func cmdWait(srv *Server, container *Container) error {
63
-	stdout, stdoutPipe := io.Pipe()
64
-
65
-	go func() {
66
-		srv.CmdWait(nil, stdoutPipe, container.Id)
67
-	}()
68
-
69
-	if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
70
-		return err
71
-	}
72
-	// Cleanup pipes
73
-	return closeWrap(stdout, stdoutPipe)
74
-}
75
-
76 62
 func cmdImages(srv *Server, args ...string) (string, error) {
77 63
 	stdout, stdoutPipe := io.Pipe()
78 64
 
... ...
@@ -144,41 +131,39 @@ func TestImages(t *testing.T) {
144 144
 	// todo: add checks for -a
145 145
 }
146 146
 
147
+*/
147 148
 // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
148 149
 func TestRunHostname(t *testing.T) {
149
-	runtime, err := newTestRuntime()
150
-	if err != nil {
151
-		t.Fatal(err)
152
-	}
153
-	defer nuke(runtime)
154
-
155
-	srv := &Server{runtime: runtime}
156
-
157
-	stdin, _ := io.Pipe()
158 150
 	stdout, stdoutPipe := io.Pipe()
159 151
 
152
+	cli := NewDockerCli(nil, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
153
+	defer cleanup(globalRuntime)
154
+
160 155
 	c := make(chan struct{})
161 156
 	go func() {
162
-		if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
157
+		defer close(c)
158
+		if err := cli.CmdRun("-h", "foobar", unitTestImageId, "hostname"); err != nil {
163 159
 			t.Fatal(err)
164 160
 		}
165
-		close(c)
166 161
 	}()
167
-	cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
168
-	if err != nil {
169
-		t.Fatal(err)
170
-	}
171
-	if cmdOutput != "foobar\n" {
172
-		t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
173
-	}
162
+	utils.Debugf("--")
163
+	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
164
+		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
165
+		if err != nil {
166
+			t.Fatal(err)
167
+		}
168
+		if cmdOutput != "foobar\n" {
169
+			t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
170
+		}
171
+	})
174 172
 
175
-	setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
173
+	setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
176 174
 		<-c
177
-		cmdWait(srv, srv.runtime.List()[0])
178 175
 	})
179 176
 
180 177
 }
181 178
 
179
+/*
182 180
 func TestRunExit(t *testing.T) {
183 181
 	runtime, err := newTestRuntime()
184 182
 	if err != nil {
... ...
@@ -335,33 +320,26 @@ func TestRunDisconnectTty(t *testing.T) {
335 335
 	}
336 336
 }
337 337
 */
338
-/*
338
+
339 339
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
340 340
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
341 341
 // then detach from it and print the container id.
342 342
 func TestRunAttachStdin(t *testing.T) {
343
-	runtime, err := newTestRuntime()
344
-	if err != nil {
345
-		t.Fatal(err)
346
-	}
347
-	defer nuke(runtime)
348
-	srv := &Server{runtime: runtime}
349
-	// enableCors:  false,
350
-	// lock:        &sync.Mutex{},
351
-	// pullingPool: make(map[string]struct{}),
352
-	// pushingPool: make(map[string]struct{}),
353 343
 
354 344
 	stdin, stdinPipe := io.Pipe()
355 345
 	stdout, stdoutPipe := io.Pipe()
356 346
 
347
+	cli := NewDockerCli(stdin, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
348
+	defer cleanup(globalRuntime)
349
+
357 350
 	ch := make(chan struct{})
358 351
 	go func() {
359
-		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
360
-		close(ch)
352
+		defer close(ch)
353
+		cli.CmdRun("-i", "-a", "stdin", unitTestImageId, "sh", "-c", "echo hello && cat")
361 354
 	}()
362 355
 
363 356
 	// Send input to the command, close stdin
364
-	setTimeout(t, "Write timed out", 2*time.Second, func() {
357
+	setTimeout(t, "Write timed out", 10*time.Second, func() {
365 358
 		if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
366 359
 			t.Fatal(err)
367 360
 		}
... ...
@@ -370,23 +348,27 @@ func TestRunAttachStdin(t *testing.T) {
370 370
 		}
371 371
 	})
372 372
 
373
-	container := runtime.List()[0]
373
+	container := globalRuntime.List()[0]
374 374
 
375 375
 	// Check output
376
-	cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
377
-	if err != nil {
378
-		t.Fatal(err)
379
-	}
380
-	if cmdOutput != container.ShortId()+"\n" {
381
-		t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
382
-	}
376
+	setTimeout(t, "Reading command output time out", 10*time.Second, func() {
377
+		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
378
+		if err != nil {
379
+			t.Fatal(err)
380
+		}
381
+		if cmdOutput != container.ShortID()+"\n" {
382
+			t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
383
+		}
384
+	})
383 385
 
384 386
 	// wait for CmdRun to return
385
-	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
387
+	setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
388
+		// Unblock hijack end
389
+		stdout.Read([]byte{})
386 390
 		<-ch
387 391
 	})
388 392
 
389
-	setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
393
+	setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() {
390 394
 		container.Wait()
391 395
 	})
392 396
 
... ...
@@ -404,7 +386,7 @@ func TestRunAttachStdin(t *testing.T) {
404 404
 		}
405 405
 	}
406 406
 }
407
-*/
407
+
408 408
 /*
409 409
 // Expected behaviour, the process stays alive when the client disconnects
410 410
 func TestAttachDisconnect(t *testing.T) {
... ...
@@ -24,6 +24,8 @@ const (
24 24
 	testDaemonProto   = "tcp"
25 25
 )
26 26
 
27
+var globalRuntime *Runtime
28
+
27 29
 func nuke(runtime *Runtime) error {
28 30
 	var wg sync.WaitGroup
29 31
 	for _, container := range runtime.List() {
... ...
@@ -37,6 +39,23 @@ func nuke(runtime *Runtime) error {
37 37
 	return os.RemoveAll(runtime.root)
38 38
 }
39 39
 
40
+func cleanup(runtime *Runtime) error {
41
+	for _, container := range runtime.List() {
42
+		container.Kill()
43
+		runtime.Destroy(container)
44
+	}
45
+	images, err := runtime.graph.All()
46
+	if err != nil {
47
+		return err
48
+	}
49
+	for _, image := range images {
50
+		if image.ID != unitTestImageId {
51
+			runtime.graph.Delete(image.ID)
52
+		}
53
+	}
54
+	return nil
55
+}
56
+
40 57
 func layerArchive(tarfile string) (io.Reader, error) {
41 58
 	// FIXME: need to close f somewhere
42 59
 	f, err := os.Open(tarfile)
... ...
@@ -64,6 +83,7 @@ func init() {
64 64
 	if err != nil {
65 65
 		panic(err)
66 66
 	}
67
+	globalRuntime = runtime
67 68
 
68 69
 	// Create the "Server"
69 70
 	srv := &Server{
... ...
@@ -84,6 +104,9 @@ func init() {
84 84
 			panic(err)
85 85
 		}
86 86
 	}()
87
+
88
+	// Give some time to ListenAndServer to actually start
89
+	time.Sleep(time.Second)
87 90
 }
88 91
 
89 92
 // FIXME: test that ImagePull(json=true) send correct json output
90 93
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+package docker
1
+
2
+import (
3
+	"github.com/dotcloud/docker/utils"
4
+	"runtime"
5
+	"testing"
6
+)
7
+
8
+func TestFinal(t *testing.T) {
9
+	cleanup(globalRuntime)
10
+	t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
11
+}