github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/hooks/exec/exec_test.go (about) 1 package exec 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os" 8 "runtime" 9 "strings" 10 "testing" 11 "time" 12 13 rspec "github.com/opencontainers/runtime-spec/specs-go" 14 "github.com/stretchr/testify/assert" 15 ) 16 17 // path is the path to an example hook executable. 18 var path string 19 20 // unavoidableEnvironmentKeys may be injected even if the hook 21 // executable is executed with a requested empty environment. 22 var unavoidableEnvironmentKeys []string 23 24 func TestRun(t *testing.T) { 25 ctx := context.Background() 26 hook := &rspec.Hook{ 27 Path: path, 28 Args: []string{"sh", "-c", "cat"}, 29 } 30 var stderr, stdout bytes.Buffer 31 hookErr, err := Run(ctx, hook, []byte("{}"), &stdout, &stderr, DefaultPostKillTimeout) 32 if err != nil { 33 t.Fatal(err) 34 } 35 if hookErr != nil { 36 t.Fatal(hookErr) 37 } 38 assert.Equal(t, "{}", stdout.String()) 39 assert.Equal(t, "", stderr.String()) 40 } 41 42 func TestRunIgnoreOutput(t *testing.T) { 43 ctx := context.Background() 44 hook := &rspec.Hook{ 45 Path: path, 46 Args: []string{"sh", "-c", "cat"}, 47 } 48 hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, DefaultPostKillTimeout) 49 if err != nil { 50 t.Fatal(err) 51 } 52 if hookErr != nil { 53 t.Fatal(hookErr) 54 } 55 } 56 57 func TestRunFailedStart(t *testing.T) { 58 ctx := context.Background() 59 hook := &rspec.Hook{ 60 Path: "/does/not/exist", 61 } 62 hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, DefaultPostKillTimeout) 63 if err == nil { 64 t.Fatal("unexpected success") 65 } 66 if !os.IsNotExist(err) { 67 t.Fatal(err) 68 } 69 assert.Equal(t, err, hookErr) 70 } 71 72 func parseEnvironment(input string) (env map[string]string, err error) { 73 env = map[string]string{} 74 lines := strings.Split(input, "\n") 75 for i, line := range lines { 76 if line == "" && i == len(lines)-1 { 77 continue // no content after the terminal newline 78 } 79 keyValue := strings.SplitN(line, "=", 2) 80 if len(keyValue) < 2 { 81 return env, fmt.Errorf("no = in environment line: %q", line) 82 } 83 env[keyValue[0]] = keyValue[1] 84 } 85 for _, key := range unavoidableEnvironmentKeys { 86 delete(env, key) 87 } 88 return env, nil 89 } 90 91 func TestRunEnvironment(t *testing.T) { 92 ctx := context.Background() 93 hook := &rspec.Hook{ 94 Path: path, 95 Args: []string{"sh", "-c", "env"}, 96 } 97 for _, tt := range []struct { 98 name string 99 env []string 100 expected map[string]string 101 }{ 102 { 103 name: "unset", 104 expected: map[string]string{}, 105 }, 106 { 107 name: "set empty", 108 env: []string{}, 109 expected: map[string]string{}, 110 }, 111 { 112 name: "set", 113 env: []string{ 114 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 115 "TERM=xterm", 116 }, 117 expected: map[string]string{ 118 "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 119 "TERM": "xterm", 120 }, 121 }, 122 } { 123 test := tt 124 t.Run(test.name, func(t *testing.T) { 125 var stderr, stdout bytes.Buffer 126 hook.Env = test.env 127 hookErr, err := Run(ctx, hook, []byte("{}"), &stdout, &stderr, DefaultPostKillTimeout) 128 if err != nil { 129 t.Fatal(err) 130 } 131 if hookErr != nil { 132 t.Fatal(hookErr) 133 } 134 assert.Equal(t, "", stderr.String()) 135 136 env, err := parseEnvironment(stdout.String()) 137 if err != nil { 138 t.Fatal(err) 139 } 140 assert.Equal(t, test.expected, env) 141 }) 142 } 143 } 144 145 func TestRunCancel(t *testing.T) { 146 hook := &rspec.Hook{ 147 Path: path, 148 Args: []string{"sh", "-c", "echo waiting; sleep 2; echo done"}, 149 } 150 one := 1 151 for _, tt := range []struct { 152 name string 153 contextTimeout time.Duration 154 hookTimeout *int 155 expectedHookError string 156 expectedRunError error 157 expectedStdout string 158 }{ 159 { 160 name: "no timeouts", 161 expectedStdout: "waiting\ndone\n", 162 }, 163 { 164 name: "context timeout", 165 contextTimeout: time.Duration(1) * time.Second, 166 expectedStdout: "waiting\n", 167 expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$", 168 expectedRunError: context.DeadlineExceeded, 169 }, 170 { 171 name: "hook timeout", 172 hookTimeout: &one, 173 expectedStdout: "waiting\n", 174 expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$", 175 expectedRunError: context.DeadlineExceeded, 176 }, 177 } { 178 test := tt 179 t.Run(test.name, func(t *testing.T) { 180 ctx := context.Background() 181 var stderr, stdout bytes.Buffer 182 if test.contextTimeout > 0 { 183 var cancel context.CancelFunc 184 ctx, cancel = context.WithTimeout(ctx, test.contextTimeout) 185 defer cancel() 186 } 187 hook.Timeout = test.hookTimeout 188 hookErr, err := Run(ctx, hook, []byte("{}"), &stdout, &stderr, DefaultPostKillTimeout) 189 assert.Equal(t, test.expectedRunError, err) 190 if test.expectedHookError == "" { 191 if hookErr != nil { 192 t.Fatal(hookErr) 193 } 194 } else { 195 assert.Regexp(t, test.expectedHookError, hookErr.Error()) 196 } 197 assert.Equal(t, "", stderr.String()) 198 assert.Equal(t, test.expectedStdout, stdout.String()) 199 }) 200 } 201 } 202 203 func TestRunKillTimeout(t *testing.T) { 204 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(500)*time.Millisecond) 205 defer cancel() 206 hook := &rspec.Hook{ 207 Path: path, 208 Args: []string{"sh", "-c", "sleep 1"}, 209 } 210 hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, time.Duration(0)) 211 assert.Equal(t, context.DeadlineExceeded, err) 212 assert.Regexp(t, "^(failed to reap process within 0s of the kill signal|executing \\[sh -c sleep 1]: signal: killed)$", hookErr) 213 } 214 215 func init() { 216 if runtime.GOOS != "windows" { 217 path = "/bin/sh" 218 unavoidableEnvironmentKeys = []string{"PWD", "SHLVL", "_"} 219 } else { 220 panic("we need a reliable executable path on Windows") 221 } 222 }