8015: Fix tests
[arvados.git] / services / crunch-run / crunchrun_test.go
1 package main
2
3 import (
4         "bytes"
5         "crypto/md5"
6         "encoding/json"
7         "errors"
8         "fmt"
9         "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
10         "git.curoverse.com/arvados.git/sdk/go/keepclient"
11         "git.curoverse.com/arvados.git/sdk/go/manifest"
12         "github.com/curoverse/dockerclient"
13         . "gopkg.in/check.v1"
14         "io"
15         "io/ioutil"
16         "strings"
17         "syscall"
18         "testing"
19         "time"
20 )
21
22 // Gocheck boilerplate
23 func TestCrunchExec(t *testing.T) {
24         TestingT(t)
25 }
26
27 type TestSuite struct{}
28
29 // Gocheck boilerplate
30 var _ = Suite(&TestSuite{})
31
32 type ArvTestClient struct {
33         Total   int64
34         Calls   int
35         Content arvadosclient.Dict
36         ContainerRecord
37         Logs          map[string]*bytes.Buffer
38         WasSetRunning bool
39 }
40
41 type KeepTestClient struct {
42         Called  bool
43         Content []byte
44 }
45
46 var hwManifest = ". 82ab40c24fc8df01798e57ba66795bb1+841216+Aa124ac75e5168396c73c0a18eda641a4f41791c0@569fa8c3 0:841216:9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7.tar\n"
47 var hwPDH = "a45557269dcb65a6b78f9ac061c0850b+120"
48 var hwImageId = "9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7"
49
50 var otherManifest = ". 68a84f561b1d1708c6baff5e019a9ab3+46+Ae5d0af96944a3690becb1decdf60cc1c937f556d@5693216f 0:46:md5sum.txt\n"
51 var otherPDH = "a3e8f74c6f101eae01fa08bfb4e49b3a+54"
52
53 type TestDockerClient struct {
54         imageLoaded  string
55         stdoutReader io.ReadCloser
56         stderrReader io.ReadCloser
57         stdoutWriter io.WriteCloser
58         stderrWriter io.WriteCloser
59         fn           func(t *TestDockerClient)
60         finish       chan dockerclient.WaitResult
61         stop         chan bool
62         cwd          string
63         env          []string
64 }
65
66 func NewTestDockerClient() *TestDockerClient {
67         t := &TestDockerClient{}
68         t.stdoutReader, t.stdoutWriter = io.Pipe()
69         t.stderrReader, t.stderrWriter = io.Pipe()
70         t.finish = make(chan dockerclient.WaitResult)
71         t.stop = make(chan bool)
72         t.cwd = "/"
73         return t
74 }
75
76 func (t *TestDockerClient) StopContainer(id string, timeout int) error {
77         t.stop <- true
78         return nil
79 }
80
81 func (t *TestDockerClient) InspectImage(id string) (*dockerclient.ImageInfo, error) {
82         if t.imageLoaded == id {
83                 return &dockerclient.ImageInfo{}, nil
84         } else {
85                 return nil, errors.New("")
86         }
87 }
88
89 func (t *TestDockerClient) LoadImage(reader io.Reader) error {
90         _, err := io.Copy(ioutil.Discard, reader)
91         if err != nil {
92                 return err
93         } else {
94                 t.imageLoaded = hwImageId
95                 return nil
96         }
97 }
98
99 func (t *TestDockerClient) CreateContainer(config *dockerclient.ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (string, error) {
100         if config.WorkingDir != "" {
101                 t.cwd = config.WorkingDir
102         }
103         t.env = config.Env
104         return "abcde", nil
105 }
106
107 func (t *TestDockerClient) StartContainer(id string, config *dockerclient.HostConfig) error {
108         if id == "abcde" {
109                 go t.fn(t)
110                 return nil
111         } else {
112                 return errors.New("Invalid container id")
113         }
114 }
115
116 func (t *TestDockerClient) ContainerLogs(id string, options *dockerclient.LogOptions) (io.ReadCloser, error) {
117         if options.Stdout {
118                 return t.stdoutReader, nil
119         }
120         if options.Stderr {
121                 return t.stderrReader, nil
122         }
123         return nil, nil
124 }
125
126 func (t *TestDockerClient) Wait(id string) <-chan dockerclient.WaitResult {
127         return t.finish
128 }
129
130 func (*TestDockerClient) RemoveImage(name string, force bool) ([]*dockerclient.ImageDelete, error) {
131         return nil, nil
132 }
133
134 func (this *ArvTestClient) Create(resourceType string,
135         parameters arvadosclient.Dict,
136         output interface{}) error {
137
138         this.Calls += 1
139         this.Content = parameters
140
141         if resourceType == "logs" {
142                 et := parameters["log"].(arvadosclient.Dict)["event_type"].(string)
143                 if this.Logs == nil {
144                         this.Logs = make(map[string]*bytes.Buffer)
145                 }
146                 if this.Logs[et] == nil {
147                         this.Logs[et] = &bytes.Buffer{}
148                 }
149                 this.Logs[et].Write([]byte(parameters["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]))
150         }
151
152         if resourceType == "collections" && output != nil {
153                 mt := parameters["collection"].(arvadosclient.Dict)["manifest_text"].(string)
154                 outmap := output.(*CollectionRecord)
155                 outmap.PortableDataHash = fmt.Sprintf("%x+%d", md5.Sum([]byte(mt)), len(mt))
156         }
157
158         return nil
159 }
160
161 func (this *ArvTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
162         if resourceType == "collections" {
163                 if uuid == hwPDH {
164                         output.(*CollectionRecord).ManifestText = hwManifest
165                 } else if uuid == otherPDH {
166                         output.(*CollectionRecord).ManifestText = otherManifest
167                 }
168         }
169         if resourceType == "containers" {
170                 (*output.(*ContainerRecord)) = this.ContainerRecord
171         }
172         return nil
173 }
174
175 func (this *ArvTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
176
177         this.Content = parameters
178         if resourceType == "containers" {
179                 if parameters["container"].(arvadosclient.Dict)["state"] == "Running" {
180                         this.WasSetRunning = true
181                 }
182
183         }
184         return nil
185 }
186
187 func (this *KeepTestClient) PutHB(hash string, buf []byte) (string, int, error) {
188         this.Content = buf
189         return fmt.Sprintf("%s+%d", hash, len(buf)), len(buf), nil
190 }
191
192 type FileWrapper struct {
193         io.ReadCloser
194         len uint64
195 }
196
197 func (this FileWrapper) Len() uint64 {
198         return this.len
199 }
200
201 func (this *KeepTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
202         if filename == hwImageId+".tar" {
203                 rdr := ioutil.NopCloser(&bytes.Buffer{})
204                 this.Called = true
205                 return FileWrapper{rdr, 1321984}, nil
206         }
207         return nil, nil
208 }
209
210 func (s *TestSuite) TestLoadImage(c *C) {
211         kc := &KeepTestClient{}
212         docker := NewTestDockerClient()
213         cr := NewContainerRunner(&ArvTestClient{}, kc, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
214
215         _, err := cr.Docker.RemoveImage(hwImageId, true)
216
217         _, err = cr.Docker.InspectImage(hwImageId)
218         c.Check(err, NotNil)
219
220         cr.ContainerRecord.ContainerImage = hwPDH
221
222         // (1) Test loading image from keep
223         c.Check(kc.Called, Equals, false)
224         c.Check(cr.ContainerConfig.Image, Equals, "")
225
226         err = cr.LoadImage()
227
228         c.Check(err, IsNil)
229         defer func() {
230                 cr.Docker.RemoveImage(hwImageId, true)
231         }()
232
233         c.Check(kc.Called, Equals, true)
234         c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
235
236         _, err = cr.Docker.InspectImage(hwImageId)
237         c.Check(err, IsNil)
238
239         // (2) Test using image that's already loaded
240         kc.Called = false
241         cr.ContainerConfig.Image = ""
242
243         err = cr.LoadImage()
244         c.Check(err, IsNil)
245         c.Check(kc.Called, Equals, false)
246         c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
247
248 }
249
250 type ArvErrorTestClient struct{}
251 type KeepErrorTestClient struct{}
252 type KeepReadErrorTestClient struct{}
253
254 func (this ArvErrorTestClient) Create(resourceType string,
255         parameters arvadosclient.Dict,
256         output interface{}) error {
257         return nil
258 }
259
260 func (this ArvErrorTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
261         return errors.New("ArvError")
262 }
263
264 func (this ArvErrorTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
265         return nil
266 }
267
268 func (this KeepErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
269         return "", 0, errors.New("KeepError")
270 }
271
272 func (this KeepErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
273         return nil, errors.New("KeepError")
274 }
275
276 func (this KeepReadErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
277         return "", 0, nil
278 }
279
280 type ErrorReader struct{}
281
282 func (this ErrorReader) Read(p []byte) (n int, err error) {
283         return 0, errors.New("ErrorReader")
284 }
285
286 func (this ErrorReader) Close() error {
287         return nil
288 }
289
290 func (this ErrorReader) Len() uint64 {
291         return 0
292 }
293
294 func (this KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
295         return ErrorReader{}, nil
296 }
297
298 func (s *TestSuite) TestLoadImageArvError(c *C) {
299         // (1) Arvados error
300         cr := NewContainerRunner(ArvErrorTestClient{}, &KeepTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
301         cr.ContainerRecord.ContainerImage = hwPDH
302
303         err := cr.LoadImage()
304         c.Check(err.Error(), Equals, "While getting container image collection: ArvError")
305 }
306
307 func (s *TestSuite) TestLoadImageKeepError(c *C) {
308         // (2) Keep error
309         docker := NewTestDockerClient()
310         cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
311         cr.ContainerRecord.ContainerImage = hwPDH
312
313         err := cr.LoadImage()
314         c.Check(err.Error(), Equals, "While creating ManifestFileReader for container image: KeepError")
315 }
316
317 func (s *TestSuite) TestLoadImageCollectionError(c *C) {
318         // (3) Collection doesn't contain image
319         cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
320         cr.ContainerRecord.ContainerImage = otherPDH
321
322         err := cr.LoadImage()
323         c.Check(err.Error(), Equals, "First file in the container image collection does not end in .tar")
324 }
325
326 func (s *TestSuite) TestLoadImageKeepReadError(c *C) {
327         // (4) Collection doesn't contain image
328         docker := NewTestDockerClient()
329         cr := NewContainerRunner(&ArvTestClient{}, KeepReadErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
330         cr.ContainerRecord.ContainerImage = hwPDH
331
332         err := cr.LoadImage()
333         c.Check(err, NotNil)
334 }
335
336 type ClosableBuffer struct {
337         bytes.Buffer
338 }
339
340 type TestLogs struct {
341         Stdout ClosableBuffer
342         Stderr ClosableBuffer
343 }
344
345 func (this *ClosableBuffer) Close() error {
346         return nil
347 }
348
349 func (this *TestLogs) NewTestLoggingWriter(logstr string) io.WriteCloser {
350         if logstr == "stdout" {
351                 return &this.Stdout
352         }
353         if logstr == "stderr" {
354                 return &this.Stderr
355         }
356         return nil
357 }
358
359 func (s *TestSuite) TestRunContainer(c *C) {
360         docker := NewTestDockerClient()
361         docker.fn = func(t *TestDockerClient) {
362                 t.stdoutWriter.Write([]byte("Hello world\n"))
363                 t.stdoutWriter.Close()
364                 t.stderrWriter.Close()
365                 t.finish <- dockerclient.WaitResult{}
366         }
367         cr := NewContainerRunner(&ArvTestClient{}, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
368
369         var logs TestLogs
370         cr.NewLogWriter = logs.NewTestLoggingWriter
371         cr.ContainerRecord.ContainerImage = hwPDH
372         cr.ContainerRecord.Command = []string{"./hw"}
373         err := cr.LoadImage()
374         c.Check(err, IsNil)
375
376         err = cr.StartContainer()
377         c.Check(err, IsNil)
378
379         err = cr.AttachLogs()
380         c.Check(err, IsNil)
381
382         err = cr.WaitFinish()
383         c.Check(err, IsNil)
384
385         c.Check(strings.HasSuffix(logs.Stdout.String(), "Hello world\n"), Equals, true)
386         c.Check(logs.Stderr.String(), Equals, "")
387 }
388
389 func (s *TestSuite) TestCommitLogs(c *C) {
390         api := &ArvTestClient{}
391         kc := &KeepTestClient{}
392         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
393         cr.CrunchLog.Timestamper = (&TestTimestamper{}).Timestamp
394
395         cr.CrunchLog.Print("Hello world!")
396         cr.CrunchLog.Print("Goodbye")
397         cr.finalState = "Complete"
398
399         err := cr.CommitLogs()
400         c.Check(err, IsNil)
401
402         c.Check(api.Content["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
403         c.Check(api.Content["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
404         c.Check(*cr.LogsPDH, Equals, "63da7bdacf08c40f604daad80c261e9a+60")
405 }
406
407 func (s *TestSuite) TestUpdateContainerRecordRunning(c *C) {
408         api := &ArvTestClient{}
409         kc := &KeepTestClient{}
410         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
411
412         err := cr.UpdateContainerRecordRunning()
413         c.Check(err, IsNil)
414
415         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Running")
416 }
417
418 func (s *TestSuite) TestUpdateContainerRecordComplete(c *C) {
419         api := &ArvTestClient{}
420         kc := &KeepTestClient{}
421         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
422
423         cr.LogsPDH = new(string)
424         *cr.LogsPDH = "d3a229d2fe3690c2c3e75a71a153c6a3+60"
425
426         cr.ExitCode = new(int)
427         *cr.ExitCode = 42
428         cr.finalState = "Complete"
429
430         err := cr.UpdateContainerRecordComplete()
431         c.Check(err, IsNil)
432
433         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
434         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
435         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
436 }
437
438 func (s *TestSuite) TestUpdateContainerRecordCancelled(c *C) {
439         api := &ArvTestClient{}
440         kc := &KeepTestClient{}
441         cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
442         cr.Cancelled = true
443         cr.finalState = "Cancelled"
444
445         err := cr.UpdateContainerRecordComplete()
446         c.Check(err, IsNil)
447
448         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], IsNil)
449         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], IsNil)
450         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
451 }
452
453 // Used by the TestFullRun*() test below to DRY up boilerplate setup to do full
454 // dress rehersal of the Run() function, starting from a JSON container record.
455 func FullRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner) {
456         rec := ContainerRecord{}
457         err := json.NewDecoder(strings.NewReader(record)).Decode(&rec)
458         c.Check(err, IsNil)
459
460         docker := NewTestDockerClient()
461         docker.fn = fn
462         docker.RemoveImage(hwImageId, true)
463
464         api = &ArvTestClient{ContainerRecord: rec}
465         cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
466
467         err = cr.Run()
468         c.Check(err, IsNil)
469         c.Check(api.WasSetRunning, Equals, true)
470
471         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
472
473         if err != nil {
474                 for k, v := range api.Logs {
475                         c.Log(k)
476                         c.Log(v.String())
477                 }
478         }
479
480         return
481 }
482
483 func (s *TestSuite) TestFullRunHello(c *C) {
484         api, _ := FullRunHelper(c, `{
485     "command": ["echo", "hello world"],
486     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
487     "cwd": ".",
488     "environment": {},
489     "mounts": {"/tmp": {"kind": "tmp"} },
490     "output_path": "/tmp",
491     "priority": 1,
492     "runtime_constraints": {}
493 }`, func(t *TestDockerClient) {
494                 t.stdoutWriter.Write([]byte("hello world\n"))
495                 t.stdoutWriter.Close()
496                 t.stderrWriter.Close()
497                 t.finish <- dockerclient.WaitResult{}
498         })
499
500         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
501         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
502
503         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello world\n"), Equals, true)
504
505 }
506
507 func (s *TestSuite) TestFullRunStderr(c *C) {
508         api, _ := FullRunHelper(c, `{
509     "command": ["/bin/sh", "-c", "echo hello ; echo world 1>&2 ; exit 1"],
510     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
511     "cwd": ".",
512     "environment": {},
513     "mounts": {"/tmp": {"kind": "tmp"} },
514     "output_path": "/tmp",
515     "priority": 1,
516     "runtime_constraints": {}
517 }`, func(t *TestDockerClient) {
518                 t.stdoutWriter.Write([]byte("hello\n"))
519                 t.stderrWriter.Write([]byte("world\n"))
520                 t.stdoutWriter.Close()
521                 t.stderrWriter.Close()
522                 t.finish <- dockerclient.WaitResult{ExitCode: 1}
523         })
524
525         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
526         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
527         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
528
529         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello\n"), Equals, true)
530         c.Check(strings.HasSuffix(api.Logs["stderr"].String(), "world\n"), Equals, true)
531 }
532
533 func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
534         api, _ := FullRunHelper(c, `{
535     "command": ["pwd"],
536     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
537     "cwd": ".",
538     "environment": {},
539     "mounts": {"/tmp": {"kind": "tmp"} },
540     "output_path": "/tmp",
541     "priority": 1,
542     "runtime_constraints": {}
543 }`, func(t *TestDockerClient) {
544                 t.stdoutWriter.Write([]byte(t.cwd + "\n"))
545                 t.stdoutWriter.Close()
546                 t.stderrWriter.Close()
547                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
548         })
549
550         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
551         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
552
553         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/\n"), Equals, true)
554 }
555
556 func (s *TestSuite) TestFullRunSetCwd(c *C) {
557         api, _ := FullRunHelper(c, `{
558     "command": ["pwd"],
559     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
560     "cwd": "/bin",
561     "environment": {},
562     "mounts": {"/tmp": {"kind": "tmp"} },
563     "output_path": "/tmp",
564     "priority": 1,
565     "runtime_constraints": {}
566 }`, func(t *TestDockerClient) {
567                 t.stdoutWriter.Write([]byte(t.cwd + "\n"))
568                 t.stdoutWriter.Close()
569                 t.stderrWriter.Close()
570                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
571         })
572
573         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
574         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
575
576         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/bin\n"), Equals, true)
577 }
578
579 func (s *TestSuite) TestCancel(c *C) {
580         record := `{
581     "command": ["/bin/sh", "-c", "echo foo && sleep 30 && echo bar"],
582     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
583     "cwd": ".",
584     "environment": {},
585     "mounts": {"/tmp": {"kind": "tmp"} },
586     "output_path": "/tmp",
587     "priority": 1,
588     "runtime_constraints": {}
589 }`
590
591         rec := ContainerRecord{}
592         err := json.NewDecoder(strings.NewReader(record)).Decode(&rec)
593         c.Check(err, IsNil)
594
595         docker := NewTestDockerClient()
596         docker.fn = func(t *TestDockerClient) {
597                 <-t.stop
598                 t.stdoutWriter.Write([]byte("foo\n"))
599                 t.stdoutWriter.Close()
600                 t.stderrWriter.Close()
601                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
602         }
603         docker.RemoveImage(hwImageId, true)
604
605         api := &ArvTestClient{ContainerRecord: rec}
606         cr := NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
607
608         go func() {
609                 for cr.ContainerID == "" {
610                         time.Sleep(1 * time.Second)
611                 }
612                 cr.SigChan <- syscall.SIGINT
613         }()
614
615         err = cr.Run()
616
617         c.Check(err, IsNil)
618
619         c.Check(api.Content["container"].(arvadosclient.Dict)["log"], NotNil)
620
621         if err != nil {
622                 for k, v := range api.Logs {
623                         c.Log(k)
624                         c.Log(v.String())
625                 }
626         }
627
628         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
629
630         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "foo\n"), Equals, true)
631
632 }
633
634 func (s *TestSuite) TestFullRunSetEnv(c *C) {
635         api, _ := FullRunHelper(c, `{
636     "command": ["/bin/sh", "-c", "echo $FROBIZ"],
637     "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
638     "cwd": "/bin",
639     "environment": {"FROBIZ": "bilbo"},
640     "mounts": {"/tmp": {"kind": "tmp"} },
641     "output_path": "/tmp",
642     "priority": 1,
643     "runtime_constraints": {}
644 }`, func(t *TestDockerClient) {
645                 t.stdoutWriter.Write([]byte(t.env[0][7:] + "\n"))
646                 t.stdoutWriter.Close()
647                 t.stderrWriter.Close()
648                 t.finish <- dockerclient.WaitResult{ExitCode: 0}
649         })
650
651         c.Check(api.Content["container"].(arvadosclient.Dict)["exit_code"], Equals, 0)
652         c.Check(api.Content["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
653
654         c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "bilbo\n"), Equals, true)
655 }