Browse code

Merge pull request #5706 from vieux/remove_add_string

Solomon Hykes authored on 2014/05/15 09:31:52
Showing 9 changed files
... ...
@@ -122,17 +122,17 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter
122 122
 	var (
123 123
 		authConfig, err = ioutil.ReadAll(r.Body)
124 124
 		job             = eng.Job("auth")
125
-		status          string
125
+		stdoutBuffer    = bytes.NewBuffer(nil)
126 126
 	)
127 127
 	if err != nil {
128 128
 		return err
129 129
 	}
130 130
 	job.Setenv("authConfig", string(authConfig))
131
-	job.Stdout.AddString(&status)
131
+	job.Stdout.Add(stdoutBuffer)
132 132
 	if err = job.Run(); err != nil {
133 133
 		return err
134 134
 	}
135
-	if status != "" {
135
+	if status := engine.Tail(stdoutBuffer, 1); status != "" {
136 136
 		var env engine.Env
137 137
 		env.Set("Status", status)
138 138
 		return writeJSON(w, http.StatusOK, env)
... ...
@@ -393,9 +393,10 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
393 393
 		return err
394 394
 	}
395 395
 	var (
396
-		config engine.Env
397
-		env    engine.Env
398
-		job    = eng.Job("commit", r.Form.Get("container"))
396
+		config       engine.Env
397
+		env          engine.Env
398
+		job          = eng.Job("commit", r.Form.Get("container"))
399
+		stdoutBuffer = bytes.NewBuffer(nil)
399 400
 	)
400 401
 	if err := config.Decode(r.Body); err != nil {
401 402
 		utils.Errorf("%s", err)
... ...
@@ -407,12 +408,11 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
407 407
 	job.Setenv("comment", r.Form.Get("comment"))
408 408
 	job.SetenvSubEnv("config", &config)
409 409
 
410
-	var id string
411
-	job.Stdout.AddString(&id)
410
+	job.Stdout.Add(stdoutBuffer)
412 411
 	if err := job.Run(); err != nil {
413 412
 		return err
414 413
 	}
415
-	env.Set("Id", id)
414
+	env.Set("Id", engine.Tail(stdoutBuffer, 1))
416 415
 	return writeJSON(w, http.StatusCreated, env)
417 416
 }
418 417
 
... ...
@@ -603,17 +603,17 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
603 603
 		return nil
604 604
 	}
605 605
 	var (
606
-		out         engine.Env
607
-		job         = eng.Job("create", r.Form.Get("name"))
608
-		outWarnings []string
609
-		outId       string
610
-		warnings    = bytes.NewBuffer(nil)
606
+		out          engine.Env
607
+		job          = eng.Job("create", r.Form.Get("name"))
608
+		outWarnings  []string
609
+		stdoutBuffer = bytes.NewBuffer(nil)
610
+		warnings     = bytes.NewBuffer(nil)
611 611
 	)
612 612
 	if err := job.DecodeEnv(r.Body); err != nil {
613 613
 		return err
614 614
 	}
615 615
 	// Read container ID from the first line of stdout
616
-	job.Stdout.AddString(&outId)
616
+	job.Stdout.Add(stdoutBuffer)
617 617
 	// Read warnings from stderr
618 618
 	job.Stderr.Add(warnings)
619 619
 	if err := job.Run(); err != nil {
... ...
@@ -624,7 +624,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
624 624
 	for scanner.Scan() {
625 625
 		outWarnings = append(outWarnings, scanner.Text())
626 626
 	}
627
-	out.Set("Id", outId)
627
+	out.Set("Id", engine.Tail(stdoutBuffer, 1))
628 628
 	out.SetList("Warnings", outWarnings)
629 629
 	return writeJSON(w, http.StatusCreated, out)
630 630
 }
... ...
@@ -720,20 +720,16 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp
720 720
 		return fmt.Errorf("Missing parameter")
721 721
 	}
722 722
 	var (
723
-		env    engine.Env
724
-		status string
725
-		job    = eng.Job("wait", vars["name"])
723
+		env          engine.Env
724
+		stdoutBuffer = bytes.NewBuffer(nil)
725
+		job          = eng.Job("wait", vars["name"])
726 726
 	)
727
-	job.Stdout.AddString(&status)
727
+	job.Stdout.Add(stdoutBuffer)
728 728
 	if err := job.Run(); err != nil {
729 729
 		return err
730 730
 	}
731
-	// Parse a 16-bit encoded integer to map typical unix exit status.
732
-	_, err := strconv.ParseInt(status, 10, 16)
733
-	if err != nil {
734
-		return err
735
-	}
736
-	env.Set("StatusCode", status)
731
+
732
+	env.Set("StatusCode", engine.Tail(stdoutBuffer, 1))
737 733
 	return writeJSON(w, http.StatusOK, env)
738 734
 }
739 735
 
... ...
@@ -3,11 +3,12 @@ package engine
3 3
 import (
4 4
 	"bufio"
5 5
 	"fmt"
6
-	"github.com/dotcloud/docker/utils"
7 6
 	"io"
8 7
 	"os"
9 8
 	"sort"
10 9
 	"strings"
10
+
11
+	"github.com/dotcloud/docker/utils"
11 12
 )
12 13
 
13 14
 // Installer is a standard interface for objects which can "install" themselves
... ...
@@ -1,6 +1,7 @@
1 1
 package engine
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"fmt"
5 6
 	"io"
6 7
 	"strings"
... ...
@@ -56,8 +57,8 @@ func (job *Job) Run() error {
56 56
 	defer func() {
57 57
 		job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString())
58 58
 	}()
59
-	var errorMessage string
60
-	job.Stderr.AddString(&errorMessage)
59
+	var errorMessage = bytes.NewBuffer(nil)
60
+	job.Stderr.Add(errorMessage)
61 61
 	if job.handler == nil {
62 62
 		job.Errorf("%s: command not found", job.Name)
63 63
 		job.status = 127
... ...
@@ -76,7 +77,7 @@ func (job *Job) Run() error {
76 76
 		return err
77 77
 	}
78 78
 	if job.status != 0 {
79
-		return fmt.Errorf("%s", errorMessage)
79
+		return fmt.Errorf("%s", Tail(errorMessage, 1))
80 80
 	}
81 81
 	return nil
82 82
 }
... ...
@@ -1,6 +1,8 @@
1 1
 package engine
2 2
 
3 3
 import (
4
+	"bytes"
5
+	"fmt"
4 6
 	"testing"
5 7
 )
6 8
 
... ...
@@ -40,13 +42,13 @@ func TestJobStdoutString(t *testing.T) {
40 40
 	})
41 41
 
42 42
 	job := eng.Job("say_something_in_stdout")
43
-	var output string
44
-	if err := job.Stdout.AddString(&output); err != nil {
45
-		t.Fatal(err)
46
-	}
43
+	var outputBuffer = bytes.NewBuffer(nil)
44
+	job.Stdout.Add(outputBuffer)
47 45
 	if err := job.Run(); err != nil {
48 46
 		t.Fatal(err)
49 47
 	}
48
+	fmt.Println(outputBuffer)
49
+	var output = Tail(outputBuffer, 1)
50 50
 	if expectedOutput := "Hello world"; output != expectedOutput {
51 51
 		t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
52 52
 	}
... ...
@@ -61,13 +63,12 @@ func TestJobStderrString(t *testing.T) {
61 61
 	})
62 62
 
63 63
 	job := eng.Job("say_something_in_stderr")
64
-	var output string
65
-	if err := job.Stderr.AddString(&output); err != nil {
66
-		t.Fatal(err)
67
-	}
64
+	var outputBuffer = bytes.NewBuffer(nil)
65
+	job.Stderr.Add(outputBuffer)
68 66
 	if err := job.Run(); err != nil {
69 67
 		t.Fatal(err)
70 68
 	}
69
+	var output = Tail(outputBuffer, 1)
71 70
 	if expectedOutput := "Something happened"; output != expectedOutput {
72 71
 		t.Fatalf("Stderr last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
73 72
 	}
... ...
@@ -1,8 +1,7 @@
1 1
 package engine
2 2
 
3 3
 import (
4
-	"bufio"
5
-	"container/ring"
4
+	"bytes"
6 5
 	"fmt"
7 6
 	"io"
8 7
 	"io/ioutil"
... ...
@@ -16,6 +15,28 @@ type Output struct {
16 16
 	used  bool
17 17
 }
18 18
 
19
+// Tail returns the n last lines of a buffer
20
+// stripped out of the last \n, if any
21
+// if n <= 0, returns an empty string
22
+func Tail(buffer *bytes.Buffer, n int) string {
23
+	if n <= 0 {
24
+		return ""
25
+	}
26
+	bytes := buffer.Bytes()
27
+	if len(bytes) > 0 && bytes[len(bytes)-1] == '\n' {
28
+		bytes = bytes[:len(bytes)-1]
29
+	}
30
+	for i := buffer.Len() - 2; i >= 0; i-- {
31
+		if bytes[i] == '\n' {
32
+			n--
33
+			if n == 0 {
34
+				return string(bytes[i+1:])
35
+			}
36
+		}
37
+	}
38
+	return string(bytes)
39
+}
40
+
19 41
 // NewOutput returns a new Output object with no destinations attached.
20 42
 // Writing to an empty Output will cause the written data to be discarded.
21 43
 func NewOutput() *Output {
... ...
@@ -58,42 +79,6 @@ func (o *Output) AddPipe() (io.Reader, error) {
58 58
 	return r, nil
59 59
 }
60 60
 
61
-// AddTail starts a new goroutine which will read all subsequent data written to the output,
62
-// line by line, and append the last `n` lines to `dst`.
63
-func (o *Output) AddTail(dst *[]string, n int) error {
64
-	src, err := o.AddPipe()
65
-	if err != nil {
66
-		return err
67
-	}
68
-	o.tasks.Add(1)
69
-	go func() {
70
-		defer o.tasks.Done()
71
-		Tail(src, n, dst)
72
-	}()
73
-	return nil
74
-}
75
-
76
-// AddString starts a new goroutine which will read all subsequent data written to the output,
77
-// line by line, and store the last line into `dst`.
78
-func (o *Output) AddString(dst *string) error {
79
-	src, err := o.AddPipe()
80
-	if err != nil {
81
-		return err
82
-	}
83
-	o.tasks.Add(1)
84
-	go func() {
85
-		defer o.tasks.Done()
86
-		lines := make([]string, 0, 1)
87
-		Tail(src, 1, &lines)
88
-		if len(lines) == 0 {
89
-			*dst = ""
90
-		} else {
91
-			*dst = lines[0]
92
-		}
93
-	}()
94
-	return nil
95
-}
96
-
97 61
 // Write writes the same data to all registered destinations.
98 62
 // This method is thread-safe.
99 63
 func (o *Output) Write(p []byte) (n int, err error) {
... ...
@@ -174,26 +159,6 @@ func (i *Input) Add(src io.Reader) error {
174 174
 	return nil
175 175
 }
176 176
 
177
-// Tail reads from `src` line per line, and returns the last `n` lines as an array.
178
-// A ring buffer is used to only store `n` lines at any time.
179
-func Tail(src io.Reader, n int, dst *[]string) {
180
-	scanner := bufio.NewScanner(src)
181
-	r := ring.New(n)
182
-	for scanner.Scan() {
183
-		if n == 0 {
184
-			continue
185
-		}
186
-		r.Value = scanner.Text()
187
-		r = r.Next()
188
-	}
189
-	r.Do(func(v interface{}) {
190
-		if v == nil {
191
-			return
192
-		}
193
-		*dst = append(*dst, v.(string))
194
-	})
195
-}
196
-
197 177
 // AddEnv starts a new goroutine which will decode all subsequent data
198 178
 // as a stream of json-encoded objects, and point `dst` to the last
199 179
 // decoded object.
... ...
@@ -10,53 +10,6 @@ import (
10 10
 	"testing"
11 11
 )
12 12
 
13
-func TestOutputAddString(t *testing.T) {
14
-	var testInputs = [][2]string{
15
-		{
16
-			"hello, world!",
17
-			"hello, world!",
18
-		},
19
-
20
-		{
21
-			"One\nTwo\nThree",
22
-			"Three",
23
-		},
24
-
25
-		{
26
-			"",
27
-			"",
28
-		},
29
-
30
-		{
31
-			"A line\nThen another nl-terminated line\n",
32
-			"Then another nl-terminated line",
33
-		},
34
-
35
-		{
36
-			"A line followed by an empty line\n\n",
37
-			"",
38
-		},
39
-	}
40
-	for _, testData := range testInputs {
41
-		input := testData[0]
42
-		expectedOutput := testData[1]
43
-		o := NewOutput()
44
-		var output string
45
-		if err := o.AddString(&output); err != nil {
46
-			t.Error(err)
47
-		}
48
-		if n, err := o.Write([]byte(input)); err != nil {
49
-			t.Error(err)
50
-		} else if n != len(input) {
51
-			t.Errorf("Expected %d, got %d", len(input), n)
52
-		}
53
-		o.Close()
54
-		if output != expectedOutput {
55
-			t.Errorf("Last line is not stored as return string.\nInput:   '%s'\nExpected: '%s'\nGot:       '%s'", input, expectedOutput, output)
56
-		}
57
-	}
58
-}
59
-
60 13
 type sentinelWriteCloser struct {
61 14
 	calledWrite bool
62 15
 	calledClose bool
... ...
@@ -145,59 +98,24 @@ func TestOutputAddPipe(t *testing.T) {
145 145
 }
146 146
 
147 147
 func TestTail(t *testing.T) {
148
-	var tests = make(map[string][][]string)
149
-	tests["hello, world!"] = [][]string{
150
-		{},
151
-		{"hello, world!"},
152
-		{"hello, world!"},
153
-		{"hello, world!"},
154
-	}
155
-	tests["One\nTwo\nThree"] = [][]string{
156
-		{},
157
-		{"Three"},
158
-		{"Two", "Three"},
159
-		{"One", "Two", "Three"},
160
-	}
161
-	for input, outputs := range tests {
162
-		for n, expectedOutput := range outputs {
163
-			var output []string
164
-			Tail(strings.NewReader(input), n, &output)
165
-			if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
166
-				t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot     : '%s'", expectedOutput, output)
167
-			}
168
-		}
169
-	}
170
-}
171
-
172
-func TestOutputAddTail(t *testing.T) {
173
-	var tests = make(map[string][][]string)
174
-	tests["hello, world!"] = [][]string{
175
-		{},
176
-		{"hello, world!"},
177
-		{"hello, world!"},
178
-		{"hello, world!"},
148
+	var tests = make(map[string][]string)
149
+	tests["hello, world!"] = []string{
150
+		"",
151
+		"hello, world!",
152
+		"hello, world!",
153
+		"hello, world!",
179 154
 	}
180
-	tests["One\nTwo\nThree"] = [][]string{
181
-		{},
182
-		{"Three"},
183
-		{"Two", "Three"},
184
-		{"One", "Two", "Three"},
155
+	tests["One\nTwo\nThree"] = []string{
156
+		"",
157
+		"Three",
158
+		"Two\nThree",
159
+		"One\nTwo\nThree",
185 160
 	}
186 161
 	for input, outputs := range tests {
187 162
 		for n, expectedOutput := range outputs {
188
-			o := NewOutput()
189
-			var output []string
190
-			if err := o.AddTail(&output, n); err != nil {
191
-				t.Error(err)
192
-			}
193
-			if n, err := o.Write([]byte(input)); err != nil {
194
-				t.Error(err)
195
-			} else if n != len(input) {
196
-				t.Errorf("Expected %d, got %d", len(input), n)
197
-			}
198
-			o.Close()
199
-			if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
200
-				t.Errorf("Tail(%d) returned wrong result.\nExpected: %v\nGot:      %v", n, expectedOutput, output)
163
+			output := Tail(bytes.NewBufferString(input), n)
164
+			if output != expectedOutput {
165
+				t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot     : '%s'", n, expectedOutput, output)
201 166
 			}
202 167
 		}
203 168
 	}
... ...
@@ -3,13 +3,6 @@ package docker
3 3
 import (
4 4
 	"bytes"
5 5
 	"fmt"
6
-	"github.com/dotcloud/docker/daemon"
7
-	"github.com/dotcloud/docker/engine"
8
-	"github.com/dotcloud/docker/image"
9
-	"github.com/dotcloud/docker/nat"
10
-	"github.com/dotcloud/docker/runconfig"
11
-	"github.com/dotcloud/docker/sysinit"
12
-	"github.com/dotcloud/docker/utils"
13 6
 	"io"
14 7
 	"log"
15 8
 	"net"
... ...
@@ -22,6 +15,14 @@ import (
22 22
 	"syscall"
23 23
 	"testing"
24 24
 	"time"
25
+
26
+	"github.com/dotcloud/docker/daemon"
27
+	"github.com/dotcloud/docker/engine"
28
+	"github.com/dotcloud/docker/image"
29
+	"github.com/dotcloud/docker/nat"
30
+	"github.com/dotcloud/docker/runconfig"
31
+	"github.com/dotcloud/docker/sysinit"
32
+	"github.com/dotcloud/docker/utils"
25 33
 )
26 34
 
27 35
 const (
... ...
@@ -421,13 +422,14 @@ func TestGet(t *testing.T) {
421 421
 
422 422
 func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) {
423 423
 	var (
424
-		err     error
425
-		id      string
426
-		strPort string
427
-		eng     = NewTestEngine(t)
428
-		daemon  = mkDaemonFromEngine(eng, t)
429
-		port    = 5554
430
-		p       nat.Port
424
+		err          error
425
+		id           string
426
+		outputBuffer = bytes.NewBuffer(nil)
427
+		strPort      string
428
+		eng          = NewTestEngine(t)
429
+		daemon       = mkDaemonFromEngine(eng, t)
430
+		port         = 5554
431
+		p            nat.Port
431 432
 	)
432 433
 	defer func() {
433 434
 		if err != nil {
... ...
@@ -455,10 +457,11 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem
455 455
 		jobCreate.SetenvList("Cmd", []string{"sh", "-c", cmd})
456 456
 		jobCreate.SetenvList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)})
457 457
 		jobCreate.SetenvJson("ExposedPorts", ep)
458
-		jobCreate.Stdout.AddString(&id)
458
+		jobCreate.Stdout.Add(outputBuffer)
459 459
 		if err := jobCreate.Run(); err != nil {
460 460
 			t.Fatal(err)
461 461
 		}
462
+		id = engine.Tail(outputBuffer, 1)
462 463
 		// FIXME: this relies on the undocumented behavior of daemon.Create
463 464
 		// which will return a nil error AND container if the exposed ports
464 465
 		// are invalid. That behavior should be fixed!
... ...
@@ -720,12 +723,12 @@ func TestContainerNameValidation(t *testing.T) {
720 720
 			t.Fatal(err)
721 721
 		}
722 722
 
723
-		var shortID string
723
+		var outputBuffer = bytes.NewBuffer(nil)
724 724
 		job := eng.Job("create", test.Name)
725 725
 		if err := job.ImportEnv(config); err != nil {
726 726
 			t.Fatal(err)
727 727
 		}
728
-		job.Stdout.AddString(&shortID)
728
+		job.Stdout.Add(outputBuffer)
729 729
 		if err := job.Run(); err != nil {
730 730
 			if !test.Valid {
731 731
 				continue
... ...
@@ -733,7 +736,7 @@ func TestContainerNameValidation(t *testing.T) {
733 733
 			t.Fatal(err)
734 734
 		}
735 735
 
736
-		container := daemon.Get(shortID)
736
+		container := daemon.Get(engine.Tail(outputBuffer, 1))
737 737
 
738 738
 		if container.Name != "/"+test.Name {
739 739
 			t.Fatalf("Expect /%s got %s", test.Name, container.Name)
... ...
@@ -1,12 +1,14 @@
1 1
 package docker
2 2
 
3 3
 import (
4
-	"github.com/dotcloud/docker/engine"
5
-	"github.com/dotcloud/docker/runconfig"
6
-	"github.com/dotcloud/docker/server"
4
+	"bytes"
7 5
 	"strings"
8 6
 	"testing"
9 7
 	"time"
8
+
9
+	"github.com/dotcloud/docker/engine"
10
+	"github.com/dotcloud/docker/runconfig"
11
+	"github.com/dotcloud/docker/server"
10 12
 )
11 13
 
12 14
 func TestCreateNumberHostname(t *testing.T) {
... ...
@@ -70,13 +72,13 @@ func TestMergeConfigOnCommit(t *testing.T) {
70 70
 	job.Setenv("repo", "testrepo")
71 71
 	job.Setenv("tag", "testtag")
72 72
 	job.SetenvJson("config", config)
73
-	var newId string
74
-	job.Stdout.AddString(&newId)
73
+	var outputBuffer = bytes.NewBuffer(nil)
74
+	job.Stdout.Add(outputBuffer)
75 75
 	if err := job.Run(); err != nil {
76 76
 		t.Error(err)
77 77
 	}
78 78
 
79
-	container2, _, _ := mkContainer(runtime, []string{newId}, t)
79
+	container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t)
80 80
 	defer runtime.Destroy(container2)
81 81
 
82 82
 	job = eng.Job("inspect", container1.Name, "container")
... ...
@@ -168,8 +170,6 @@ func TestRestartKillWait(t *testing.T) {
168 168
 
169 169
 	setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() {
170 170
 		job = srv.Eng.Job("wait", outs.Data[0].Get("Id"))
171
-		var statusStr string
172
-		job.Stdout.AddString(&statusStr)
173 171
 		if err := job.Run(); err != nil {
174 172
 			t.Fatal(err)
175 173
 		}
... ...
@@ -266,8 +266,6 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) {
266 266
 	job.Setenv("Memory", "524287")
267 267
 	job.Setenv("CpuShares", "1000")
268 268
 	job.SetenvList("Cmd", []string{"/bin/cat"})
269
-	var id string
270
-	job.Stdout.AddString(&id)
271 269
 	if err := job.Run(); err == nil {
272 270
 		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
273 271
 	}
... ...
@@ -302,13 +300,13 @@ func TestRmi(t *testing.T) {
302 302
 
303 303
 	job = eng.Job("commit", containerID)
304 304
 	job.Setenv("repo", "test")
305
-	var imageID string
306
-	job.Stdout.AddString(&imageID)
305
+	var outputBuffer = bytes.NewBuffer(nil)
306
+	job.Stdout.Add(outputBuffer)
307 307
 	if err := job.Run(); err != nil {
308 308
 		t.Fatal(err)
309 309
 	}
310 310
 
311
-	if err := eng.Job("tag", imageID, "test", "0.1").Run(); err != nil {
311
+	if err := eng.Job("tag", engine.Tail(outputBuffer, 1), "test", "0.1").Run(); err != nil {
312 312
 		t.Fatal(err)
313 313
 	}
314 314
 
... ...
@@ -339,7 +337,7 @@ func TestRmi(t *testing.T) {
339 339
 		t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len())
340 340
 	}
341 341
 
342
-	if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false, false); err != nil {
342
+	if err = srv.DeleteImage(engine.Tail(outputBuffer, 1), engine.NewTable("", 0), true, false, false); err != nil {
343 343
 		t.Fatal(err)
344 344
 	}
345 345
 
... ...
@@ -3,7 +3,6 @@ package docker
3 3
 import (
4 4
 	"bytes"
5 5
 	"fmt"
6
-	"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
7 6
 	"io"
8 7
 	"io/ioutil"
9 8
 	"net/http"
... ...
@@ -14,6 +13,8 @@ import (
14 14
 	"testing"
15 15
 	"time"
16 16
 
17
+	"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
18
+
17 19
 	"github.com/dotcloud/docker/builtins"
18 20
 	"github.com/dotcloud/docker/daemon"
19 21
 	"github.com/dotcloud/docker/engine"
... ...
@@ -42,11 +43,12 @@ func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f ut
42 42
 	if err := job.ImportEnv(config); err != nil {
43 43
 		f.Fatal(err)
44 44
 	}
45
-	job.Stdout.AddString(&shortId)
45
+	var outputBuffer = bytes.NewBuffer(nil)
46
+	job.Stdout.Add(outputBuffer)
46 47
 	if err := job.Run(); err != nil {
47 48
 		f.Fatal(err)
48 49
 	}
49
-	return
50
+	return engine.Tail(outputBuffer, 1)
50 51
 }
51 52
 
52 53
 func createTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler) (shortId string) {