From 15ae314cbb8ede09a2d2c9e70c6a0d7d87817b64 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 21:55:08 -0700 Subject: [PATCH] Mock Hijack and Implement Unit test for Attach --- api_test.go | 119 +++++++++++++++++++++++++++++++++++++++++++++-- commands_test.go | 12 ++--- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/api_test.go b/api_test.go index 89093848f1..5ac3d7ad05 100644 --- a/api_test.go +++ b/api_test.go @@ -702,8 +702,94 @@ func TestPostContainersWait(t *testing.T) { } func TestPostContainersAttach(t *testing.T) { - //FIXME: Implement this test - t.Log("Test on implemented") + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + // Start the process + if err := container.Start(); err != nil { + t.Fatal(err) + } + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + // 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. + + r := &hijackTester{ + ResponseRecorder: httptest.NewRecorder(), + in: stdin, + out: stdoutPipe, + } + + req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + + body, err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id}) + close(c1) + if err != nil { + t.Fatal(err) + } + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + }() + + // Acknowledge hijack + setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { + stdout.Read([]byte{}) + stdout.Read(make([]byte, 4096)) + }) + + 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 (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() } // FIXME: Test deleting runnign container @@ -760,5 +846,32 @@ func TestDeleteContainers(t *testing.T) { func TestDeleteImages(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") +} + +// Mocked types for tests +type NopConn struct { + io.ReadCloser + io.Writer +} + +func (c *NopConn) LocalAddr() net.Addr { return nil } +func (c *NopConn) RemoteAddr() net.Addr { return nil } +func (c *NopConn) SetDeadline(t time.Time) error { return nil } +func (c *NopConn) SetReadDeadline(t time.Time) error { return nil } +func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil } + +type hijackTester struct { + *httptest.ResponseRecorder + in io.ReadCloser + out io.Writer +} + +func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) { + bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out)) + conn := &NopConn{ + ReadCloser: t.in, + Writer: t.out, + } + return conn, bufrw, nil } diff --git a/commands_test.go b/commands_test.go index c874e8cd2b..80f31e4f76 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1,17 +1,15 @@ package docker import ( - /*"bufio" + "bufio" "fmt" - "github.com/dotcloud/docker/rcli" "io" - "io/ioutil" - "strings"*/ + _ "io/ioutil" + "strings" "testing" "time" ) -/*TODO func closeWrap(args ...io.Closer) error { e := false ret := fmt.Errorf("Error closing elements") @@ -26,7 +24,7 @@ func closeWrap(args ...io.Closer) error { } return nil } -*/ + func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { c := make(chan bool) @@ -44,7 +42,6 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { } } -/*TODO func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error { for i := 0; i < count; i++ { if _, err := w.Write([]byte(input)); err != nil { @@ -61,6 +58,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } +/*TODO func cmdWait(srv *Server, container *Container) error { stdout, stdoutPipe := io.Pipe()