blob: ced8fc840c49affaa1a2e4c04f64427cd5489437 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Cosmos Nicolaou62613842014-08-25 21:57:37 -07005package modules_test
6
7import (
8 "bufio"
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -07009 "bytes"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070010 "fmt"
11 "io"
Asim Shankar4a698282015-03-21 21:59:18 -070012 "io/ioutil"
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070013 "math/rand"
Cosmos Nicolaou1e78ccc2014-10-09 08:10:26 -070014 "os"
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080015 "reflect"
Cosmos Nicolaou5339a7b2014-11-18 17:28:00 -080016 "sort"
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070017 "strconv"
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -070018 "strings"
Bogdan Caprita490a4512014-11-20 21:12:19 -080019 "syscall"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070020 "testing"
21 "time"
22
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070023 "v.io/v23"
24
Jiri Simsaffceefa2015-02-28 11:03:34 -080025 "v.io/x/ref/lib/exec"
26 execconsts "v.io/x/ref/lib/exec/consts"
Jiri Simsaffceefa2015-02-28 11:03:34 -080027 "v.io/x/ref/lib/flags/consts"
Jiri Simsaffceefa2015-02-28 11:03:34 -080028 _ "v.io/x/ref/profiles"
Asim Shankar4a698282015-03-21 21:59:18 -070029 vsecurity "v.io/x/ref/security"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070030 "v.io/x/ref/test"
31 "v.io/x/ref/test/modules"
Asim Shankar4a698282015-03-21 21:59:18 -070032 "v.io/x/ref/test/testutil"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070033)
34
Ankur9f957942014-11-24 16:34:18 -080035const credentialsEnvPrefix = "\"" + consts.VeyronCredentials + "="
36
Cosmos Nicolaou62613842014-08-25 21:57:37 -070037func init() {
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -070038 modules.RegisterChild("envtest", "envtest: <variables to print>...", PrintFromEnv)
39 modules.RegisterChild("printenv", "printenv", PrintEnv)
Ryan Browna08a2212015-01-15 15:40:10 -080040 modules.RegisterChild("printblessing", "printblessing", PrintBlessing)
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080041 modules.RegisterChild("echos", "[args]*", Echo)
42 modules.RegisterChild("errortestChild", "", ErrorMain)
Bogdan Caprita490a4512014-11-20 21:12:19 -080043 modules.RegisterChild("ignores_stdin", "", ignoresStdin)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070044 modules.RegisterChild("pipeProc", "", pipeEcho)
45 modules.RegisterChild("lifo", "", lifo)
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080046
47 modules.RegisterFunction("envtestf", "envtest: <variables to print>...", PrintFromEnv)
48 modules.RegisterFunction("echof", "[args]*", Echo)
49 modules.RegisterFunction("errortestFunc", "", ErrorMain)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070050 modules.RegisterFunction("pipeFunc", "", pipeEcho)
Bogdan Caprita490a4512014-11-20 21:12:19 -080051}
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080052
Suharsh Sivakumard19c95d2015-02-19 14:44:50 -080053// We must call Testmain ourselves because using v23 test generate
54// creates an import cycle for this package.
55func TestMain(m *testing.M) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070056 test.Init()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070057 if modules.IsModulesChildProcess() {
Suharsh Sivakumard19c95d2015-02-19 14:44:50 -080058 if err := modules.Dispatch(); err != nil {
59 fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
60 os.Exit(1)
61 }
62 return
63 }
64 os.Exit(m.Run())
65}
66
Bogdan Caprita490a4512014-11-20 21:12:19 -080067func ignoresStdin(io.Reader, io.Writer, io.Writer, map[string]string, ...string) error {
68 <-time.After(time.Minute)
69 return nil
Cosmos Nicolaou62613842014-08-25 21:57:37 -070070}
71
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -070072func Echo(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
73 for _, a := range args {
74 fmt.Fprintf(stdout, "stdout: %s\n", a)
75 fmt.Fprintf(stderr, "stderr: %s\n", a)
76 }
77 return nil
78}
79
Cosmos Nicolaou42a17362015-03-10 16:40:18 -070080func pipeEcho(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
81 scanner := bufio.NewScanner(stdin)
82 for scanner.Scan() {
83 fmt.Fprintf(stdout, "%p: %s\n", pipeEcho, scanner.Text())
84 }
85 return nil
86}
87
88func lifo(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
89 scanner := bufio.NewScanner(stdin)
90 scanner.Scan()
91 msg := scanner.Text()
92 modules.WaitForEOF(stdin)
93 fmt.Fprintf(stdout, "%p: %s\n", lifo, msg)
94 return nil
95}
96
Ryan Browna08a2212015-01-15 15:40:10 -080097func PrintBlessing(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070098 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080099 defer shutdown()
100
Jiri Simsa6ac95222015-02-23 16:11:49 -0800101 blessing := v23.GetPrincipal(ctx).BlessingStore().Default()
Ryan Browna08a2212015-01-15 15:40:10 -0800102 fmt.Fprintf(stdout, "%s", blessing)
103 return nil
104}
105
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700106func PrintFromEnv(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
Suharsh Sivakumar9d17e4a2015-02-02 22:42:16 -0800107 for _, a := range args {
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700108 if v := env[a]; len(v) > 0 {
Cosmos Nicolaou74cc9b22014-10-23 15:25:35 -0700109 fmt.Fprintf(stdout, "%s\n", a+"="+v)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700110 } else {
111 fmt.Fprintf(stderr, "missing %s\n", a)
112 }
113 }
114 modules.WaitForEOF(stdin)
115 fmt.Fprintf(stdout, "done\n")
116 return nil
117}
118
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700119const printEnvArgPrefix = "PRINTENV_ARG="
120
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700121func PrintEnv(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
122 for _, a := range args {
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700123 fmt.Fprintf(stdout, "%s%s\n", printEnvArgPrefix, a)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700124 }
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700125 for k, v := range env {
Cosmos Nicolaou74cc9b22014-10-23 15:25:35 -0700126 fmt.Fprintf(stdout, "%q\n", k+"="+v)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700127 }
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700128 return nil
129}
130
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700131func ErrorMain(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
132 return fmt.Errorf("an error")
133}
134
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700135func waitForInput(scanner *bufio.Scanner) bool {
136 ch := make(chan struct{})
137 go func(ch chan<- struct{}) {
138 scanner.Scan()
139 ch <- struct{}{}
140 }(ch)
141 select {
142 case <-ch:
143 return true
144 case <-time.After(10 * time.Second):
145 return false
146 }
147}
148
149func testCommand(t *testing.T, sh *modules.Shell, name, key, val string) {
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700150 h, err := sh.Start(name, nil, key)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700151 if err != nil {
152 t.Fatalf("unexpected error: %s", err)
153 }
154 defer func() {
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700155 var stdout, stderr bytes.Buffer
156 sh.Cleanup(&stdout, &stderr)
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700157 want := ""
158 if testing.Verbose() {
159 want = "---- Shell Cleanup ----\n"
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700160 }
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700161 if got := stdout.String(); got != "" && got != want {
162 t.Errorf("got %q, want %q", got, want)
163 }
164 if got := stderr.String(); got != "" && got != want {
165 t.Errorf("got %q, want %q", got, want)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700166 }
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700167 }()
168 scanner := bufio.NewScanner(h.Stdout())
169 if !waitForInput(scanner) {
170 t.Errorf("timeout")
171 return
172 }
173 if got, want := scanner.Text(), key+"="+val; got != want {
174 t.Errorf("got %q, want %q", got, want)
175 }
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700176 h.CloseStdin()
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700177 if !waitForInput(scanner) {
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700178 t.Fatalf("timeout")
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700179 return
180 }
181 if got, want := scanner.Text(), "done"; got != want {
182 t.Errorf("got %q, want %q", got, want)
183 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700184 if err := h.Shutdown(nil, nil); err != nil {
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700185 t.Fatalf("unexpected error: %s", err)
186 }
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700187}
188
Ryan Browna08a2212015-01-15 15:40:10 -0800189func getBlessing(t *testing.T, sh *modules.Shell, env ...string) string {
190 h, err := sh.Start("printblessing", env)
191 if err != nil {
192 t.Fatalf("unexpected error: %s", err)
193 }
194 scanner := bufio.NewScanner(h.Stdout())
195 if !waitForInput(scanner) {
196 t.Errorf("timeout")
197 return ""
198 }
199 return scanner.Text()
200}
201
Ryan Brown61d69382015-02-25 11:13:39 -0800202func getCustomBlessing(t *testing.T, sh *modules.Shell, creds *modules.CustomCredentials) string {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700203 h, err := sh.StartWithOpts(sh.DefaultStartOpts().WithCustomCredentials(creds), nil, "printblessing")
Ryan Brown61d69382015-02-25 11:13:39 -0800204 if err != nil {
205 t.Fatalf("unexpected error: %s", err)
206 }
207 scanner := bufio.NewScanner(h.Stdout())
208 if !waitForInput(scanner) {
209 t.Errorf("timeout")
210 return ""
211 }
212 return scanner.Text()
213}
214
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700215func TestChild(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700216 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800217 defer shutdown()
218
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700219 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800220 if err != nil {
221 t.Fatalf("unexpected error: %s", err)
222 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700223 defer sh.Cleanup(nil, nil)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700224 key, val := "simpleVar", "foo & bar"
225 sh.SetVar(key, val)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700226 testCommand(t, sh, "envtest", key, val)
227}
228
Ryan Browna08a2212015-01-15 15:40:10 -0800229func TestAgent(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700230 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800231 defer shutdown()
232
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700233 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Ryan Browna08a2212015-01-15 15:40:10 -0800234 if err != nil {
235 t.Fatalf("unexpected error: %s", err)
236 }
237 defer sh.Cleanup(os.Stdout, os.Stderr)
238 a := getBlessing(t, sh)
239 b := getBlessing(t, sh)
240 if a != b {
241 t.Errorf("Expected same blessing for children, got %s and %s", a, b)
242 }
Ryan Browna08a2212015-01-15 15:40:10 -0800243}
244
245func TestCustomPrincipal(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700246 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800247 defer shutdown()
248
Asim Shankar4a698282015-03-21 21:59:18 -0700249 p, err := vsecurity.NewPrincipal()
250 if err != nil {
251 t.Fatal(err)
252 }
253 b, err := p.BlessSelf("myshell")
254 if err != nil {
255 t.Fatal(err)
256 }
257 if err := vsecurity.SetDefaultBlessings(p, b); err != nil {
258 t.Fatal(err)
259 }
Ryan Browna08a2212015-01-15 15:40:10 -0800260 cleanDebug := p.BlessingStore().DebugString()
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700261 sh, err := modules.NewShell(ctx, p, testing.Verbose(), t)
Ryan Browna08a2212015-01-15 15:40:10 -0800262 if err != nil {
263 t.Fatalf("unexpected error: %s", err)
264 }
265 defer sh.Cleanup(os.Stdout, os.Stderr)
Asim Shankar4a698282015-03-21 21:59:18 -0700266 if got, want := getBlessing(t, sh), "myshell/child"; got != want {
267 t.Errorf("Bad blessing. Got %q, want %q", got, want)
Ryan Browna08a2212015-01-15 15:40:10 -0800268 }
269 newDebug := p.BlessingStore().DebugString()
270 if cleanDebug != newDebug {
271 t.Errorf("Shell modified custom principal. Was:\n%q\nNow:\n%q", cleanDebug, newDebug)
272 }
273}
274
Ryan Brown61d69382015-02-25 11:13:39 -0800275func TestCustomCredentials(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700276 ctx, shutdown := test.InitForTest()
Ryan Brown61d69382015-02-25 11:13:39 -0800277 defer shutdown()
278
Asim Shankar4a698282015-03-21 21:59:18 -0700279 root := testutil.NewIDProvider("myshell")
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700280 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Ryan Brown61d69382015-02-25 11:13:39 -0800281 if err != nil {
282 t.Fatal(err)
283 }
284 defer sh.Cleanup(os.Stdout, os.Stderr)
285
286 newCreds := func(ext string) *modules.CustomCredentials {
287 p, err := sh.NewCustomCredentials()
288 if err != nil {
289 t.Fatal(err)
290 }
291 b, err := root.NewBlessings(p.Principal(), ext)
292 if err != nil {
293 t.Fatal(err)
294 }
Asim Shankar4a698282015-03-21 21:59:18 -0700295 if err := vsecurity.SetDefaultBlessings(p.Principal(), b); err != nil {
296 t.Fatal(err)
297 }
Ryan Brown61d69382015-02-25 11:13:39 -0800298 return p
299 }
300
301 a := newCreds("a")
302 cleanDebug := a.Principal().BlessingStore().DebugString()
303
304 blessing := getCustomBlessing(t, sh, a)
305 if blessing != "myshell/a" {
306 t.Errorf("Bad blessing. Expected myshell/a, go %q", blessing)
307 }
308
309 b := newCreds("bar")
310 blessing = getCustomBlessing(t, sh, b)
311 if blessing != "myshell/bar" {
312 t.Errorf("Bad blessing. Expected myshell/bar, go %q", blessing)
313 }
314
315 // Make sure we can re-use credentials
316 blessing = getCustomBlessing(t, sh, a)
317 if blessing != "myshell/a" {
318 t.Errorf("Bad blessing. Expected myshell/a, go %q", blessing)
319 }
320
321 newDebug := a.Principal().BlessingStore().DebugString()
322 if cleanDebug != newDebug {
323 t.Errorf("Shell modified custom principal. Was:\n%q\nNow:\n%q", cleanDebug, newDebug)
324 }
325}
326
Asim Shankar4a698282015-03-21 21:59:18 -0700327func createCredentials(blessing string) (string, error) {
328 dir, err := ioutil.TempDir("", "TestNoAgent_v23_credentials")
329 if err != nil {
330 return "", err
331 }
332 p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
333 if err != nil {
334 os.RemoveAll(dir)
335 return "", err
336 }
337 b, err := p.BlessSelf(blessing)
338 if err != nil {
339 os.RemoveAll(dir)
340 return "", err
341 }
342 if err := vsecurity.SetDefaultBlessings(p, b); err != nil {
343 os.RemoveAll(dir)
344 return "", err
345 }
346 return dir, nil
347}
348
Ryan Browna08a2212015-01-15 15:40:10 -0800349func TestNoAgent(t *testing.T) {
Asim Shankar4a698282015-03-21 21:59:18 -0700350 const noagent = "noagent"
351 creds, err := createCredentials(noagent)
352 if err != nil {
353 t.Fatal(err)
354 }
Ryan Browna08a2212015-01-15 15:40:10 -0800355 defer os.RemoveAll(creds)
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700356 sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
Ryan Browna08a2212015-01-15 15:40:10 -0800357 if err != nil {
358 t.Fatalf("unexpected error: %s", err)
359 }
360 defer sh.Cleanup(os.Stdout, os.Stderr)
Asim Shankar4a698282015-03-21 21:59:18 -0700361 if got, want := getBlessing(t, sh, fmt.Sprintf("VEYRON_CREDENTIALS=%s", creds)), noagent; got != want {
362 t.Errorf("Bad blessing. Got %q, want %q", got, want)
Ryan Browna08a2212015-01-15 15:40:10 -0800363 }
364}
365
Cosmos Nicolaou1e78ccc2014-10-09 08:10:26 -0700366func TestChildNoRegistration(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700367 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800368 defer shutdown()
369
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700370 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800371 if err != nil {
372 t.Fatalf("unexpected error: %s", err)
373 }
Cosmos Nicolaou1e78ccc2014-10-09 08:10:26 -0700374 defer sh.Cleanup(os.Stderr, os.Stderr)
375 key, val := "simpleVar", "foo & bar"
376 sh.SetVar(key, val)
377 testCommand(t, sh, "envtest", key, val)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800378 _, err = sh.Start("non-existent-command", nil, "random", "args")
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800379 if err == nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700380 fmt.Fprintf(os.Stderr, "Failed: %v\n", err)
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800381 t.Fatalf("expected error")
Cosmos Nicolaou1e78ccc2014-10-09 08:10:26 -0700382 }
383}
384
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700385func TestFunction(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700386 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800387 defer shutdown()
388
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700389 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800390 if err != nil {
391 t.Fatalf("unexpected error: %s", err)
392 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700393 defer sh.Cleanup(nil, nil)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700394 key, val := "simpleVar", "foo & bar & baz"
395 sh.SetVar(key, val)
Cosmos Nicolaou1e78ccc2014-10-09 08:10:26 -0700396 testCommand(t, sh, "envtestf", key, val)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700397}
398
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700399func TestErrorChild(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700400 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800401 defer shutdown()
402
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700403 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800404 if err != nil {
405 t.Fatalf("unexpected error: %s", err)
406 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700407 defer sh.Cleanup(nil, nil)
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800408 h, err := sh.Start("errortestChild", nil)
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700409 if err != nil {
410 t.Fatalf("unexpected error: %s", err)
411 }
Suharsh Sivakumard19c95d2015-02-19 14:44:50 -0800412 if got, want := h.Shutdown(nil, nil), "exit status 1"; got == nil || got.Error() != want {
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700413 t.Errorf("got %q, want %q", got, want)
414 }
415}
416
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800417func testShutdown(t *testing.T, sh *modules.Shell, command string, isfunc bool) {
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700418 result := ""
419 args := []string{"a", "b c", "ddd"}
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800420 if _, err := sh.Start(command, nil, args...); err != nil {
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700421 t.Fatalf("unexpected error: %s", err)
422 }
423 var stdoutBuf bytes.Buffer
424 var stderrBuf bytes.Buffer
425 sh.Cleanup(&stdoutBuf, &stderrBuf)
Suharsh Sivakumar9d17e4a2015-02-02 22:42:16 -0800426 var stdoutOutput, stderrOutput string
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700427 for _, a := range args {
428 stdoutOutput += fmt.Sprintf("stdout: %s\n", a)
429 stderrOutput += fmt.Sprintf("stderr: %s\n", a)
430 }
431 if got, want := stdoutBuf.String(), stdoutOutput+result; got != want {
432 t.Errorf("got %q want %q", got, want)
433 }
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700434 if !isfunc {
435 stderrBuf.ReadString('\n') // Skip past the random # generator output
436 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700437 if got, want := stderrBuf.String(), stderrOutput; got != want {
438 t.Errorf("got %q want %q", got, want)
439 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700440}
441
442func TestShutdownSubprocess(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700443 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800444 defer shutdown()
445
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700446 sh, err := modules.NewShell(ctx, nil, false, t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800447 if err != nil {
448 t.Fatalf("unexpected error: %s", err)
449 }
Robin Thellendcf140c02014-12-08 14:56:24 -0800450 defer sh.Cleanup(nil, nil)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800451 testShutdown(t, sh, "echos", false)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700452}
453
Bogdan Caprita490a4512014-11-20 21:12:19 -0800454// TestShutdownSubprocessIgnoresStdin verifies that Shutdown doesn't wait
455// forever if a child does not die upon closing stdin; but instead times out and
456// returns an appropriate error.
457func TestShutdownSubprocessIgnoresStdin(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700458 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800459 defer shutdown()
460
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700461 sh, err := modules.NewShell(ctx, nil, false, t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800462 if err != nil {
463 t.Fatalf("unexpected error: %s", err)
464 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700465 opts := sh.DefaultStartOpts()
466 opts.ShutdownTimeout = time.Second
467 h, err := sh.StartWithOpts(opts, nil, "ignores_stdin")
Bogdan Caprita490a4512014-11-20 21:12:19 -0800468 if err != nil {
469 t.Fatalf("unexpected error: %s", err)
470 }
471 var stdoutBuf, stderrBuf bytes.Buffer
472 if err := sh.Cleanup(&stdoutBuf, &stderrBuf); err == nil || err.Error() != exec.ErrTimeout.Error() {
473 t.Errorf("unexpected error in Cleanup: got %v, want %v", err, exec.ErrTimeout)
474 }
475 if err := syscall.Kill(h.Pid(), syscall.SIGINT); err != nil {
476 t.Errorf("Kill failed: %v", err)
477 }
478}
479
480// TestStdoutRace exemplifies a potential race between reading from child's
481// stdout and closing stdout in Wait (called by Shutdown).
482//
483// NOTE: triggering the actual --race failure is hard, even if the
484// implementation inappropriately sets stdout to the file that is to be closed
485// in Wait.
486func TestStdoutRace(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700487 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800488 defer shutdown()
489
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700490 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800491 if err != nil {
492 t.Fatalf("unexpected error: %s", err)
493 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700494 opts := sh.DefaultStartOpts()
495 opts.ShutdownTimeout = time.Second
496 h, err := sh.StartWithOpts(opts, nil, "ignores_stdin")
Bogdan Caprita490a4512014-11-20 21:12:19 -0800497 if err != nil {
498 t.Fatalf("unexpected error: %s", err)
499 }
500 ch := make(chan error, 1)
501 go func() {
502 buf := make([]byte, 5)
503 // This will block since the child is not writing anything on
504 // stdout.
505 _, err := h.Stdout().Read(buf)
506 ch <- err
507 }()
508 // Give the goroutine above a chance to run, so that we're blocked on
509 // stdout.Read.
510 <-time.After(time.Second)
511 // Cleanup should close stdout, and unblock the goroutine.
512 sh.Cleanup(nil, nil)
513 if got, want := <-ch, io.EOF; got != want {
514 t.Errorf("Expected %v, got %v instead", want, got)
515 }
516
517 if err := syscall.Kill(h.Pid(), syscall.SIGINT); err != nil {
518 t.Errorf("Kill failed: %v", err)
519 }
520}
521
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700522func TestShutdownFunction(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700523 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800524 defer shutdown()
525
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700526 sh, err := modules.NewShell(ctx, nil, false, t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800527 if err != nil {
528 t.Fatalf("unexpected error: %s", err)
529 }
Robin Thellendcf140c02014-12-08 14:56:24 -0800530 defer sh.Cleanup(nil, nil)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800531 testShutdown(t, sh, "echof", true)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700532}
533
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700534func TestErrorFunc(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700535 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800536 defer shutdown()
537
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700538 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800539 if err != nil {
540 t.Fatalf("unexpected error: %s", err)
541 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700542 defer sh.Cleanup(nil, nil)
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800543 h, err := sh.Start("errortestFunc", nil)
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700544 if err != nil {
545 t.Fatalf("unexpected error: %s", err)
546 }
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700547 if got, want := h.Shutdown(nil, nil), "an error"; got != nil && got.Error() != want {
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700548 t.Errorf("got %q, want %q", got, want)
549 }
550}
551
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700552func find(want string, in []string) bool {
553 for _, a := range in {
554 if a == want {
555 return true
556 }
557 }
558 return false
559}
560
561func TestEnvelope(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700562 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800563 defer shutdown()
564
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700565 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800566 if err != nil {
567 t.Fatalf("unexpected error: %s", err)
568 }
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700569 defer sh.Cleanup(nil, nil)
570 sh.SetVar("a", "1")
571 sh.SetVar("b", "2")
572 args := []string{"oh", "ah"}
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700573 h, err := sh.Start("printenv", nil, args...)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700574 if err != nil {
575 t.Fatalf("unexpected error: %s", err)
576 }
577 scanner := bufio.NewScanner(h.Stdout())
578 childArgs, childEnv := []string{}, []string{}
579 for scanner.Scan() {
580 o := scanner.Text()
581 if strings.HasPrefix(o, printEnvArgPrefix) {
582 childArgs = append(childArgs, strings.TrimPrefix(o, printEnvArgPrefix))
583 } else {
584 childEnv = append(childEnv, o)
585 }
586 }
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700587 shArgs, shEnv := sh.CommandEnvelope("printenv", nil, args...)
Cosmos Nicolaou74cc9b22014-10-23 15:25:35 -0700588 for i, ev := range shEnv {
589 shEnv[i] = fmt.Sprintf("%q", ev)
590 }
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700591 for _, want := range args {
592 if !find(want, childArgs) {
593 t.Errorf("failed to find %q in %s", want, childArgs)
594 }
595 if !find(want, shArgs) {
596 t.Errorf("failed to find %q in %s", want, shArgs)
597 }
598 }
599
600 for _, want := range shEnv {
601 if !find(want, childEnv) {
Cosmos Nicolaou74cc9b22014-10-23 15:25:35 -0700602 t.Errorf("failed to find %s in %#v", want, childEnv)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700603 }
604 }
Ankur9f957942014-11-24 16:34:18 -0800605
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700606 for _, want := range childEnv {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800607 if want == "\""+execconsts.ExecVersionVariable+"=\"" {
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700608 continue
609 }
610 if !find(want, shEnv) {
Cosmos Nicolaou74cc9b22014-10-23 15:25:35 -0700611 t.Errorf("failed to find %s in %#v", want, shEnv)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700612 }
613 }
614}
615
616func TestEnvMerge(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700617 ctx, shutdown := test.InitForTest()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800618 defer shutdown()
619
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700620 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou344cc4a2014-11-26 15:38:43 -0800621 if err != nil {
622 t.Fatalf("unexpected error: %s", err)
623 }
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700624 defer sh.Cleanup(nil, nil)
625 sh.SetVar("a", "1")
626 os.Setenv("a", "wrong, should be 1")
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700627 sh.SetVar("b", "2 also wrong")
628 os.Setenv("b", "wrong, should be 2")
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700629 h, err := sh.Start("printenv", []string{"b=2"})
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700630 if err != nil {
631 t.Fatalf("unexpected error: %s", err)
632 }
633 scanner := bufio.NewScanner(h.Stdout())
634 for scanner.Scan() {
635 o := scanner.Text()
636 if strings.HasPrefix(o, "a=") {
637 if got, want := o, "a=1"; got != want {
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700638 t.Errorf("got: %q, want %q", got, want)
639 }
640 }
641 if strings.HasPrefix(o, "b=") {
642 if got, want := o, "b=2"; got != want {
643 t.Errorf("got: %q, want %q", got, want)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700644 }
645 }
646 }
647}
648
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800649func TestSetEntryPoint(t *testing.T) {
650 env := map[string]string{"a": "a", "b": "b"}
651 nenv := modules.SetEntryPoint(env, "eg1")
652 if got, want := len(nenv), 3; got != want {
653 t.Errorf("got %d, want %d", got, want)
654 }
655 nenv = modules.SetEntryPoint(env, "eg2")
656 if got, want := len(nenv), 3; got != want {
657 t.Errorf("got %d, want %d", got, want)
658 }
Cosmos Nicolaou5339a7b2014-11-18 17:28:00 -0800659 sort.Strings(nenv)
660 if got, want := nenv, []string{"VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT=eg2", "a=a", "b=b"}; !reflect.DeepEqual(got, want) {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800661 t.Errorf("got %d, want %d", got, want)
662 }
663}
664
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700665func TestNoExec(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700666 ctx, shutdown := test.InitForTest()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700667 defer shutdown()
668
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700669 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700670 if err != nil {
671 t.Fatalf("unexpected error: %s", err)
672 }
673 defer sh.Cleanup(nil, nil)
674 h, err := sh.StartWithOpts(sh.DefaultStartOpts().NoExecCommand(), nil, "/bin/echo", "hello", "world")
675 if err != nil {
676 t.Fatalf("unexpected error: %s", err)
677 }
678 scanner := bufio.NewScanner(h.Stdout())
679 scanner.Scan()
680 if got, want := scanner.Text(), "hello world"; got != want {
681 t.Fatalf("got %v, want %v", got, want)
682 }
683}
684
685func TestExternal(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700686 ctx, shutdown := test.InitForTest()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700687 defer shutdown()
688
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700689 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700690 if err != nil {
691 t.Fatalf("unexpected error: %s", err)
692 }
693 defer sh.Cleanup(nil, nil)
694 cookie := strconv.Itoa(rand.Int())
695 sh.SetConfigKey("cookie", cookie)
696 h, err := sh.StartWithOpts(sh.DefaultStartOpts().ExternalCommand(), nil, os.Args[0], "--test.run=TestExternalTestHelper")
697 if err != nil {
698 t.Fatalf("unexpected error: %s", err)
699 }
700 scanner := bufio.NewScanner(h.Stdout())
701 scanner.Scan()
702 if got, want := scanner.Text(), fmt.Sprintf("cookie: %s", cookie); got != want {
703 h.Shutdown(os.Stderr, os.Stderr)
704 t.Fatalf("got %v, want %v", got, want)
705 }
706}
707
708// TestExternalTestHelper is used by TestExternal above and has not utility
709// as a test in it's own right.
710func TestExternalTestHelper(t *testing.T) {
711 child, err := exec.GetChildHandle()
712 if err != nil {
713 return
714 }
715 child.SetReady()
716 val, err := child.Config.Get("cookie")
717 if err != nil {
718 t.Fatalf("failed to get child handle: %s", err)
719 }
720 fmt.Printf("cookie: %s\n", val)
721}
722
723func TestPipe(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700724 ctx, shutdown := test.InitForTest()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700725 defer shutdown()
726
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700727 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700728 if err != nil {
729 t.Fatalf("unexpected error: %s", err)
730 }
731 defer sh.Cleanup(nil, nil)
732
733 for _, cmd := range []string{"pipeProc", "pipeFunc"} {
734 r, w, err := os.Pipe()
735 if err != nil {
736 t.Fatal(err)
737 }
738 opts := sh.DefaultStartOpts()
739 opts.Stdin = r
740 h, err := sh.StartWithOpts(opts, nil, cmd)
741 if err != nil {
742 t.Fatal(err)
743 }
744 cookie := strconv.Itoa(rand.Int())
745 go func(w *os.File, s string) {
746 fmt.Fprintf(w, "hello world\n")
747 fmt.Fprintf(w, "%s\n", s)
748 w.Close()
749 }(w, cookie)
750
751 scanner := bufio.NewScanner(h.Stdout())
752 want := []string{
753 fmt.Sprintf("%p: hello world", pipeEcho),
754 fmt.Sprintf("%p: %s", pipeEcho, cookie),
755 }
756 i := 0
757 for scanner.Scan() {
758 if got, want := scanner.Text(), want[i]; got != want {
759 t.Fatalf("%s: got %v, want %v", cmd, got, want)
760 }
761 i++
762 }
763 if got, want := i, 2; got != want {
764 t.Fatalf("%s: got %v, want %v", cmd, got, want)
765 }
766 if err := h.Shutdown(os.Stderr, os.Stderr); err != nil {
767 t.Fatal(err)
768 }
769 r.Close()
770 }
771}
772
773func TestLIFO(t *testing.T) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700774 ctx, shutdown := test.InitForTest()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700775 defer shutdown()
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700776 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700777 if err != nil {
778 t.Fatalf("unexpected error: %s", err)
779 }
780 defer sh.Cleanup(nil, nil)
781
782 cases := []string{"a", "b", "c"}
783 for _, msg := range cases {
784 h, err := sh.Start("lifo", nil)
785 if err != nil {
786 t.Fatal(err)
787 }
788 fmt.Fprintf(h.Stdin(), "%s\n", msg)
789 }
790 var buf bytes.Buffer
791 if err := sh.Cleanup(&buf, nil); err != nil {
792 t.Fatal(err)
793 }
794 lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
795 if got, want := len(lines), len(cases); got != want {
796 t.Fatalf("got %v, want %v", got, want)
797 }
798 sort.Sort(sort.Reverse(sort.StringSlice(cases)))
799 for i, msg := range cases {
800 if got, want := lines[i], fmt.Sprintf("%p: %s", lifo, msg); got != want {
801 t.Fatalf("got %v, want %v", got, want)
802 }
803 }
804}
805
806func TestStartOpts(t *testing.T) {
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700807 sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700808 if err != nil {
809 t.Fatalf("unexpected error: %s", err)
810 }
811 opts := modules.StartOpts{
812 External: true,
813 }
814 sh.SetDefaultStartOpts(opts)
815 def := sh.DefaultStartOpts()
816 if got, want := def.External, opts.External; got != want {
817 t.Fatalf("got %v, want %v", got, want)
818 }
819 def.External = false
820 if got, want := def, (modules.StartOpts{}); !reflect.DeepEqual(got, want) {
821 t.Fatalf("got %v, want %v", got, want)
822 }
823
824 // Verify that the shell retains a copy.
825 opts.External = false
826 opts.ExecProtocol = true
827 def = sh.DefaultStartOpts()
828 if got, want := def.External, true; got != want {
829 t.Fatalf("got %v, want %v", got, want)
830 }
831 if got, want := def.ExecProtocol, false; got != want {
832 t.Fatalf("got %v, want %v", got, want)
833 }
834
835 sh.SetDefaultStartOpts(opts)
836 def = sh.DefaultStartOpts()
837 if got, want := def.ExecProtocol, true; got != want {
838 t.Fatalf("got %v, want %v", got, want)
839 }
840}
841
842func TestEmbeddedSession(t *testing.T) {
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700843 sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700844 if err != nil {
845 t.Fatalf("unexpected error: %s", err)
846 }
847 def := sh.DefaultStartOpts()
848 if def.ExpectTesting == nil {
849 t.Fatalf("ExpectTesting should be non nil")
850 }
851}
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700852
853func TestCredentialsAndNoExec(t *testing.T) {
854 ctx, shutdown := test.InitForTest()
855 defer shutdown()
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700856 sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700857 if err != nil {
858 t.Fatalf("unexpected error: %s", err)
859 }
860 opts := sh.DefaultStartOpts()
861 opts = opts.NoExecCommand()
862 creds, err := sh.NewCustomCredentials()
863 if err != nil {
864 t.Fatalf("unexpected error: %s", err)
865 }
866 opts = opts.WithCustomCredentials(creds)
867 h, err := sh.StartWithOpts(opts, nil, "echos", "a")
868
869 if got, want := err, modules.ErrNoExecAndCustomCreds; got != want {
870 t.Fatalf("got %v, want %v", got, want)
871 }
872 if got, want := h, modules.Handle(nil); got != want {
873 t.Fatalf("got %v, want %v", got, want)
874 }
875}