Implement regression test for stdin attach

This commit is contained in:
Guillaume J. Charmes 2013-06-24 18:22:02 -07:00
parent 873a5aa8e7
commit 5190f7f33a
5 changed files with 96 additions and 62 deletions

15
api.go
View File

@ -660,7 +660,20 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
if err != nil { if err != nil {
return err return err
} }
defer in.Close() defer func() {
if tcpc, ok := in.(*net.TCPConn); ok {
tcpc.CloseWrite()
} else {
in.Close()
}
}()
defer func() {
if tcpc, ok := out.(*net.TCPConn); ok {
tcpc.CloseWrite()
} else if closer, ok := out.(io.Closer); ok {
closer.Close()
}
}()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil { if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {

View File

@ -1279,8 +1279,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
} }
if !config.AttachStdout && !config.AttachStderr { if !config.AttachStdout && !config.AttachStderr {
fmt.Fprintf(cli.out, "%s\n", runResult.ID) // Make this asynchrone in order to let the client write to stdin before having to read the ID
go fmt.Fprintf(cli.out, "%s\n", runResult.ID)
} }
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 {
@ -1301,6 +1303,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if config.AttachStderr { if config.AttachStderr {
v.Set("stderr", "1") v.Set("stderr", "1")
} }
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil { if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil {
utils.Debugf("Error hijack: %s", err) utils.Debugf("Error hijack: %s", err)
return err return err
@ -1466,6 +1469,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
} }
defer term.RestoreTerminal(cli.terminalFd, oldState) defer term.RestoreTerminal(cli.terminalFd, oldState)
} }
sendStdin := utils.Go(func() error { sendStdin := utils.Go(func() error {
if in != nil { if in != nil {
io.Copy(rwc, in) io.Copy(rwc, in)

View File

@ -3,8 +3,9 @@ package docker
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/dotcloud/docker/utils"
"io" "io"
_ "io/ioutil" "io/ioutil"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -59,20 +60,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
} }
/*TODO /*TODO
func cmdWait(srv *Server, container *Container) error {
stdout, stdoutPipe := io.Pipe()
go func() {
srv.CmdWait(nil, stdoutPipe, container.Id)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
return err
}
// Cleanup pipes
return closeWrap(stdout, stdoutPipe)
}
func cmdImages(srv *Server, args ...string) (string, error) { func cmdImages(srv *Server, args ...string) (string, error) {
stdout, stdoutPipe := io.Pipe() stdout, stdoutPipe := io.Pipe()
@ -144,41 +131,39 @@ func TestImages(t *testing.T) {
// todo: add checks for -a // todo: add checks for -a
} }
*/
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
func TestRunHostname(t *testing.T) { func TestRunHostname(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, _ := io.Pipe()
stdout, stdoutPipe := io.Pipe() stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(nil, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{}) c := make(chan struct{})
go func() { go func() {
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil { defer close(c)
if err := cli.CmdRun("-h", "foobar", unitTestImageId, "hostname"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
close(c)
}() }()
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') utils.Debugf("--")
if err != nil { setTimeout(t, "Reading command output time out", 2*time.Second, func() {
t.Fatal(err) cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
} if err != nil {
if cmdOutput != "foobar\n" { t.Fatal(err)
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput) }
} if cmdOutput != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
}
})
setTimeout(t, "CmdRun timed out", 2*time.Second, func() { setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c <-c
cmdWait(srv, srv.runtime.List()[0])
}) })
} }
/*
func TestRunExit(t *testing.T) { func TestRunExit(t *testing.T) {
runtime, err := newTestRuntime() runtime, err := newTestRuntime()
if err != nil { if err != nil {
@ -335,33 +320,26 @@ func TestRunDisconnectTty(t *testing.T) {
} }
} }
*/ */
/*
// 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.
func TestRunAttachStdin(t *testing.T) { func TestRunAttachStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
// enableCors: false,
// lock: &sync.Mutex{},
// pullingPool: make(map[string]struct{}),
// pushingPool: make(map[string]struct{}),
stdin, stdinPipe := io.Pipe() stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe() stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(stdin, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
ch := make(chan struct{}) ch := make(chan struct{})
go func() { go func() {
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat") defer close(ch)
close(ch) cli.CmdRun("-i", "-a", "stdin", unitTestImageId, "sh", "-c", "echo hello && cat")
}() }()
// Send input to the command, close stdin // Send input to the command, close stdin
setTimeout(t, "Write timed out", 2*time.Second, func() { setTimeout(t, "Write timed out", 10*time.Second, func() {
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil { if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -370,23 +348,27 @@ func TestRunAttachStdin(t *testing.T) {
} }
}) })
container := runtime.List()[0] container := globalRuntime.List()[0]
// Check output // Check output
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') setTimeout(t, "Reading command output time out", 10*time.Second, func() {
if err != nil { cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
t.Fatal(err) if err != nil {
} t.Fatal(err)
if cmdOutput != container.ShortId()+"\n" { }
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput) if cmdOutput != container.ShortID()+"\n" {
} t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
}
})
// wait for CmdRun to return // wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() { setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
// Unblock hijack end
stdout.Read([]byte{})
<-ch <-ch
}) })
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() { setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() {
container.Wait() container.Wait()
}) })
@ -404,7 +386,7 @@ func TestRunAttachStdin(t *testing.T) {
} }
} }
} }
*/
/* /*
// Expected behaviour, the process stays alive when the client disconnects // Expected behaviour, the process stays alive when the client disconnects
func TestAttachDisconnect(t *testing.T) { func TestAttachDisconnect(t *testing.T) {

View File

@ -24,6 +24,8 @@ const (
testDaemonProto = "tcp" testDaemonProto = "tcp"
) )
var globalRuntime *Runtime
func nuke(runtime *Runtime) error { func nuke(runtime *Runtime) error {
var wg sync.WaitGroup var wg sync.WaitGroup
for _, container := range runtime.List() { for _, container := range runtime.List() {
@ -37,6 +39,23 @@ func nuke(runtime *Runtime) error {
return os.RemoveAll(runtime.root) return os.RemoveAll(runtime.root)
} }
func cleanup(runtime *Runtime) error {
for _, container := range runtime.List() {
container.Kill()
runtime.Destroy(container)
}
images, err := runtime.graph.All()
if err != nil {
return err
}
for _, image := range images {
if image.ID != unitTestImageId {
runtime.graph.Delete(image.ID)
}
}
return nil
}
func layerArchive(tarfile string) (io.Reader, error) { func layerArchive(tarfile string) (io.Reader, error) {
// FIXME: need to close f somewhere // FIXME: need to close f somewhere
f, err := os.Open(tarfile) f, err := os.Open(tarfile)
@ -64,6 +83,7 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
globalRuntime = runtime
// Create the "Server" // Create the "Server"
srv := &Server{ srv := &Server{
@ -84,6 +104,9 @@ func init() {
panic(err) panic(err)
} }
}() }()
// Give some time to ListenAndServer to actually start
time.Sleep(time.Second)
} }
// FIXME: test that ImagePull(json=true) send correct json output // FIXME: test that ImagePull(json=true) send correct json output

12
z_final_test.go Normal file
View File

@ -0,0 +1,12 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"runtime"
"testing"
)
func TestFinal(t *testing.T) {
cleanup(globalRuntime)
t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
}