9 "git.curoverse.com/arvados.git/sdk/go/arvados"
10 "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
11 "git.curoverse.com/arvados.git/sdk/go/keepclient"
12 "git.curoverse.com/arvados.git/sdk/go/manifest"
13 "github.com/curoverse/dockerclient"
28 // Gocheck boilerplate
29 func TestCrunchExec(t *testing.T) {
33 type TestSuite struct{}
35 // Gocheck boilerplate
36 var _ = Suite(&TestSuite{})
38 type ArvTestClient struct {
41 Content []arvadosclient.Dict
43 Logs map[string]*bytes.Buffer
48 type KeepTestClient struct {
53 var hwManifest = ". 82ab40c24fc8df01798e57ba66795bb1+841216+Aa124ac75e5168396c73c0a18eda641a4f41791c0@569fa8c3 0:841216:9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7.tar\n"
54 var hwPDH = "a45557269dcb65a6b78f9ac061c0850b+120"
55 var hwImageId = "9c31ee32b3d15268a0754e8edc74d4f815ee014b693bc5109058e431dd5caea7"
57 var otherManifest = ". 68a84f561b1d1708c6baff5e019a9ab3+46+Ae5d0af96944a3690becb1decdf60cc1c937f556d@5693216f 0:46:md5sum.txt\n"
58 var otherPDH = "a3e8f74c6f101eae01fa08bfb4e49b3a+54"
60 var fakeAuthUUID = "zzzzz-gj3su-55pqoyepgi2glem"
61 var fakeAuthToken = "a3ltuwzqcu2u4sc0q7yhpc2w7s00fdcqecg5d6e0u3pfohmbjt"
63 type TestDockerClient struct {
65 logReader io.ReadCloser
66 logWriter io.WriteCloser
67 fn func(t *TestDockerClient)
68 finish chan dockerclient.WaitResult
74 func NewTestDockerClient() *TestDockerClient {
75 t := &TestDockerClient{}
76 t.logReader, t.logWriter = io.Pipe()
77 t.finish = make(chan dockerclient.WaitResult)
78 t.stop = make(chan bool)
83 func (t *TestDockerClient) StopContainer(id string, timeout int) error {
88 func (t *TestDockerClient) InspectImage(id string) (*dockerclient.ImageInfo, error) {
89 if t.imageLoaded == id {
90 return &dockerclient.ImageInfo{}, nil
92 return nil, errors.New("")
96 func (t *TestDockerClient) LoadImage(reader io.Reader) error {
97 _, err := io.Copy(ioutil.Discard, reader)
101 t.imageLoaded = hwImageId
106 func (t *TestDockerClient) CreateContainer(config *dockerclient.ContainerConfig, name string, authConfig *dockerclient.AuthConfig) (string, error) {
107 if config.WorkingDir != "" {
108 t.cwd = config.WorkingDir
114 func (t *TestDockerClient) StartContainer(id string, config *dockerclient.HostConfig) error {
119 return errors.New("Invalid container id")
123 func (t *TestDockerClient) AttachContainer(id string, options *dockerclient.AttachOptions) (io.ReadCloser, error) {
124 return t.logReader, nil
127 func (t *TestDockerClient) Wait(id string) <-chan dockerclient.WaitResult {
131 func (*TestDockerClient) RemoveImage(name string, force bool) ([]*dockerclient.ImageDelete, error) {
135 func (client *ArvTestClient) Create(resourceType string,
136 parameters arvadosclient.Dict,
137 output interface{}) error {
140 defer client.Mutex.Unlock()
143 client.Content = append(client.Content, parameters)
145 if resourceType == "logs" {
146 et := parameters["log"].(arvadosclient.Dict)["event_type"].(string)
147 if client.Logs == nil {
148 client.Logs = make(map[string]*bytes.Buffer)
150 if client.Logs[et] == nil {
151 client.Logs[et] = &bytes.Buffer{}
153 client.Logs[et].Write([]byte(parameters["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]))
156 if resourceType == "collections" && output != nil {
157 mt := parameters["collection"].(arvadosclient.Dict)["manifest_text"].(string)
158 outmap := output.(*arvados.Collection)
159 outmap.PortableDataHash = fmt.Sprintf("%x+%d", md5.Sum([]byte(mt)), len(mt))
165 func (client *ArvTestClient) Call(method, resourceType, uuid, action string, parameters arvadosclient.Dict, output interface{}) error {
167 case method == "GET" && resourceType == "containers" && action == "auth":
168 return json.Unmarshal([]byte(`{
169 "kind": "arvados#api_client_authorization",
170 "uuid": "`+fakeAuthUUID+`",
171 "api_token": "`+fakeAuthToken+`"
174 return fmt.Errorf("Not found")
178 func (client *ArvTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
179 if resourceType == "collections" {
181 output.(*arvados.Collection).ManifestText = hwManifest
182 } else if uuid == otherPDH {
183 output.(*arvados.Collection).ManifestText = otherManifest
186 if resourceType == "containers" {
187 (*output.(*arvados.Container)) = client.Container
192 func (client *ArvTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
194 defer client.Mutex.Unlock()
196 client.Content = append(client.Content, parameters)
197 if resourceType == "containers" {
198 if parameters["container"].(arvadosclient.Dict)["state"] == "Running" {
199 client.WasSetRunning = true
205 var discoveryMap = map[string]interface{}{"defaultTrashLifetime": float64(1209600)}
207 func (client *ArvTestClient) Discovery(key string) (interface{}, error) {
208 return discoveryMap[key], nil
211 // CalledWith returns the parameters from the first API call whose
212 // parameters match jpath/string. E.g., CalledWith(c, "foo.bar",
213 // "baz") returns parameters with parameters["foo"]["bar"]=="baz". If
214 // no call matches, it returns nil.
215 func (client *ArvTestClient) CalledWith(jpath string, expect interface{}) arvadosclient.Dict {
217 for _, content := range client.Content {
218 var v interface{} = content
219 for _, k := range strings.Split(jpath, ".") {
220 if dict, ok := v.(arvadosclient.Dict); !ok {
233 func (client *KeepTestClient) PutHB(hash string, buf []byte) (string, int, error) {
235 return fmt.Sprintf("%s+%d", hash, len(buf)), len(buf), nil
238 type FileWrapper struct {
243 func (fw FileWrapper) Len() uint64 {
247 func (client *KeepTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
248 if filename == hwImageId+".tar" {
249 rdr := ioutil.NopCloser(&bytes.Buffer{})
251 return FileWrapper{rdr, 1321984}, nil
256 func (s *TestSuite) TestLoadImage(c *C) {
257 kc := &KeepTestClient{}
258 docker := NewTestDockerClient()
259 cr := NewContainerRunner(&ArvTestClient{}, kc, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
261 _, err := cr.Docker.RemoveImage(hwImageId, true)
263 _, err = cr.Docker.InspectImage(hwImageId)
266 cr.Container.ContainerImage = hwPDH
268 // (1) Test loading image from keep
269 c.Check(kc.Called, Equals, false)
270 c.Check(cr.ContainerConfig.Image, Equals, "")
276 cr.Docker.RemoveImage(hwImageId, true)
279 c.Check(kc.Called, Equals, true)
280 c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
282 _, err = cr.Docker.InspectImage(hwImageId)
285 // (2) Test using image that's already loaded
287 cr.ContainerConfig.Image = ""
291 c.Check(kc.Called, Equals, false)
292 c.Check(cr.ContainerConfig.Image, Equals, hwImageId)
296 type ArvErrorTestClient struct{}
298 func (ArvErrorTestClient) Create(resourceType string,
299 parameters arvadosclient.Dict,
300 output interface{}) error {
304 func (ArvErrorTestClient) Call(method, resourceType, uuid, action string, parameters arvadosclient.Dict, output interface{}) error {
305 return errors.New("ArvError")
308 func (ArvErrorTestClient) Get(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) error {
309 return errors.New("ArvError")
312 func (ArvErrorTestClient) Update(resourceType string, uuid string, parameters arvadosclient.Dict, output interface{}) (err error) {
316 func (ArvErrorTestClient) Discovery(key string) (interface{}, error) {
317 return discoveryMap[key], nil
320 type KeepErrorTestClient struct{}
322 func (KeepErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
323 return "", 0, errors.New("KeepError")
326 func (KeepErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
327 return nil, errors.New("KeepError")
330 type KeepReadErrorTestClient struct{}
332 func (KeepReadErrorTestClient) PutHB(hash string, buf []byte) (string, int, error) {
336 type ErrorReader struct{}
338 func (ErrorReader) Read(p []byte) (n int, err error) {
339 return 0, errors.New("ErrorReader")
342 func (ErrorReader) Close() error {
346 func (ErrorReader) Len() uint64 {
350 func (KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (keepclient.ReadCloserWithLen, error) {
351 return ErrorReader{}, nil
354 func (s *TestSuite) TestLoadImageArvError(c *C) {
356 cr := NewContainerRunner(ArvErrorTestClient{}, &KeepTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
357 cr.Container.ContainerImage = hwPDH
359 err := cr.LoadImage()
360 c.Check(err.Error(), Equals, "While getting container image collection: ArvError")
363 func (s *TestSuite) TestLoadImageKeepError(c *C) {
365 docker := NewTestDockerClient()
366 cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
367 cr.Container.ContainerImage = hwPDH
369 err := cr.LoadImage()
370 c.Check(err.Error(), Equals, "While creating ManifestFileReader for container image: KeepError")
373 func (s *TestSuite) TestLoadImageCollectionError(c *C) {
374 // (3) Collection doesn't contain image
375 cr := NewContainerRunner(&ArvTestClient{}, KeepErrorTestClient{}, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
376 cr.Container.ContainerImage = otherPDH
378 err := cr.LoadImage()
379 c.Check(err.Error(), Equals, "First file in the container image collection does not end in .tar")
382 func (s *TestSuite) TestLoadImageKeepReadError(c *C) {
383 // (4) Collection doesn't contain image
384 docker := NewTestDockerClient()
385 cr := NewContainerRunner(&ArvTestClient{}, KeepReadErrorTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
386 cr.Container.ContainerImage = hwPDH
388 err := cr.LoadImage()
392 type ClosableBuffer struct {
396 func (*ClosableBuffer) Close() error {
400 type TestLogs struct {
401 Stdout ClosableBuffer
402 Stderr ClosableBuffer
405 func (tl *TestLogs) NewTestLoggingWriter(logstr string) io.WriteCloser {
406 if logstr == "stdout" {
409 if logstr == "stderr" {
415 func dockerLog(fd byte, msg string) []byte {
417 header := make([]byte, 8+len(by))
419 header[7] = byte(len(by))
424 func (s *TestSuite) TestRunContainer(c *C) {
425 docker := NewTestDockerClient()
426 docker.fn = func(t *TestDockerClient) {
427 t.logWriter.Write(dockerLog(1, "Hello world\n"))
429 t.finish <- dockerclient.WaitResult{}
431 cr := NewContainerRunner(&ArvTestClient{}, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
434 cr.NewLogWriter = logs.NewTestLoggingWriter
435 cr.Container.ContainerImage = hwPDH
436 cr.Container.Command = []string{"./hw"}
437 err := cr.LoadImage()
440 err = cr.CreateContainer()
443 err = cr.StartContainer()
446 err = cr.WaitFinish()
449 c.Check(strings.HasSuffix(logs.Stdout.String(), "Hello world\n"), Equals, true)
450 c.Check(logs.Stderr.String(), Equals, "")
453 func (s *TestSuite) TestCommitLogs(c *C) {
454 api := &ArvTestClient{}
455 kc := &KeepTestClient{}
456 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
457 cr.CrunchLog.Timestamper = (&TestTimestamper{}).Timestamp
459 cr.CrunchLog.Print("Hello world!")
460 cr.CrunchLog.Print("Goodbye")
461 cr.finalState = "Complete"
463 err := cr.CommitLogs()
466 c.Check(api.Calls, Equals, 2)
467 c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["name"], Equals, "logs for zzzzz-zzzzz-zzzzzzzzzzzzzzz")
468 c.Check(api.Content[1]["collection"].(arvadosclient.Dict)["manifest_text"], Equals, ". 744b2e4553123b02fa7b452ec5c18993+123 0:123:crunch-run.txt\n")
469 c.Check(*cr.LogsPDH, Equals, "63da7bdacf08c40f604daad80c261e9a+60")
472 func (s *TestSuite) TestUpdateContainerRunning(c *C) {
473 api := &ArvTestClient{}
474 kc := &KeepTestClient{}
475 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
477 err := cr.UpdateContainerRunning()
480 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Running")
483 func (s *TestSuite) TestUpdateContainerComplete(c *C) {
484 api := &ArvTestClient{}
485 kc := &KeepTestClient{}
486 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
488 cr.LogsPDH = new(string)
489 *cr.LogsPDH = "d3a229d2fe3690c2c3e75a71a153c6a3+60"
491 cr.ExitCode = new(int)
493 cr.finalState = "Complete"
495 err := cr.UpdateContainerFinal()
498 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], Equals, *cr.LogsPDH)
499 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], Equals, *cr.ExitCode)
500 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Complete")
503 func (s *TestSuite) TestUpdateContainerCancelled(c *C) {
504 api := &ArvTestClient{}
505 kc := &KeepTestClient{}
506 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
508 cr.finalState = "Cancelled"
510 err := cr.UpdateContainerFinal()
513 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["log"], IsNil)
514 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["exit_code"], IsNil)
515 c.Check(api.Content[0]["container"].(arvadosclient.Dict)["state"], Equals, "Cancelled")
518 // Used by the TestFullRun*() test below to DRY up boilerplate setup to do full
519 // dress rehearsal of the Run() function, starting from a JSON container record.
520 func FullRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner) {
521 rec := arvados.Container{}
522 err := json.Unmarshal([]byte(record), &rec)
525 docker := NewTestDockerClient()
527 docker.RemoveImage(hwImageId, true)
529 api = &ArvTestClient{Container: rec}
530 cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
531 cr.statInterval = 100 * time.Millisecond
532 am := &ArvMountCmdLine{}
533 cr.RunArvMount = am.ArvMountTest
537 c.Check(api.WasSetRunning, Equals, true)
539 c.Check(api.Content[api.Calls-1]["container"].(arvadosclient.Dict)["log"], NotNil)
542 for k, v := range api.Logs {
551 func (s *TestSuite) TestFullRunHello(c *C) {
552 api, _ := FullRunHelper(c, `{
553 "command": ["echo", "hello world"],
554 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
557 "mounts": {"/tmp": {"kind": "tmp"} },
558 "output_path": "/tmp",
560 "runtime_constraints": {}
561 }`, func(t *TestDockerClient) {
562 t.logWriter.Write(dockerLog(1, "hello world\n"))
564 t.finish <- dockerclient.WaitResult{}
567 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
568 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
569 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello world\n"), Equals, true)
573 func (s *TestSuite) TestCrunchstat(c *C) {
574 api, _ := FullRunHelper(c, `{
575 "command": ["sleep", "1"],
576 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
579 "mounts": {"/tmp": {"kind": "tmp"} },
580 "output_path": "/tmp",
582 "runtime_constraints": {}
583 }`, func(t *TestDockerClient) {
584 time.Sleep(time.Second)
586 t.finish <- dockerclient.WaitResult{}
589 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
590 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
592 // We didn't actually start a container, so crunchstat didn't
593 // find accounting files and therefore didn't log any stats.
594 // It should have logged a "can't find accounting files"
595 // message after one poll interval, though, so we can confirm
597 c.Assert(api.Logs["crunchstat"], NotNil)
598 c.Check(api.Logs["crunchstat"].String(), Matches, `(?ms).*cgroup stats files have not appeared after 100ms.*`)
600 // The "files never appeared" log assures us that we called
601 // (*crunchstat.Reporter)Stop(), and that we set it up with
602 // the correct container ID "abcde":
603 c.Check(api.Logs["crunchstat"].String(), Matches, `(?ms).*cgroup stats files never appeared for abcde\n`)
606 func (s *TestSuite) TestFullRunStderr(c *C) {
607 api, _ := FullRunHelper(c, `{
608 "command": ["/bin/sh", "-c", "echo hello ; echo world 1>&2 ; exit 1"],
609 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
612 "mounts": {"/tmp": {"kind": "tmp"} },
613 "output_path": "/tmp",
615 "runtime_constraints": {}
616 }`, func(t *TestDockerClient) {
617 t.logWriter.Write(dockerLog(1, "hello\n"))
618 t.logWriter.Write(dockerLog(2, "world\n"))
620 t.finish <- dockerclient.WaitResult{ExitCode: 1}
623 final := api.CalledWith("container.state", "Complete")
624 c.Assert(final, NotNil)
625 c.Check(final["container"].(arvadosclient.Dict)["exit_code"], Equals, 1)
626 c.Check(final["container"].(arvadosclient.Dict)["log"], NotNil)
628 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "hello\n"), Equals, true)
629 c.Check(strings.HasSuffix(api.Logs["stderr"].String(), "world\n"), Equals, true)
632 func (s *TestSuite) TestFullRunDefaultCwd(c *C) {
633 api, _ := FullRunHelper(c, `{
635 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
638 "mounts": {"/tmp": {"kind": "tmp"} },
639 "output_path": "/tmp",
641 "runtime_constraints": {}
642 }`, func(t *TestDockerClient) {
643 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
645 t.finish <- dockerclient.WaitResult{ExitCode: 0}
648 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
649 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
650 c.Log(api.Logs["stdout"])
651 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/\n"), Equals, true)
654 func (s *TestSuite) TestFullRunSetCwd(c *C) {
655 api, _ := FullRunHelper(c, `{
657 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
660 "mounts": {"/tmp": {"kind": "tmp"} },
661 "output_path": "/tmp",
663 "runtime_constraints": {}
664 }`, func(t *TestDockerClient) {
665 t.logWriter.Write(dockerLog(1, t.cwd+"\n"))
667 t.finish <- dockerclient.WaitResult{ExitCode: 0}
670 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
671 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
672 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "/bin\n"), Equals, true)
675 func (s *TestSuite) TestCancel(c *C) {
677 "command": ["/bin/sh", "-c", "echo foo && sleep 30 && echo bar"],
678 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
681 "mounts": {"/tmp": {"kind": "tmp"} },
682 "output_path": "/tmp",
684 "runtime_constraints": {}
687 rec := arvados.Container{}
688 err := json.Unmarshal([]byte(record), &rec)
691 docker := NewTestDockerClient()
692 docker.fn = func(t *TestDockerClient) {
694 t.logWriter.Write(dockerLog(1, "foo\n"))
696 t.finish <- dockerclient.WaitResult{ExitCode: 0}
698 docker.RemoveImage(hwImageId, true)
700 api := &ArvTestClient{Container: rec}
701 cr := NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
702 am := &ArvMountCmdLine{}
703 cr.RunArvMount = am.ArvMountTest
706 for cr.ContainerID == "" {
707 time.Sleep(time.Millisecond)
709 cr.SigChan <- syscall.SIGINT
716 for k, v := range api.Logs {
722 c.Check(api.CalledWith("container.log", nil), NotNil)
723 c.Check(api.CalledWith("container.state", "Cancelled"), NotNil)
724 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "foo\n"), Equals, true)
728 func (s *TestSuite) TestFullRunSetEnv(c *C) {
729 api, _ := FullRunHelper(c, `{
730 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
731 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
733 "environment": {"FROBIZ": "bilbo"},
734 "mounts": {"/tmp": {"kind": "tmp"} },
735 "output_path": "/tmp",
737 "runtime_constraints": {}
738 }`, func(t *TestDockerClient) {
739 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
741 t.finish <- dockerclient.WaitResult{ExitCode: 0}
744 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
745 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
746 c.Check(strings.HasSuffix(api.Logs["stdout"].String(), "bilbo\n"), Equals, true)
749 type ArvMountCmdLine struct {
754 func (am *ArvMountCmdLine) ArvMountTest(c []string, token string) (*exec.Cmd, error) {
760 func (s *TestSuite) TestSetupMounts(c *C) {
761 api := &ArvTestClient{}
762 kc := &KeepTestClient{}
763 cr := NewContainerRunner(api, kc, nil, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
764 am := &ArvMountCmdLine{}
765 cr.RunArvMount = am.ArvMountTest
767 realTemp, err := ioutil.TempDir("", "crunchrun_test-")
769 defer os.RemoveAll(realTemp)
772 cr.MkTempDir = func(_ string, prefix string) (string, error) {
774 d := fmt.Sprintf("%s/%s%d", realTemp, prefix, i)
775 err := os.Mkdir(d, os.ModePerm)
776 if err != nil && strings.Contains(err.Error(), ": file exists") {
777 // Test case must have pre-populated the tempdir
783 checkEmpty := func() {
784 filepath.Walk(realTemp, func(path string, _ os.FileInfo, err error) error {
785 c.Check(path, Equals, realTemp)
793 cr.Container.Mounts = make(map[string]arvados.Mount)
794 cr.Container.Mounts["/tmp"] = arvados.Mount{Kind: "tmp"}
795 cr.OutputPath = "/tmp"
797 err := cr.SetupMounts()
799 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
800 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/2:/tmp"})
807 cr.Container.Mounts = map[string]arvados.Mount{
808 "/keeptmp": {Kind: "collection", Writable: true},
810 cr.OutputPath = "/keeptmp"
812 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
814 err := cr.SetupMounts()
816 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
817 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/keep1/tmp0:/keeptmp"})
824 cr.Container.Mounts = map[string]arvados.Mount{
825 "/keepinp": {Kind: "collection", PortableDataHash: "59389a8f9ee9d399be35462a0f92541c+53"},
826 "/keepout": {Kind: "collection", Writable: true},
828 cr.OutputPath = "/keepout"
830 os.MkdirAll(realTemp+"/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53", os.ModePerm)
831 os.MkdirAll(realTemp+"/keep1/tmp0", os.ModePerm)
833 err := cr.SetupMounts()
835 c.Check(am.Cmd, DeepEquals, []string{"--foreground", "--allow-other", "--read-write", "--mount-tmp", "tmp0", "--mount-by-pdh", "by_id", realTemp + "/keep1"})
836 sort.StringSlice(cr.Binds).Sort()
837 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/keep1/by_id/59389a8f9ee9d399be35462a0f92541c+53:/keepinp:ro",
838 realTemp + "/keep1/tmp0:/keepout"})
843 for _, test := range []struct {
847 {in: "foo", out: `"foo"`},
848 {in: nil, out: `null`},
849 {in: map[string]int{"foo": 123}, out: `{"foo":123}`},
852 cr.Container.Mounts = map[string]arvados.Mount{
853 "/mnt/test.json": {Kind: "json", Content: test.in},
855 err := cr.SetupMounts()
857 sort.StringSlice(cr.Binds).Sort()
858 c.Check(cr.Binds, DeepEquals, []string{realTemp + "/2/mountdata.json:/mnt/test.json:ro"})
859 content, err := ioutil.ReadFile(realTemp + "/2/mountdata.json")
861 c.Check(content, DeepEquals, []byte(test.out))
867 func (s *TestSuite) TestStdout(c *C) {
869 "command": ["/bin/sh", "-c", "echo $FROBIZ"],
870 "container_image": "d4ab34d3d4f8a72f5c4973051ae69fab+122",
872 "environment": {"FROBIZ": "bilbo"},
873 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path": "/tmp/a/b/c.out"} },
874 "output_path": "/tmp",
876 "runtime_constraints": {}
879 api, _ := FullRunHelper(c, helperRecord, func(t *TestDockerClient) {
880 t.logWriter.Write(dockerLog(1, t.env[0][7:]+"\n"))
882 t.finish <- dockerclient.WaitResult{ExitCode: 0}
885 c.Check(api.CalledWith("container.exit_code", 0), NotNil)
886 c.Check(api.CalledWith("container.state", "Complete"), NotNil)
887 c.Check(api.CalledWith("collection.manifest_text", "./a/b 307372fa8fd5c146b22ae7a45b49bc31+6 0:6:c.out\n"), NotNil)
890 // Used by the TestStdoutWithWrongPath*()
891 func StdoutErrorRunHelper(c *C, record string, fn func(t *TestDockerClient)) (api *ArvTestClient, cr *ContainerRunner, err error) {
892 rec := arvados.Container{}
893 err = json.Unmarshal([]byte(record), &rec)
896 docker := NewTestDockerClient()
898 docker.RemoveImage(hwImageId, true)
900 api = &ArvTestClient{Container: rec}
901 cr = NewContainerRunner(api, &KeepTestClient{}, docker, "zzzzz-zzzzz-zzzzzzzzzzzzzzz")
902 am := &ArvMountCmdLine{}
903 cr.RunArvMount = am.ArvMountTest
909 func (s *TestSuite) TestStdoutWithWrongPath(c *C) {
910 _, _, err := StdoutErrorRunHelper(c, `{
911 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "file", "path":"/tmpa.out"} },
912 "output_path": "/tmp"
913 }`, func(t *TestDockerClient) {})
916 c.Check(strings.Contains(err.Error(), "Stdout path does not start with OutputPath"), Equals, true)
919 func (s *TestSuite) TestStdoutWithWrongKindTmp(c *C) {
920 _, _, err := StdoutErrorRunHelper(c, `{
921 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "tmp", "path":"/tmp/a.out"} },
922 "output_path": "/tmp"
923 }`, func(t *TestDockerClient) {})
926 c.Check(strings.Contains(err.Error(), "Unsupported mount kind 'tmp' for stdout"), Equals, true)
929 func (s *TestSuite) TestStdoutWithWrongKindCollection(c *C) {
930 _, _, err := StdoutErrorRunHelper(c, `{
931 "mounts": {"/tmp": {"kind": "tmp"}, "stdout": {"kind": "collection", "path":"/tmp/a.out"} },
932 "output_path": "/tmp"
933 }`, func(t *TestDockerClient) {})
936 c.Check(strings.Contains(err.Error(), "Unsupported mount kind 'collection' for stdout"), Equals, true)