blob: 89a955582f5e45c2524bd2541460a27a2e4907ae [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gosh_test
// TODO(sadovsky): Add more tests:
// - variadic function registration and invocation
// - effects of Shell.Cleanup
// - Cmd.{Wait,Run}
// - Shell.{Args,Wait,Rename,MakeTempFile,MakeTempDir}
// - Opts (including defaulting behavior)
// - {,Maybe}WatchParent
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime/debug"
"testing"
"time"
"v.io/x/lib/gosh"
"v.io/x/lib/gosh/internal/gosh_example_lib"
)
var fakeError = errors.New("fake error")
func fatal(t *testing.T, v ...interface{}) {
debug.PrintStack()
t.Fatal(v...)
}
func fatalf(t *testing.T, format string, v ...interface{}) {
debug.PrintStack()
t.Fatalf(format, v...)
}
func ok(t *testing.T, err error) {
if err != nil {
fatal(t, err)
}
}
func nok(t *testing.T, err error) {
if err == nil {
fatal(t, "nil err")
}
}
func eq(t *testing.T, got, want interface{}) {
if !reflect.DeepEqual(got, want) {
fatalf(t, "got %v, want %v", got, want)
}
}
func neq(t *testing.T, got, notWant interface{}) {
if reflect.DeepEqual(got, notWant) {
fatalf(t, "got %v", got)
}
}
func makeErrorf(t *testing.T) func(string, ...interface{}) {
return func(format string, v ...interface{}) {
debug.PrintStack()
t.Fatalf(format, v...)
}
}
func TestPushdPopd(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
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.
var calledErrorf bool
sh.Opts.Errorf = func(string, ...interface{}) { calledErrorf = true }
sh.Popd()
// Note, our deferred sh.Cleanup() should succeed despite this error.
nok(t, sh.Err)
eq(t, calledErrorf, true)
}
func TestPushdNoPopdCleanup(t *testing.T) {
startDir, err := os.Getwd()
ok(t, err)
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
tmpDir := sh.MakeTempDir()
sh.Pushd(tmpDir)
cwd, err := os.Getwd()
ok(t, err)
eq(t, cwd, 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()
cwd, err = os.Getwd()
ok(t, err)
eq(t, cwd, startDir)
}
func TestCmds(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
defer sh.Cleanup()
// Start server.
binPath := sh.BuildGoPkg("v.io/x/lib/gosh/internal/gosh_example_server")
c := sh.Cmd(binPath)
c.Start()
c.AwaitReady()
addr := c.AwaitVars("Addr")["Addr"]
neq(t, addr, "")
// Run client.
binPath = sh.BuildGoPkg("v.io/x/lib/gosh/internal/gosh_example_client")
c = sh.Cmd(binPath, "-addr="+addr)
stdout, _ := c.Output()
eq(t, stdout, "Hello, world!\n")
}
var (
get = gosh.Register("get", lib.Get)
serve = gosh.Register("serve", lib.Serve)
)
func TestFns(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
defer sh.Cleanup()
// Start server.
c := sh.Fn(serve)
c.Start()
c.AwaitReady()
addr := c.AwaitVars("Addr")["Addr"]
neq(t, addr, "")
// Run client.
c = sh.Fn(get, addr)
stdout, _ := c.Output()
eq(t, stdout, "Hello, world!\n")
}
func TestShellMain(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
defer sh.Cleanup()
stdout, _ := sh.Main(lib.HelloWorldMain).Output()
eq(t, stdout, "Hello, world!\n")
}
var write = gosh.Register("write", func(stdout, stderr bool) error {
tenMs := 10 * time.Millisecond
if stdout {
time.Sleep(tenMs)
if _, err := os.Stdout.Write([]byte("A")); err != nil {
return err
}
}
if stderr {
time.Sleep(tenMs)
if _, err := os.Stderr.Write([]byte("B")); err != nil {
return err
}
}
if stdout {
time.Sleep(tenMs)
if _, err := os.Stdout.Write([]byte("A")); err != nil {
return err
}
}
if stderr {
time.Sleep(tenMs)
if _, err := os.Stderr.Write([]byte("B")); err != nil {
return err
}
}
return nil
})
func toString(r io.Reader) string {
if b, err := ioutil.ReadAll(r); err != nil {
panic(err)
} else {
return string(b)
}
}
func TestStdoutStderr(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
defer sh.Cleanup()
// Write to stdout only.
c := sh.Fn(write, true, false)
stdoutPipe, stderrPipe := c.StdoutPipe(), c.StderrPipe()
eq(t, c.CombinedOutput(), "AA")
eq(t, toString(stdoutPipe), "AA")
eq(t, toString(stderrPipe), "")
stdout, stderr := sh.Fn(write, true, false).Output()
eq(t, stdout, "AA")
eq(t, stderr, "")
// Write to stderr only.
c = sh.Fn(write, false, true)
stdoutPipe, stderrPipe = c.StdoutPipe(), c.StderrPipe()
eq(t, c.CombinedOutput(), "BB")
eq(t, toString(stdoutPipe), "")
eq(t, toString(stderrPipe), "BB")
stdout, stderr = sh.Fn(write, false, true).Output()
eq(t, stdout, "")
eq(t, stderr, "BB")
// Write to both stdout and stderr.
c = sh.Fn(write, true, true)
stdoutPipe, stderrPipe = c.StdoutPipe(), c.StderrPipe()
eq(t, c.CombinedOutput(), "ABAB")
eq(t, toString(stdoutPipe), "AA")
eq(t, toString(stderrPipe), "BB")
stdout, stderr = sh.Fn(write, true, true).Output()
eq(t, stdout, "AA")
eq(t, stderr, "BB")
}
var sleep = gosh.Register("sleep", func(d time.Duration) {
time.Sleep(d)
})
func TestShutdown(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
defer sh.Cleanup()
for _, d := range []time.Duration{0, time.Second} {
for _, s := range []os.Signal{os.Interrupt, os.Kill} {
fmt.Println(d, s)
c := sh.Fn(sleep, d)
c.Start()
time.Sleep(10 * time.Millisecond)
c.Shutdown(s)
}
}
}
var exit = gosh.Register("exit", func(code int) {
os.Exit(code)
})
func TestExitErrorIsOk(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
defer sh.Cleanup()
// Exit code 0 is not an error.
c := sh.Fn(exit, 0)
c.Run()
ok(t, c.Err)
ok(t, sh.Err)
// Exit code 1 is an error.
c = sh.Fn(exit, 1)
c.ExitErrorIsOk = true
c.Run()
nok(t, c.Err)
ok(t, sh.Err)
// If ExitErrorIsOk is false, exit code 1 triggers sh.HandleError.
sh.Opts.Errorf = func(string, ...interface{}) {}
c = sh.Fn(exit, 1)
c.Run()
nok(t, c.Err)
nok(t, sh.Err)
}
// Tests that sh.Ok panics under various conditions.
func TestOkPanics(t *testing.T) {
func() { // errDidNotCallNewShell
sh := gosh.Shell{}
defer func() { neq(t, recover(), nil) }()
sh.Ok()
}()
func() { // errShellErrIsNotNil
sh := gosh.NewShell(gosh.Opts{Errorf: t.Logf})
defer sh.Cleanup()
sh.Err = fakeError
defer func() { neq(t, recover(), nil) }()
sh.Ok()
}()
func() { // errAlreadyCalledCleanup
sh := gosh.NewShell(gosh.Opts{Errorf: t.Logf})
sh.Cleanup()
defer func() { neq(t, recover(), nil) }()
sh.Ok()
}()
}
// Tests that sh.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(gosh.Opts{Errorf: t.Logf})
defer sh.Cleanup()
sh.Err = fakeError
defer func() { neq(t, recover(), nil) }()
sh.HandleError(fakeError)
}()
func() { // errAlreadyCalledCleanup
sh := gosh.NewShell(gosh.Opts{Errorf: t.Logf})
sh.Cleanup()
defer func() { neq(t, recover(), nil) }()
sh.HandleError(fakeError)
}()
}
// Tests that sh.Cleanup panics under various conditions.
func TestCleanupPanics(t *testing.T) {
func() { // errDidNotCallNewShell
sh := gosh.Shell{}
defer func() { neq(t, recover(), nil) }()
sh.Cleanup()
}()
}
// Tests that sh.Cleanup succeeds even if sh.Err is not nil.
func TestCleanupAfterError(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
sh.Err = fakeError
sh.Cleanup()
}
// Tests that sh.Cleanup can be called multiple times.
func TestMultipleCleanup(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
sh.Cleanup()
sh.Cleanup()
}
func TestMain(m *testing.M) {
os.Exit(gosh.Run(m.Run))
}