15370: Merge branch 'main' into 15370-loopback-dispatchcloud
[arvados.git] / lib / crunchrun / executor_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package crunchrun
6
7 import (
8         "bytes"
9         "io"
10         "strings"
11         "time"
12
13         "git.arvados.org/arvados.git/sdk/go/arvados"
14         "git.arvados.org/arvados.git/sdk/go/arvadostest"
15         "golang.org/x/net/context"
16         . "gopkg.in/check.v1"
17 )
18
19 type nopWriteCloser struct{ io.Writer }
20
21 func (nopWriteCloser) Close() error { return nil }
22
23 // embedded by dockerSuite and singularitySuite so they can share
24 // tests.
25 type executorSuite struct {
26         newExecutor func(*C) // embedding struct's SetUpSuite method must set this
27         executor    containerExecutor
28         spec        containerSpec
29         stdout      bytes.Buffer
30         stderr      bytes.Buffer
31 }
32
33 func (s *executorSuite) SetUpTest(c *C) {
34         s.newExecutor(c)
35         s.stdout = bytes.Buffer{}
36         s.stderr = bytes.Buffer{}
37         s.spec = containerSpec{
38                 Image:       "busybox:uclibc",
39                 VCPUs:       1,
40                 WorkingDir:  "",
41                 Env:         map[string]string{"PATH": "/bin:/usr/bin"},
42                 NetworkMode: "default",
43                 Stdout:      nopWriteCloser{&s.stdout},
44                 Stderr:      nopWriteCloser{&s.stderr},
45         }
46         err := s.executor.LoadImage("", arvadostest.BusyboxDockerImage(c), arvados.Container{}, "", nil)
47         c.Assert(err, IsNil)
48 }
49
50 func (s *executorSuite) TearDownTest(c *C) {
51         s.executor.Close()
52 }
53
54 func (s *executorSuite) TestExecTrivialContainer(c *C) {
55         c.Logf("Using container runtime: %s", s.executor.Runtime())
56         s.spec.Command = []string{"echo", "ok"}
57         s.checkRun(c, 0)
58         c.Check(s.stdout.String(), Equals, "ok\n")
59         c.Check(s.stderr.String(), Equals, "")
60 }
61
62 func (s *executorSuite) TestExitStatus(c *C) {
63         s.spec.Command = []string{"false"}
64         s.checkRun(c, 1)
65 }
66
67 func (s *executorSuite) TestSignalExitStatus(c *C) {
68         if _, isdocker := s.executor.(*dockerExecutor); isdocker {
69                 // It's not quite this easy to make busybox kill
70                 // itself in docker where it's pid 1.
71                 c.Skip("kill -9 $$ doesn't work on busybox with pid=1 in docker")
72                 return
73         }
74         s.spec.Command = []string{"sh", "-c", "kill -9 $$"}
75         s.checkRun(c, 0x80+9)
76 }
77
78 func (s *executorSuite) TestExecStop(c *C) {
79         s.spec.Command = []string{"sh", "-c", "sleep 10; echo ok"}
80         err := s.executor.Create(s.spec)
81         c.Assert(err, IsNil)
82         err = s.executor.Start()
83         c.Assert(err, IsNil)
84         go func() {
85                 time.Sleep(time.Second / 10)
86                 s.executor.Stop()
87         }()
88         ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
89         defer cancel()
90         code, err := s.executor.Wait(ctx)
91         c.Check(code, Not(Equals), 0)
92         c.Check(err, IsNil)
93         c.Check(s.stdout.String(), Equals, "")
94         c.Check(s.stderr.String(), Equals, "")
95 }
96
97 func (s *executorSuite) TestExecCleanEnv(c *C) {
98         s.spec.Command = []string{"env"}
99         s.checkRun(c, 0)
100         c.Check(s.stderr.String(), Equals, "")
101         got := map[string]string{}
102         for _, kv := range strings.Split(s.stdout.String(), "\n") {
103                 if kv == "" {
104                         continue
105                 }
106                 kv := strings.SplitN(kv, "=", 2)
107                 switch kv[0] {
108                 case "HOSTNAME", "HOME":
109                         // docker sets these by itself
110                 case "LD_LIBRARY_PATH", "SINGULARITY_NAME", "PWD", "LANG", "SHLVL", "SINGULARITY_INIT", "SINGULARITY_CONTAINER":
111                         // singularity sets these by itself (cf. https://sylabs.io/guides/3.5/user-guide/environment_and_metadata.html)
112                 case "SINGULARITY_APPNAME":
113                         // singularity also sets this by itself (v3.5.2, but not v3.7.4)
114                 case "PROMPT_COMMAND", "PS1", "SINGULARITY_BIND", "SINGULARITY_COMMAND", "SINGULARITY_ENVIRONMENT":
115                         // singularity also sets these by itself (v3.7.4)
116                 default:
117                         got[kv[0]] = kv[1]
118                 }
119         }
120         c.Check(got, DeepEquals, s.spec.Env)
121 }
122 func (s *executorSuite) TestExecEnableNetwork(c *C) {
123         for _, enable := range []bool{false, true} {
124                 s.SetUpTest(c)
125                 s.spec.Command = []string{"ip", "route"}
126                 s.spec.EnableNetwork = enable
127                 s.checkRun(c, 0)
128                 if enable {
129                         c.Check(s.stdout.String(), Matches, "(?ms).*default via.*")
130                 } else {
131                         c.Check(s.stdout.String(), Equals, "")
132                 }
133         }
134 }
135
136 func (s *executorSuite) TestExecWorkingDir(c *C) {
137         s.spec.WorkingDir = "/tmp"
138         s.spec.Command = []string{"sh", "-c", "pwd"}
139         s.checkRun(c, 0)
140         c.Check(s.stdout.String(), Equals, "/tmp\n")
141 }
142
143 func (s *executorSuite) TestExecStdoutStderr(c *C) {
144         s.spec.Command = []string{"sh", "-c", "echo foo; echo -n bar >&2; echo baz; echo waz >&2"}
145         s.checkRun(c, 0)
146         c.Check(s.stdout.String(), Equals, "foo\nbaz\n")
147         c.Check(s.stderr.String(), Equals, "barwaz\n")
148 }
149
150 func (s *executorSuite) checkRun(c *C, expectCode int) {
151         c.Assert(s.executor.Create(s.spec), IsNil)
152         c.Assert(s.executor.Start(), IsNil)
153         ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
154         defer cancel()
155         code, err := s.executor.Wait(ctx)
156         c.Assert(err, IsNil)
157         c.Check(code, Equals, expectCode)
158 }