func TestPipelineDifferentShells(t *testing.T) { sh1 := gosh.NewShell(t) defer sh1.Cleanup() sh2 := gosh.NewShell(t) defer sh2.Cleanup() setsErr(t, sh1, func() { gosh.NewPipeline(sh1.FuncCmd(echoFunc), sh2.FuncCmd(catFunc)) }) setsErr(t, sh2, func() { gosh.NewPipeline(sh2.FuncCmd(echoFunc), sh1.FuncCmd(catFunc)) }) p := gosh.NewPipeline(sh1.FuncCmd(echoFunc)) setsErr(t, sh1, func() { p.PipeStdout(sh2.FuncCmd(catFunc)) }) p = gosh.NewPipeline(sh1.FuncCmd(echoFunc)) setsErr(t, sh1, func() { p.PipeStderr(sh2.FuncCmd(catFunc)) }) p = gosh.NewPipeline(sh1.FuncCmd(echoFunc)) setsErr(t, sh1, func() { p.PipeCombinedOutput(sh2.FuncCmd(catFunc)) }) }
func TestStdoutStderr(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() // Write to stdout only. c := sh.FuncCmd(writeFunc, true, false) stdoutPipe, stderrPipe := c.StdoutPipe(), c.StderrPipe() stdout, stderr := c.StdoutStderr() eq(t, stdout, "AA") eq(t, stderr, "") eq(t, toString(t, stdoutPipe), "AA") eq(t, toString(t, stderrPipe), "") // Write to stderr only. c = sh.FuncCmd(writeFunc, false, true) stdoutPipe, stderrPipe = c.StdoutPipe(), c.StderrPipe() stdout, stderr = c.StdoutStderr() eq(t, stdout, "") eq(t, stderr, "BB") eq(t, toString(t, stdoutPipe), "") eq(t, toString(t, stderrPipe), "BB") // Write to both stdout and stderr. c = sh.FuncCmd(writeFunc, true, true) stdoutPipe, stderrPipe = c.StdoutPipe(), c.StderrPipe() stdout, stderr = c.StdoutStderr() eq(t, stdout, "AA") eq(t, stderr, "BB") eq(t, toString(t, stdoutPipe), "AA") eq(t, toString(t, stderrPipe), "BB") }
func TestPipelineTerminate(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() for _, d := range []time.Duration{0, time.Hour} { for _, s := range []os.Signal{os.Interrupt, os.Kill} { fmt.Println(d, s) p := gosh.NewPipeline(sh.FuncCmd(sleepFunc, d, 0), sh.FuncCmd(sleepFunc, d, 0)) p.Start() p.Cmds()[0].AwaitVars("ready") p.Cmds()[1].AwaitVars("ready") // Wait for a bit to allow the zero-sleep commands to exit. time.Sleep(100 * time.Millisecond) // Terminate should succeed regardless of the exit code, and regardless of // whether the signal arrived or the processes had already exited. p.Terminate(s) } } // Terminate should fail if Wait has been called. z := time.Duration(0) p := gosh.NewPipeline(sh.FuncCmd(sleepFunc, z, 0), sh.FuncCmd(sleepFunc, z, 0)) p.Run() setsErr(t, sh, func() { p.Terminate(os.Interrupt) }) }
func TestSignal(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() for _, d := range []time.Duration{0, time.Hour} { for _, s := range []os.Signal{os.Interrupt, os.Kill} { fmt.Println(d, s) c := sh.FuncCmd(sleepFunc, d, 0) c.Start() c.AwaitVars("ready") // Wait for a bit to allow the zero-sleep commands to exit. time.Sleep(100 * time.Millisecond) c.Signal(s) switch { case s == os.Interrupt: // Wait should succeed as long as the exit code was 0, regardless of // whether the signal arrived or the process had already exited. c.Wait() case d != 0: // Note: We don't call Wait in the {d: 0, s: os.Kill} case because doing // so makes the test flaky on slow systems. setsErr(t, sh, func() { c.Wait() }) } } } // Signal should fail if Wait has been called. c := sh.FuncCmd(sleepFunc, time.Duration(0), 0) c.Run() setsErr(t, sh, func() { c.Signal(os.Interrupt) }) }
// Tests that we don't log command failures when ExitErrorIsOk or // ContinueOnError is set. func TestCmdFailureLoggingDisabled(t *testing.T) { tb := &customTB{t: t, buf: &bytes.Buffer{}} sh := gosh.NewShell(tb) defer sh.Cleanup() // If ExitErrorIsOk is set and the command fails, we shouldn't log anything. tb.Reset() c := sh.FuncCmd(exitFunc, 1) c.ExitErrorIsOk = true c.Run() eq(t, tb.calledFailNow, false) eq(t, tb.buf.String(), "") // If ContinueOnError is set and the command fails, we should log the exit // status but not the command stderr. tb.Reset() c = sh.FuncCmd(exitFunc, 1) sh.ContinueOnError = true c.Run() eq(t, tb.calledFailNow, false) got := tb.buf.String() if !strings.Contains(got, "exit status 1") { t.Fatalf("missing error: %s", got) } if strings.Contains(got, "STDERR") { t.Fatalf("should not log stderr: %s", got) } }
func TestPushdPopd(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() startDir, err := os.Getwd() ok(t, err) parentDir := filepath.Dir(startDir) neq(t, startDir, parentDir) sh.Pushd(parentDir) cwd, err := os.Getwd() ok(t, err) eq(t, cwd, parentDir) sh.Pushd(startDir) cwd, err = os.Getwd() ok(t, err) eq(t, cwd, startDir) sh.Popd() cwd, err = os.Getwd() ok(t, err) eq(t, cwd, parentDir) sh.Popd() cwd, err = os.Getwd() ok(t, err) eq(t, cwd, startDir) // The next sh.Popd() will fail. setsErr(t, sh, func() { sh.Popd() }) }
// Tests that it's safe to add os.Stdout and os.Stderr as writers. func TestAddStdoutStderrWriter(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() stdout, stderr := sh.FuncCmd(writeMoreFunc).StdoutStderr() eq(t, stdout, "AA stdout done") eq(t, stderr, "BB stderr done") }
// Tests that AwaitVars returns immediately when the process exits. func TestAwaitVarsProcessExit(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() c := sh.FuncCmd(exitFunc, 0) c.Start() setsErr(t, sh, func() { c.AwaitVars("foo") }) }
func TestMakeTempFile(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() file := sh.MakeTempFile() fi, err := file.Stat() ok(t, err) eq(t, fi.Mode().IsRegular(), true) }
func TestMakeTempDir(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() name := sh.MakeTempDir() fi, err := os.Stat(name) ok(t, err) eq(t, fi.Mode().IsDir(), true) }
func TestCustomTB(t *testing.T) { tb := &customTB{t: t} sh := gosh.NewShell(tb) defer sh.Cleanup() sh.HandleError(fakeError) // Note, our deferred sh.Cleanup() should succeed despite this error. nok(t, sh.Err) eq(t, tb.calledFailNow, true) }
func TestPushdNoPopdCleanup(t *testing.T) { startDir := getwdEvalSymlinks(t) sh := gosh.NewShell(t) tmpDir := sh.MakeTempDir() sh.Pushd(tmpDir) eq(t, getwdEvalSymlinks(t), evalSymlinks(t, tmpDir)) // There is no matching popd; the cwd is tmpDir, which is deleted by Cleanup. // Cleanup needs to put us back in startDir, otherwise all subsequent Pushd // calls will fail. sh.Cleanup() eq(t, getwdEvalSymlinks(t), startDir) }
// Tests that Shell.HandleError panics under various conditions. func TestHandleErrorPanics(t *testing.T) { func() { // errDidNotCallNewShell sh := gosh.Shell{} defer func() { neq(t, recover(), nil) }() sh.HandleError(fakeError) }() func() { // errShellErrIsNotNil sh := gosh.NewShell(t) sh.ContinueOnError = true defer sh.Cleanup() sh.Err = fakeError defer func() { neq(t, recover(), nil) }() sh.HandleError(fakeError) }() func() { // errAlreadyCalledCleanup sh := gosh.NewShell(t) sh.ContinueOnError = true sh.Cleanup() defer func() { neq(t, recover(), nil) }() sh.HandleError(fakeError) }() }
// Mirrors ExampleFuncCmd in internal/gosh_example/main.go. func TestFuncCmd(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() // Start server. c := sh.FuncCmd(serveFunc) c.Start() addr := c.AwaitVars("addr")["addr"] neq(t, addr, "") // Run client. c = sh.FuncCmd(getFunc, addr) eq(t, c.Stdout(), helloWorldStr) }
// Mirrors TestFuncCmd in shell_test.go. func ExampleFuncCmd() { sh := gosh.NewShell(nil) defer sh.Cleanup() // Start server. c := sh.FuncCmd(serveFunc) c.Start() addr := c.AwaitVars("addr")["addr"] fmt.Println(addr) // Run client. c = sh.FuncCmd(getFunc, addr) fmt.Print(c.Stdout()) }
// Tests that Shell.Cmd uses Shell.Vars["PATH"] to locate executables with // relative names. func TestLookPath(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() binDir := sh.MakeTempDir() sh.Vars["PATH"] = binDir + ":" + sh.Vars["PATH"] relName := "hw" absName := filepath.Join(binDir, relName) gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o", absName) c := sh.Cmd(relName) eq(t, c.Stdout(), helloWorldStr) // Test the case where we cannot find the executable. sh.Vars["PATH"] = "" setsErr(t, sh, func() { sh.Cmd("yes") }) }
func TestPipelineClosedPipe(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() writeLoop, readLine := sh.FuncCmd(writeLoopFunc), sh.FuncCmd(readFunc) // WriteLoop finishes because it gets a closed pipe write error after readLine // finishes. Note that the closed pipe error is ignored. p := gosh.NewPipeline(writeLoop, readLine) eq(t, p.Stdout(), "") ok(t, p.Cmds()[0].Err) ok(t, p.Cmds()[1].Err) p = p.Clone() eq(t, p.Stdout(), "") ok(t, p.Cmds()[0].Err) ok(t, p.Cmds()[1].Err) }
func TestCombinedOutput(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() c := sh.FuncCmd(writeFunc, true, true) buf := &bytes.Buffer{} c.AddStdoutWriter(buf) c.AddStderrWriter(buf) output := c.CombinedOutput() // Note, we can't assume any particular ordering of stdout and stderr, so we // simply check the length of the combined output. eq(t, len(output), 4) // The ordering must be the same, regardless of how we captured the combined // output. eq(t, output, buf.String()) }
// Mirrors TestCmd in shell_test.go. func ExampleCmd() { sh := gosh.NewShell(nil) defer sh.Cleanup() // Start server. binDir := sh.MakeTempDir() binPath := gosh.BuildGoPkg(sh, binDir, "github.com/asadovsky/gosh/internal/gosh_example_server") c := sh.Cmd(binPath) c.Start() addr := c.AwaitVars("addr")["addr"] fmt.Println(addr) // Run client. binPath = gosh.BuildGoPkg(sh, binDir, "github.com/asadovsky/gosh/internal/gosh_example_client") c = sh.Cmd(binPath, "-addr="+addr) fmt.Print(c.Stdout()) }
// Mirrors ExampleCmd in internal/gosh_example/main.go. func TestCmd(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() // Start server. binDir := sh.MakeTempDir() binPath := gosh.BuildGoPkg(sh, binDir, "github.com/asadovsky/gosh/internal/gosh_example_server") c := sh.Cmd(binPath) c.Start() addr := c.AwaitVars("addr")["addr"] neq(t, addr, "") // Run client. binPath = gosh.BuildGoPkg(sh, binDir, "github.com/asadovsky/gosh/internal/gosh_example_client") c = sh.Cmd(binPath, "-addr="+addr) eq(t, c.Stdout(), helloWorldStr) }
func TestStdin(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() // The "cat" command exits after the reader returns EOF. c := sh.FuncCmd(catFunc) c.SetStdinReader(strings.NewReader("foo\n")) eq(t, c.Stdout(), "foo\n") // The "cat" command exits after the reader returns EOF, so we must explicitly // close the stdin pipe. c = sh.FuncCmd(catFunc) stdin := c.StdinPipe() stdin.Write([]byte("foo\n")) stdin.Close() eq(t, c.Stdout(), "foo\n") // The "read" command exits when it sees a newline, so it is not necessary to // explicitly close the stdin pipe. c = sh.FuncCmd(readFunc) stdin = c.StdinPipe() stdin.Write([]byte("foo\n")) c.Run() // No stdin, so cat should exit immediately. c = sh.FuncCmd(catFunc) eq(t, c.Stdout(), "") // It's an error to call both StdinPipe and SetStdinReader. c = sh.FuncCmd(catFunc) c.StdinPipe() setsErr(t, sh, func() { c.StdinPipe() }) c = sh.FuncCmd(catFunc) c.StdinPipe() setsErr(t, sh, func() { c.SetStdinReader(strings.NewReader("")) }) c = sh.FuncCmd(catFunc) c.SetStdinReader(strings.NewReader("")) setsErr(t, sh, func() { c.StdinPipe() }) c = sh.FuncCmd(catFunc) c.SetStdinReader(strings.NewReader("")) setsErr(t, sh, func() { c.SetStdinReader(strings.NewReader("")) }) }
func TestCleanupProcessGroup(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() c := sh.FuncCmd(processGroup, 5) c.Start() pids := c.AwaitVars("pids")["pids"] c.Signal(os.Interrupt) // Wait for all processes in the child's process group to exit. for syscall.Kill(-c.Pid(), 0) != syscall.ESRCH { time.Sleep(100 * time.Millisecond) } for _, pid := range strings.Split(pids, ",") { p, _ := strconv.Atoi(pid) eq(t, syscall.Kill(p, 0), syscall.ESRCH) } }
// Tests that Shell.HandleError logs errors using an appropriate runtime.Caller // skip value. func TestHandleErrorLogging(t *testing.T) { tb := &customTB{t: t, buf: &bytes.Buffer{}} sh := gosh.NewShell(tb) defer sh.Cleanup() // Call HandleError, then check that the stack trace and error got logged. tb.Reset() sh.HandleError(fakeError) _, file, line, _ := runtime.Caller(0) got, wantSuffix := tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError) if !strings.HasSuffix(got, wantSuffix) { t.Fatalf("got %v, want suffix %v", got, wantSuffix) } if got == wantSuffix { t.Fatalf("missing stack trace: %v", got) } sh.Err = nil // Same as above, but with ContinueOnError set to true. Only the error should // get logged. sh.ContinueOnError = true tb.Reset() sh.HandleError(fakeError) _, file, line, _ = runtime.Caller(0) got, want := tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError) eq(t, got, want) sh.Err = nil // Same as above, but calling HandleErrorWithSkip, with skip set to 1. tb.Reset() sh.HandleErrorWithSkip(fakeError, 1) _, file, line, _ = runtime.Caller(0) got, want = tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line-1, fakeError) eq(t, got, want) sh.Err = nil // Same as above, but with skip set to 2. tb.Reset() sh.HandleErrorWithSkip(fakeError, 2) _, file, line, _ = runtime.Caller(1) got, want = tb.buf.String(), fmt.Sprintf("%s:%d: %v\n", filepath.Base(file), line, fakeError) eq(t, got, want) sh.Err = nil }
// Tests that when a command fails, we log the head and tail of its stdout and // stderr. func TestCmdFailureLoggingEnabled(t *testing.T) { tb := &customTB{t: t, buf: &bytes.Buffer{}} sh := gosh.NewShell(tb) defer sh.Cleanup() const k = 1 << 15 // Note: When a FuncCmd fails, InitMain calls log.Fatal(err), which writes err // to stderr. In several places below, our expected stderr must accommodate // this logged fakeError string. tests := []struct { nStdout int nStderr int wantStdout string wantStderr string }{ {0, 0, "[ empty ]", ""}, {1, 1, "A", "B"}, {k, k, strings.Repeat("A", k), strings.Repeat("B", k)}, {k + 1, k + 1, strings.Repeat("A", k+1), strings.Repeat("B", k+1)}, // Stderr includes fakeError. {2 * k, 2 * k, strings.Repeat("A", 2*k), strings.Repeat("B", k) + "\n[ ... skipping "}, // Stderr includes fakeError. {2*k + 1, 2*k + 1, strings.Repeat("A", k) + "\n[ ... skipping 1 bytes ... ]\n" + strings.Repeat("A", k), strings.Repeat("B", k) + "\n[ ... skipping "}, } sep := strings.Repeat("-", 40) for _, test := range tests { tb.Reset() sh.FuncCmd(cmdFailureFunc, test.nStdout, test.nStderr).Run() got := tb.buf.String() wantStdout := fmt.Sprintf("\nSTDOUT\n%s\n%s\n", sep, test.wantStdout) if !strings.Contains(got, wantStdout) { t.Fatalf("got %v, want substring %v", got, wantStdout) } // Stderr includes fakeError. wantStderr := fmt.Sprintf("\nSTDERR\n%s\n%s", sep, test.wantStderr) if !strings.Contains(got, wantStderr) { t.Fatalf("got %v, want substring %v", got, wantStderr) } sh.Err = nil } }
func TestIgnoreClosedPipeError(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() // Since writeLoopFunc will only finish if it receives a write error, it's // depending on the closed pipe error from closedPipeErrorWriter. c := sh.FuncCmd(writeLoopFunc) c.AddStdoutWriter(errorWriter{io.ErrClosedPipe}) c.IgnoreClosedPipeError = true c.Run() ok(t, c.Err) ok(t, sh.Err) // Without IgnoreClosedPipeError, the command fails. c = sh.FuncCmd(writeLoopFunc) c.AddStdoutWriter(errorWriter{io.ErrClosedPipe}) setsErr(t, sh, func() { c.Run() }) nok(t, c.Err) }
// Tests BuildGoPkg's handling of the -o flag. func TestBuildGoPkg(t *testing.T) { if testing.Short() { t.Skip() } sh := gosh.NewShell(t) defer sh.Cleanup() // Set -o to an absolute name. relName := "hw" absName := filepath.Join(sh.MakeTempDir(), relName) eq(t, gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o", absName), absName) c := sh.Cmd(absName) eq(t, c.Stdout(), helloWorldStr) // Set -o to a relative name with no path separators. binDir := sh.MakeTempDir() absName = filepath.Join(binDir, relName) eq(t, gosh.BuildGoPkg(sh, binDir, helloWorldPkg, "-o", relName), absName) c = sh.Cmd(absName) eq(t, c.Stdout(), helloWorldStr) // Set -o to a relative name that contains a path separator. relNameWithSlash := filepath.Join("subdir", relName) absName = filepath.Join(binDir, relNameWithSlash) eq(t, gosh.BuildGoPkg(sh, binDir, helloWorldPkg, "-o", relNameWithSlash), absName) c = sh.Cmd(absName) eq(t, c.Stdout(), helloWorldStr) // Missing location after -o. setsErr(t, sh, func() { gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o") }) // Multiple -o. absName = filepath.Join(sh.MakeTempDir(), relName) gosh.BuildGoPkg(sh, "", helloWorldPkg, "-o", relName, "-o", absName) c = sh.Cmd(absName) eq(t, c.Stdout(), helloWorldStr) // Use --o instead of -o. absName = filepath.Join(sh.MakeTempDir(), relName) gosh.BuildGoPkg(sh, "", helloWorldPkg, "--o", absName) c = sh.Cmd(absName) eq(t, c.Stdout(), helloWorldStr) }
func TestShellWait(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() d0 := time.Duration(0) d200 := 200 * time.Millisecond c0 := sh.FuncCmd(sleepFunc, d0, 0) // not started c1 := sh.Cmd("/#invalid#/!binary!") // failed to start c2 := sh.FuncCmd(sleepFunc, d200, 0) // running and will succeed c3 := sh.FuncCmd(sleepFunc, d200, 1) // running and will fail c4 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded c5 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded, called wait c6 := sh.FuncCmd(sleepFunc, d0, 1) // failed c7 := sh.FuncCmd(sleepFunc, d0, 1) // failed, called wait c3.ExitErrorIsOk = true c6.ExitErrorIsOk = true c7.ExitErrorIsOk = true // Make sure c1 fails to start. This indirectly tests that Shell.Cleanup works // even if Cmd.Start failed. setsErr(t, sh, c1.Start) // Start commands, then wait for them to exit. for _, c := range []*gosh.Cmd{c2, c3, c4, c5, c6, c7} { c.Start() } // Wait for a bit to allow the zero-sleep commands to exit. time.Sleep(100 * time.Millisecond) c5.Wait() c7.Wait() sh.Wait() // It should be possible to run existing unstarted commands, and to create and // run new commands, after calling Shell.Wait. c0.Run() sh.FuncCmd(sleepFunc, d0, 0).Run() sh.FuncCmd(sleepFunc, d0, 0).Start() // Call Shell.Wait again. sh.Wait() }
// Tests that AwaitVars works under various conditions. func TestAwaitVars(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() c := sh.FuncCmd(sendVarsFunc, map[string]string{"a": "1"}) c.Start() eq(t, c.AwaitVars("a")["a"], "1") c = sh.FuncCmd(stderrFunc, `<goshVars{"a":"1","b":"2"}goshVars>`) c.Start() vars := c.AwaitVars("a", "b") eq(t, vars["a"], "1") eq(t, vars["b"], "2") c = sh.FuncCmd(stderrFunc, `<goshVars{"a":"1"}goshVars><gosh`) c.Start() eq(t, c.AwaitVars("a")["a"], "1") c = sh.FuncCmd(stderrFunc, `<goshVars{"a":"1"}goshVars><goshVars{"b":"2"}goshVars>`) c.Start() vars = c.AwaitVars("a", "b") eq(t, vars["a"], "1") eq(t, vars["b"], "2") c = sh.FuncCmd(stderrFunc, `<goshVars{"a":"1","b":"2"}goshVars>`) c.Start() vars = c.AwaitVars("a") eq(t, vars["a"], "1") eq(t, vars["b"], "") vars = c.AwaitVars("b") eq(t, vars["a"], "") eq(t, vars["b"], "2") c = sh.FuncCmd(stderrFunc, `<g<goshVars{"a":"goshVars"}goshVars>s><goshVars`) c.Start() eq(t, c.AwaitVars("a")["a"], "goshVars") c = sh.FuncCmd(stderrFunc, `<<goshVars{"a":"1"}goshVars>><<goshVars{"b":"<goshVars"}goshVars>>`) c.Start() vars = c.AwaitVars("a", "b") eq(t, vars["a"], "1") eq(t, vars["b"], "<goshVars") }
func TestStdinPipeWriteUntilExit(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() // Ensure that Write calls on stdin fail after the process exits. Note that we // write to the command's stdin concurrently with the command's exit waiter // goroutine closing stdin. Use "go test -race" catch races. // // Set a non-zero exit code, so that os.Exit exits immediately. See the // implementation of https://golang.org/pkg/os/#Exit for details. c := sh.FuncCmd(exitFunc, 1) c.ExitErrorIsOk = true stdin := c.StdinPipe() c.Start() for { if _, err := stdin.Write([]byte("a")); err != nil { return } } }
// Tests function signature-checking and execution. func TestRegistry(t *testing.T) { sh := gosh.NewShell(t) defer sh.Cleanup() // Variadic functions. Non-variadic functions are sufficiently covered in // other tests. eq(t, sh.FuncCmd(printFunc).Stdout(), "") eq(t, sh.FuncCmd(printFunc, 0).Stdout(), "0") eq(t, sh.FuncCmd(printFunc, 0, "foo").Stdout(), "0foo") eq(t, sh.FuncCmd(printfFunc, "").Stdout(), "") eq(t, sh.FuncCmd(printfFunc, "%v", 0).Stdout(), "0") eq(t, sh.FuncCmd(printfFunc, "%v%v", 0, "foo").Stdout(), "0foo") eq(t, sh.FuncCmd(printIntsFunc, 1, 2).Stdout(), "1 2") eq(t, sh.FuncCmd(printfIntsFunc, "%v %v", 1, 2).Stdout(), "1 2") // Too few arguments. setsErr(t, sh, func() { sh.FuncCmd(exitFunc) }) setsErr(t, sh, func() { sh.FuncCmd(sleepFunc, time.Second) }) setsErr(t, sh, func() { sh.FuncCmd(printfFunc) }) // Too many arguments. setsErr(t, sh, func() { sh.FuncCmd(exitFunc, 0, 0) }) setsErr(t, sh, func() { sh.FuncCmd(sleepFunc, time.Second, 0, 0) }) // Wrong argument types. setsErr(t, sh, func() { sh.FuncCmd(exitFunc, "foo") }) setsErr(t, sh, func() { sh.FuncCmd(sleepFunc, 0, 0) }) setsErr(t, sh, func() { sh.FuncCmd(printfFunc, 0) }) setsErr(t, sh, func() { sh.FuncCmd(printfFunc, 0, 0) }) // Wrong variadic argument types. setsErr(t, sh, func() { sh.FuncCmd(printIntsFunc, 0.5) }) setsErr(t, sh, func() { sh.FuncCmd(printIntsFunc, 0, 0.5) }) setsErr(t, sh, func() { sh.FuncCmd(printfIntsFunc, "%v", 0.5) }) setsErr(t, sh, func() { sh.FuncCmd(printfIntsFunc, "%v", 0, 0.5) }) // Unsupported argument types. var p *int setsErr(t, sh, func() { sh.FuncCmd(printFunc, p) }) setsErr(t, sh, func() { sh.FuncCmd(printfFunc, "%v", p) }) }