veyron/tools/debug/testdata: add debug stats watch test

Also adds a Kill(sig syscall.Signal) function to the Invocation
interface.

Change-Id: I6ce4316008fab4ee5637881121ce3830e22c01e4
diff --git a/lib/testutil/integration/util.go b/lib/testutil/integration/util.go
index 190b80c..6c4b185 100644
--- a/lib/testutil/integration/util.go
+++ b/lib/testutil/integration/util.go
@@ -78,6 +78,10 @@
 	// what was read as a string.
 	ErrorOutput() string
 
+	// Sends the given signal to this invocation. It is up to the test
+	// author whether failure to deliver the signal is fatal to the test.
+	Kill(syscall.Signal) error
+
 	// Wait waits for this invocation to finish. If either stdout or stderr
 	// is non-nil, any remaining unread output from those sources will be
 	// written to the corresponding writer.
@@ -130,6 +134,13 @@
 	return (*i.handle).Stdout()
 }
 
+func (i *integrationTestBinaryInvocation) Kill(sig syscall.Signal) error {
+	pid := (*i.handle).Pid()
+	(*i.handle).Shutdown(nil, nil)
+	i.env.t.Logf("sending signal %v to PID %d", sig, pid)
+	return syscall.Kill(pid, sig)
+}
+
 func readerToString(t *testing.T, r io.Reader) string {
 	buf := bytes.Buffer{}
 	_, err := buf.ReadFrom(r)
@@ -175,6 +186,7 @@
 	if err != nil {
 		b.env.t.Fatalf("Start(%v, %v) failed: %v", b.Path(), strings.Join(args, ", "), err)
 	}
+	b.env.t.Logf("started PID %d\n", handle.Pid())
 	return &integrationTestBinaryInvocation{
 		env:    b.env,
 		handle: &handle,
diff --git a/tools/debug/testdata/integration_test.go b/tools/debug/testdata/integration_test.go
index 46fffbc..4818c3d 100644
--- a/tools/debug/testdata/integration_test.go
+++ b/tools/debug/testdata/integration_test.go
@@ -1,6 +1,7 @@
 package testdata
 
 import (
+	"bufio"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -8,6 +9,7 @@
 	"strconv"
 	"strings"
 	"testing"
+	"time"
 
 	"v.io/core/veyron/lib/modules"
 	"v.io/core/veyron/lib/testutil/integration"
@@ -114,6 +116,46 @@
 	}
 }
 
+func TestStatsWatch(t *testing.T) {
+	env := integration.NewTestEnvironment(t)
+	defer env.Cleanup()
+
+	binary := env.BuildGoPkg("v.io/veyron/veyron/tools/debug")
+	testLogData := "This is a test log file\n"
+	file := createTestLogFile(t, env, testLogData)
+	logName := filepath.Base(file.Name())
+	binary.Start("logs", "read", env.RootMT()+"/__debug/logs/"+logName).Wait(nil, nil)
+
+	inv := binary.Start("stats", "watch", "-raw", env.RootMT()+"/__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms")
+
+	lines := make(chan string)
+	// Go off and read the invocation's stdout.
+	go func() {
+		line, err := bufio.NewReader(inv.Stdout()).ReadString('\n')
+		if err != nil {
+			t.Fatalf("Could not read line from invocation")
+		}
+		lines <- line
+	}()
+
+	// Wait up to 10 seconds for some stats output. Either some output
+	// occurs or the timeout expires without any output.
+	select {
+	case <-time.After(10 * time.Second):
+		t.Errorf("Timed out waiting for output")
+	case got := <-lines:
+		// Expect one ReadLog call to have occurred.
+		want := "latency-ms: {Count:1"
+		if !strings.Contains(got, want) {
+			t.Errorf("wanted but could not find %q in output\n%s", want, got)
+		}
+	}
+
+	// TODO(sjr): make env cleanup take care of invocations that are still
+	// running at the end of the test.
+	inv.Kill(15 /* SIGHUP */)
+}
+
 func performTracedRead(debugBinary integration.TestBinary, path string) string {
 	return debugBinary.Start("--veyron.vtrace.sample_rate=1", "logs", "read", path).Output()
 }