veyron/lib/signals, veyron2/vlog: use new modules package for testing.
Change-Id: I28a4653530c5627d242cf82eced085a4b27d14d1
diff --git a/lib/expect/expect.go b/lib/expect/expect.go
index 8f0bb72..4950758 100644
--- a/lib/expect/expect.go
+++ b/lib/expect/expect.go
@@ -198,6 +198,27 @@
return
}
+// Expectf asserts that the next line in the input matches the result of
+// formatting the supplied arguments. It's equivalent to
+// Expect(fmt.Sprintf(args))
+func (s *Session) Expectf(format string, args ...interface{}) {
+ if s.Failed() {
+ return
+ }
+ line, err := s.read(readLine)
+ s.log(err, "Expect: %s", line)
+ if err != nil {
+ s.error(err)
+ return
+ }
+ line = strings.TrimRight(line, "\n")
+ expected := fmt.Sprintf(format, args...)
+ if line != expected {
+ s.error(fmt.Errorf("got %q, want %q", line, expected))
+ }
+ return
+}
+
func (s *Session) expectRE(pattern string, n int) (string, [][]string, error) {
if s.Failed() {
return "", nil, s.err
@@ -276,6 +297,23 @@
return s.read(readAll)
}
+func (s *Session) ExpectEOF() error {
+ if s.Failed() {
+ return s.err
+ }
+ buf := [1024]byte{}
+ n, err := s.input.Read(buf[:])
+ if n != 0 || err == nil {
+ s.error(fmt.Errorf("unexpected input %d bytes: %q", n, string(buf[:n])))
+ return s.err
+ }
+ if err != io.EOF {
+ s.error(err)
+ return s.err
+ }
+ return nil
+}
+
// Finish reads all remaining input on the stream regardless of any
// prior errors and writes it to the supplied io.Writer parameter if non-nil.
// It returns both the data read and the prior error, if any, otherwise it
diff --git a/lib/expect/expect_test.go b/lib/expect/expect_test.go
index 1eadda2..c85d803 100644
--- a/lib/expect/expect_test.go
+++ b/lib/expect/expect_test.go
@@ -31,6 +31,34 @@
} else {
t.Log(s.Error())
}
+ s.ExpectEOF()
+}
+
+func TestExpectf(t *testing.T) {
+ buf := []byte{}
+ buffer := bytes.NewBuffer(buf)
+ buffer.WriteString("bar 22\n")
+ s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+ s.Expectf("bar %d", 22)
+ if err := s.Error(); err != nil {
+ t.Error(err)
+ }
+ s.ExpectEOF()
+}
+
+func TestEOF(t *testing.T) {
+ buf := []byte{}
+ buffer := bytes.NewBuffer(buf)
+ buffer.WriteString("bar 22\n")
+ buffer.WriteString("baz 22\n")
+ s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+ s.Expectf("bar %d", 22)
+ s.ExpectEOF()
+ if err := s.Error(); err == nil {
+ t.Error("unexpected success")
+ } else {
+ t.Log(s.Error())
+ }
}
func TestExpectRE(t *testing.T) {
@@ -56,6 +84,7 @@
t.Errorf("missing or wrong error: %v", s.Error())
}
}
+ s.ExpectEOF()
}
func TestRead(t *testing.T) {
@@ -87,4 +116,5 @@
if got != want {
t.Errorf("got %q, want %q", got, want)
}
+ s.ExpectEOF()
}
diff --git a/lib/modules/exec.go b/lib/modules/exec.go
index 5be34de..db7f7be 100644
--- a/lib/modules/exec.go
+++ b/lib/modules/exec.go
@@ -163,6 +163,10 @@
return eh, err
}
+func (eh *execHandle) Pid() int {
+ return eh.cmd.Process.Pid
+}
+
func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error {
eh.mu.Lock()
defer eh.mu.Unlock()
@@ -217,7 +221,7 @@
os.Exit(0)
}
-// Dispatch will execute the request subprocess command from a within a
+// Dispatch will execute the requested subprocess command from a within a
// a subprocess that is not a unit test.
func Dispatch() error {
if IsTestHelperProcess() {
diff --git a/lib/modules/func.go b/lib/modules/func.go
index 6273c64..c8e229c 100644
--- a/lib/modules/func.go
+++ b/lib/modules/func.go
@@ -88,6 +88,10 @@
return fh, nil
}
+func (eh *functionHandle) Pid() int {
+ return os.Getpid()
+}
+
func (fh *functionHandle) Shutdown(stdout_w, stderr_w io.Writer) error {
fh.mu.Lock()
fh.stdin.w.Close()
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index 26c3869..b467fbc 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -281,6 +281,9 @@
// The stdout and stderr contents are written to the corresponding
// io.Writers if they are non-nil, otherwise the content is discarded.
Shutdown(stdout, stderr io.Writer) error
+
+ // Pid returns the pid of the process running the command
+ Pid() int
}
// command is used to abstract the implementations of inprocess and subprocess
diff --git a/lib/signals/signals_test.go b/lib/signals/signals_test.go
index 813545f..3114304 100644
--- a/lib/signals/signals_test.go
+++ b/lib/signals/signals_test.go
@@ -1,10 +1,15 @@
package signals
import (
+ "bufio"
"fmt"
+ "io"
"os"
+ "path/filepath"
+ "runtime"
"syscall"
"testing"
+ "time"
"veyron.io/veyron/veyron2"
"veyron.io/veyron/veyron2/ipc"
@@ -13,29 +18,31 @@
"veyron.io/veyron/veyron2/rt"
"veyron.io/veyron/veyron2/services/mgmt/appcycle"
+ "veyron.io/veyron/veyron/lib/expect"
+ "veyron.io/veyron/veyron/lib/modules"
_ "veyron.io/veyron/veyron/lib/testutil"
- "veyron.io/veyron/veyron/lib/testutil/blackbox"
"veyron.io/veyron/veyron/lib/testutil/security"
"veyron.io/veyron/veyron/profiles"
vflag "veyron.io/veyron/veyron/security/flag"
"veyron.io/veyron/veyron/services/mgmt/node"
)
-// TestHelperProcess is boilerplate for the blackbox setup.
+// TestHelperProcess is boilerplate for the modules setup.
func TestHelperProcess(t *testing.T) {
- blackbox.HelperProcess(t)
+ modules.DispatchInTest()
}
func init() {
- blackbox.CommandTable["handleDefaults"] = handleDefaults
- blackbox.CommandTable["handleCustom"] = handleCustom
- blackbox.CommandTable["handleCustomWithStop"] = handleCustomWithStop
- blackbox.CommandTable["handleDefaultsIgnoreChan"] = handleDefaultsIgnoreChan
+ modules.RegisterChild("handleDefaults", handleDefaults)
+ modules.RegisterChild("handleCustom", handleCustom)
+ modules.RegisterChild("handleCustomWithStop", handleCustomWithStop)
+ modules.RegisterChild("handleDefaultsIgnoreChan", handleDefaultsIgnoreChan)
}
-func stopLoop(ch chan<- struct{}) {
- for {
- switch blackbox.ReadLineFromStdin() {
+func stopLoop(stdin io.Reader, ch chan<- struct{}) {
+ scanner := bufio.NewScanner(stdin)
+ for scanner.Scan() {
+ switch scanner.Text() {
case "close":
close(ch)
return
@@ -45,36 +52,40 @@
}
}
-func program(signals ...os.Signal) {
+func program(stdin io.Reader, stdout io.Writer, signals ...os.Signal) {
r := rt.Init()
closeStopLoop := make(chan struct{})
- go stopLoop(closeStopLoop)
+ go stopLoop(stdin, closeStopLoop)
wait := ShutdownOnSignals(signals...)
- fmt.Println("ready")
- fmt.Println("received signal", <-wait)
+ fmt.Fprintf(stdout, "ready\n")
+ fmt.Fprintf(stdout, "received signal %s\n", <-wait)
r.Cleanup()
<-closeStopLoop
}
-func handleDefaults([]string) {
- program()
+func handleDefaults(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ program(stdin, stdout)
+ return nil
}
-func handleCustom([]string) {
- program(syscall.SIGABRT)
+func handleCustom(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ program(stdin, stdout, syscall.SIGABRT)
+ return nil
}
-func handleCustomWithStop([]string) {
- program(STOP, syscall.SIGABRT, syscall.SIGHUP)
+func handleCustomWithStop(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ program(stdin, stdout, STOP, syscall.SIGABRT, syscall.SIGHUP)
+ return nil
}
-func handleDefaultsIgnoreChan([]string) {
+func handleDefaultsIgnoreChan(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
defer rt.Init().Cleanup()
closeStopLoop := make(chan struct{})
- go stopLoop(closeStopLoop)
+ go stopLoop(stdin, closeStopLoop)
ShutdownOnSignals()
- fmt.Println("ready")
+ fmt.Fprintf(stdout, "ready\n")
<-closeStopLoop
+ return nil
}
func isSignalInSet(sig os.Signal, set []os.Signal) bool {
@@ -98,113 +109,128 @@
}
}
+func newShell(t *testing.T, command string) (*modules.Shell, modules.Handle, *expect.Session) {
+ sh := modules.NewShell()
+ sh.AddSubprocess(command, "")
+ handle, err := sh.Start(command)
+ if err != nil {
+ sh.Cleanup(os.Stderr, os.Stderr)
+ t.Fatalf("unexpected error: %s", err)
+ return nil, nil, nil
+ }
+ session := expect.NewSession(t, handle.Stdout(), time.Minute)
+ return sh, handle, session
+}
+
// TestCleanShutdownSignal verifies that sending a signal to a child that
// handles it by default causes the child to shut down cleanly.
func TestCleanShutdownSignal(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleDefaults")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
+ sh, h, s := newShell(t, "handleDefaults")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
checkSignalIsDefault(t, syscall.SIGINT)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGINT)
- c.Expect(fmt.Sprintf("received signal %s", syscall.SIGINT))
- c.WriteLine("close")
- c.ExpectEOFAndWait()
+ syscall.Kill(h.Pid(), syscall.SIGINT)
+ s.Expectf("received signal %s", syscall.SIGINT)
+ fmt.Fprintf(h.Stdin(), "close\n")
+ s.ExpectEOF()
}
// TestCleanShutdownStop verifies that sending a stop comamnd to a child that
// handles stop commands by default causes the child to shut down cleanly.
func TestCleanShutdownStop(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleDefaults")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
- c.WriteLine("stop")
- c.Expect(fmt.Sprintf("received signal %s", veyron2.LocalStop))
- c.WriteLine("close")
- c.ExpectEOFAndWait()
+ sh, h, s := newShell(t, "handleDefaults")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
+ fmt.Fprintf(h.Stdin(), "stop\n")
+ s.Expectf("received signal %s", veyron2.LocalStop)
+ fmt.Fprintf(h.Stdin(), "close\n")
+ s.ExpectEOF()
+
}
// TestCleanShutdownStopCustom verifies that sending a stop comamnd to a child
// that handles stop command as part of a custom set of signals handled, causes
// the child to shut down cleanly.
func TestCleanShutdownStopCustom(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleCustomWithStop")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
- c.WriteLine("stop")
- c.Expect(fmt.Sprintf("received signal %s", veyron2.LocalStop))
- c.WriteLine("close")
- c.ExpectEOFAndWait()
+ sh, h, s := newShell(t, "handleCustomWithStop")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
+ fmt.Fprintf(h.Stdin(), "stop\n")
+ s.Expectf("received signal %s", veyron2.LocalStop)
+ fmt.Fprintf(h.Stdin(), "close\n")
+ s.ExpectEOF()
+}
+
+func testExitStatus(t *testing.T, h modules.Handle, s *expect.Session, code int) {
+ s.ExpectEOF()
+ _, file, line, _ := runtime.Caller(1)
+ file = filepath.Base(file)
+ if got, want := h.Shutdown(os.Stdout, os.Stderr), fmt.Errorf("exit status %d", code); got.Error() != want.Error() {
+ t.Errorf("%s:%d: got %q, want %q", file, line, got, want)
+ }
}
// TestStopNoHandler verifies that sending a stop command to a child that does
// not handle stop commands causes the child to exit immediately.
func TestStopNoHandler(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleCustom")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
- c.WriteLine("stop")
- c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", veyron2.UnhandledStopExitCode))
+ sh, h, s := newShell(t, "handleCustom")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
+ fmt.Fprintf(h.Stdin(), "stop\n")
+ testExitStatus(t, h, s, veyron2.UnhandledStopExitCode)
}
// TestDoubleSignal verifies that sending a succession of two signals to a child
// that handles these signals by default causes the child to exit immediately
// upon receiving the second signal.
func TestDoubleSignal(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleDefaults")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
+ sh, h, s := newShell(t, "handleDefaults")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
checkSignalIsDefault(t, syscall.SIGTERM)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGTERM)
- c.Expect(fmt.Sprintf("received signal %s", syscall.SIGTERM))
+ syscall.Kill(h.Pid(), syscall.SIGTERM)
+ s.Expectf("received signal %s", syscall.SIGTERM)
checkSignalIsDefault(t, syscall.SIGINT)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGINT)
- c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
+ syscall.Kill(h.Pid(), syscall.SIGINT)
+ testExitStatus(t, h, s, DoubleStopExitCode)
}
// TestSignalAndStop verifies that sending a signal followed by a stop command
// to a child that handles these by default causes the child to exit immediately
// upon receiving the stop command.
func TestSignalAndStop(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleDefaults")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
+ sh, h, s := newShell(t, "handleDefaults")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
checkSignalIsDefault(t, syscall.SIGTERM)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGTERM)
- c.Expect(fmt.Sprintf("received signal %s", syscall.SIGTERM))
- c.WriteLine("stop")
- c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
+ syscall.Kill(h.Pid(), syscall.SIGTERM)
+ s.Expectf("received signal %s", syscall.SIGTERM)
+ fmt.Fprintf(h.Stdin(), "stop\n")
+ testExitStatus(t, h, s, DoubleStopExitCode)
}
// TestDoubleStop verifies that sending a succession of stop commands to a child
// that handles stop commands by default causes the child to exit immediately
// upon receiving the second stop command.
func TestDoubleStop(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleDefaults")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
- c.WriteLine("stop")
- c.Expect(fmt.Sprintf("received signal %s", veyron2.LocalStop))
- c.WriteLine("stop")
- c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
+ sh, h, s := newShell(t, "handleDefaults")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
+ fmt.Fprintf(h.Stdin(), "stop\n")
+ s.Expectf("received signal %s", veyron2.LocalStop)
+ fmt.Fprintf(h.Stdin(), "stop\n")
+ testExitStatus(t, h, s, DoubleStopExitCode)
}
// TestSendUnhandledSignal verifies that sending a signal that the child does
// not handle causes the child to exit as per the signal being sent.
func TestSendUnhandledSignal(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleDefaults")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
+ sh, h, s := newShell(t, "handleDefaults")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
checkSignalIsNotDefault(t, syscall.SIGABRT)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGABRT)
- c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status 2"))
+ syscall.Kill(h.Pid(), syscall.SIGABRT)
+ testExitStatus(t, h, s, 2)
}
// TestDoubleSignalIgnoreChan verifies that, even if we ignore the channel that
@@ -212,31 +238,29 @@
// process to exit (ensures that there is no dependency in ShutdownOnSignals
// on having a goroutine read from the returned channel).
func TestDoubleSignalIgnoreChan(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleDefaultsIgnoreChan")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
+ sh, h, s := newShell(t, "handleDefaultsIgnoreChan")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
// Even if we ignore the channel that ShutdownOnSignals returns,
// sending two signals should still cause the process to exit.
checkSignalIsDefault(t, syscall.SIGTERM)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGTERM)
+ syscall.Kill(h.Pid(), syscall.SIGTERM)
checkSignalIsDefault(t, syscall.SIGINT)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGINT)
- c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
+ syscall.Kill(h.Pid(), syscall.SIGINT)
+ testExitStatus(t, h, s, DoubleStopExitCode)
}
// TestHandlerCustomSignal verifies that sending a non-default signal to a
// server that listens for that signal causes the server to shut down cleanly.
func TestHandlerCustomSignal(t *testing.T) {
- c := blackbox.HelperCommand(t, "handleCustom")
- defer c.Cleanup()
- c.Cmd.Start()
- c.Expect("ready")
+ sh, h, s := newShell(t, "handleCustom")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ s.Expect("ready")
checkSignalIsNotDefault(t, syscall.SIGABRT)
- syscall.Kill(c.Cmd.Process.Pid, syscall.SIGABRT)
- c.Expect(fmt.Sprintf("received signal %s", syscall.SIGABRT))
- c.WriteLine("close")
- c.ExpectEOFAndWait()
+ syscall.Kill(h.Pid(), syscall.SIGABRT)
+ s.Expectf("received signal %s", syscall.SIGABRT)
+ fmt.Fprintf(h.Stdin(), "stop\n")
+ s.ExpectEOF()
}
// TestHandlerCustomSignalWithStop verifies that sending a custom stop signal
@@ -244,15 +268,14 @@
// cleanly, even when a STOP signal is also among the handled signals.
func TestHandlerCustomSignalWithStop(t *testing.T) {
for _, signal := range []syscall.Signal{syscall.SIGABRT, syscall.SIGHUP} {
- c := blackbox.HelperCommand(t, "handleCustomWithStop")
- c.Cmd.Start()
- c.Expect("ready")
+ sh, h, s := newShell(t, "handleCustomWithStop")
+ s.Expect("ready")
checkSignalIsNotDefault(t, signal)
- syscall.Kill(c.Cmd.Process.Pid, signal)
- c.Expect(fmt.Sprintf("received signal %s", signal))
- c.WriteLine("close")
- c.ExpectEOFAndWait()
- c.Cleanup()
+ syscall.Kill(h.Pid(), signal)
+ s.Expectf("received signal %s", signal)
+ fmt.Fprintf(h.Stdin(), "close\n")
+ s.ExpectEOF()
+ sh.Cleanup(os.Stderr, os.Stderr)
}
}
@@ -303,8 +326,11 @@
func TestCleanRemoteShutdown(t *testing.T) {
r := rt.Init()
defer r.Cleanup()
- c := blackbox.HelperCommand(t, "handleDefaults")
- defer c.Cleanup()
+
+ sh := modules.NewShell()
+ sh.AddSubprocess("handleDefaults", "")
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+
// This sets up the child's identity to be derived from the parent's (so
// that default authorization works for RPCs between the two).
// TODO(caprita): Consider making this boilerplate part of blackbox.
@@ -313,11 +339,15 @@
defer os.Remove(idFile)
configServer, configServiceName, ch := createConfigServer(t)
defer configServer.Stop()
- c.Cmd.Env = append(c.Cmd.Env, fmt.Sprintf("VEYRON_IDENTITY=%v", idFile),
- fmt.Sprintf("%v=%v", mgmt.ParentNodeManagerConfigKey, configServiceName))
- c.Cmd.Start()
+ sh.SetVar("VEYRON_IDENTITY", idFile)
+ sh.SetVar(mgmt.ParentNodeManagerConfigKey, configServiceName)
+ h, err := sh.Start("handleDefaults")
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ s := expect.NewSession(t, h.Stdout(), time.Minute)
appCycleName := <-ch
- c.Expect("ready")
+ s.Expect("ready")
appCycle, err := appcycle.BindAppCycle(appCycleName)
if err != nil {
t.Fatalf("Got error: %v", err)
@@ -333,7 +363,7 @@
if err := stream.Finish(); err != nil {
t.Fatalf("Got error: %v", err)
}
- c.Expect(fmt.Sprintf("received signal %s", veyron2.RemoteStop))
- c.WriteLine("close")
- c.ExpectEOFAndWait()
+ s.Expectf("received signal %s", veyron2.RemoteStop)
+ fmt.Fprintf(h.Stdin(), "close\n")
+ s.ExpectEOF()
}