veyron/services/mgmt: an example port of test.sh to Go

The following CL demonstrates how to port a veyron integration test
written in shell to Go. In particular, this CL ports the
$VEYRON_ROOT/veyron/go/src/veyron.io/veyron/veyron/services/mgmt/build/buildd/test.sh
integration test.

Change-Id: I98fc1bf8ade8c460a30b379dad714ad8a105b1b3
diff --git a/lib/testutil/integration/util.go b/lib/testutil/integration/util.go
new file mode 100644
index 0000000..9968938
--- /dev/null
+++ b/lib/testutil/integration/util.go
@@ -0,0 +1,120 @@
+package integration
+
+import (
+	"bufio"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"veyron.io/veyron/veyron/lib/expect"
+	"veyron.io/veyron/veyron/lib/modules"
+	"veyron.io/veyron/veyron/lib/modules/core"
+)
+
+// BuildPkgs returns a path to a directory that contains the built
+// binaries for the given set of packages and a function that should
+// be invoked to clean up the build artifacts. Note that the clients
+// of this function should not modify the contents of this directory
+// directly and instead defer to the cleanup function.
+func BuildPkgs(pkgs []string) (string, func(), error) {
+	// The VEYRON_INTEGRATION_BIN_DIR environment variable can be
+	// used to identify a directory that multiple integration
+	// tests can use to share binaries. Whoever sets this
+	// environment variable is responsible for cleaning up the
+	// directory it points to.
+	binDir, cleanupFn := os.Getenv("VEYRON_INTEGRATION_BIN_DIR"), func() {}
+	if binDir == "" {
+		// If the aforementioned environment variable is not
+		// set, the given packages are built in a temporary
+		// directory, which the cleanup function removes.
+		tmpDir, err := ioutil.TempDir("", "")
+		if err != nil {
+			return "", nil, fmt.Errorf("TempDir() failed: %v", err)
+		}
+		binDir, cleanupFn = tmpDir, func() { os.RemoveAll(tmpDir) }
+	}
+	for _, pkg := range pkgs {
+		binFile := filepath.Join(binDir, path.Base(pkg))
+		if _, err := os.Stat(binFile); err != nil {
+			if !os.IsNotExist(err) {
+				return "", nil, err
+			}
+			cmd := exec.Command("veyron", "go", "build", "-o", filepath.Join(binDir, path.Base(pkg)), pkg)
+			if err := cmd.Run(); err != nil {
+				return "", nil, err
+			}
+		}
+	}
+	return binDir, cleanupFn, nil
+}
+
+// StartRootMT uses the given shell to start a root mount table and
+// returns a handle for the started command along with the object name
+// of the mount table.
+func StartRootMT(shell *modules.Shell) (modules.Handle, string, error) {
+	handle, err := shell.Start(core.RootMTCommand, nil, "--", "--veyron.tcp.address=127.0.0.1:0")
+	if err != nil {
+		return nil, "", err
+	}
+	s := expect.NewSession(nil, handle.Stdout(), time.Second)
+	name := s.ExpectVar("MT_NAME")
+	if err := s.Error(); err != nil {
+		return nil, "", err
+	}
+	s.ExpectVar("MT_ADDR")
+	if err := s.Error(); err != nil {
+		return nil, "", err
+	}
+	s.ExpectVar("PID")
+	if err := s.Error(); err != nil {
+		return nil, "", err
+	}
+	return handle, name, nil
+}
+
+// StartServer starts a veyron server using the given binary and
+// arguments, waiting for the server to successfully mount itself in
+// the mount table.
+//
+// TODO(jsimsa,sadovsky): Use an instance of modules.Shell to start
+// and manage the server process to prevent leaking processes when
+// its parent terminates unexpectedly.
+func StartServer(bin string, args []string) (*os.Process, error) {
+	args = append(args, "-logtostderr", "-vmodule=publisher=2")
+	cmd := exec.Command(bin, args...)
+	outPipe, err := cmd.StderrPipe()
+	if err != nil {
+		return nil, err
+	}
+	if err := cmd.Start(); err != nil {
+		return nil, fmt.Errorf("%q failed: %v", strings.Join(cmd.Args, " "), err)
+	}
+	// Wait for the server to mount itself.
+	ready := make(chan struct{}, 1)
+	go func() {
+		defer outPipe.Close()
+		scanner := bufio.NewScanner(outPipe)
+		for scanner.Scan() {
+			line := scanner.Text()
+			if strings.Index(line, "ipc pub: mount") != -1 {
+				close(ready)
+				return
+			}
+		}
+		if err := scanner.Err(); err != nil {
+			fmt.Fprintf(os.Stderr, "Scan() failOAed: %v\n", err)
+		}
+	}()
+	select {
+	case <-ready:
+		return cmd.Process, nil
+	case <-time.After(time.Second):
+		cmd.Process.Kill()
+		return nil, fmt.Errorf("timed out waiting for %q to mount itself", bin)
+	}
+}
diff --git a/services/mgmt/build/buildd/testdata/integration_test.go b/services/mgmt/build/buildd/testdata/integration_test.go
new file mode 100644
index 0000000..8902104
--- /dev/null
+++ b/services/mgmt/build/buildd/testdata/integration_test.go
@@ -0,0 +1,145 @@
+package integration_test
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"veyron.io/veyron/veyron/lib/modules"
+	"veyron.io/veyron/veyron/lib/testutil/integration"
+	"veyron.io/veyron/veyron/lib/testutil/security"
+	_ "veyron.io/veyron/veyron/profiles"
+	"veyron.io/veyron/veyron2/rt"
+)
+
+func init() {
+	rt.Init()
+}
+
+var binPkgs = []string{
+	"veyron.io/veyron/veyron/services/mgmt/build/buildd",
+	"veyron.io/veyron/veyron/tools/build",
+}
+
+var testProgram = `package main
+
+import "fmt"
+
+func main() { fmt.Println("Hello World!") }
+`
+
+func goRoot(bin string) (string, error) {
+	var out bytes.Buffer
+	cmd := exec.Command(bin, "env", "GOROOT")
+	cmd.Stdout = &out
+	cmd.Stderr = &out
+	if err := cmd.Run(); err != nil {
+		return "", fmt.Errorf("%q failed: %v\n%v", strings.Join(cmd.Args, " "), err, out.String())
+	}
+	cleanOut := strings.TrimSpace(out.String())
+	if cleanOut == "" {
+		return "", fmt.Errorf("%v does not set GOROOT", bin)
+	}
+	return cleanOut, nil
+}
+
+func TestHelperProcess(t *testing.T) {
+	modules.DispatchInTest()
+}
+
+func TestBuild(t *testing.T) {
+	// Build the required binaries.
+	binDir, cleanup, err := integration.BuildPkgs(binPkgs)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	defer cleanup()
+
+	// Start a root mount table.
+	shell, err := modules.NewShell(nil)
+	if err != nil {
+		t.Fatalf("NewShell() failed: %v", err)
+	}
+	defer shell.Cleanup(os.Stdin, os.Stderr)
+	handle, mtName, err := integration.StartRootMT(shell)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	defer handle.CloseStdin()
+
+	// Generate credentials.
+	root := security.NewPrincipal("root")
+	credentials := security.NewVeyronCredentials(root, "test-credentials")
+	defer os.RemoveAll(credentials)
+
+	// Start the build server.
+	buildServerBin := filepath.Join(binDir, "buildd")
+	buildServerName := "test-build-server"
+	goBin, err := exec.LookPath("go")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	goRoot, err := goRoot(goBin)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	args := []string{
+		"-name=" + buildServerName, "-gobin=" + goBin, "-goroot=" + goRoot,
+		"-veyron.tcp.address=127.0.0.1:0",
+		"-veyron.credentials=" + credentials,
+		"-veyron.namespace.root=" + mtName,
+	}
+	serverProcess, err := integration.StartServer(buildServerBin, args)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	defer serverProcess.Kill()
+
+	// Create and build a test source file.
+	testGoPath, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatalf("TempDir() failed: %v", err)
+	}
+	defer os.RemoveAll(testGoPath)
+	testBinDir := filepath.Join(testGoPath, "bin")
+	if err := os.MkdirAll(testBinDir, os.FileMode(0700)); err != nil {
+		t.Fatalf("MkdirAll(%v) failed: %v", testBinDir, err)
+	}
+	testBinFile := filepath.Join(testBinDir, "test")
+	testSrcDir := filepath.Join(testGoPath, "src", "test")
+	if err := os.MkdirAll(testSrcDir, os.FileMode(0700)); err != nil {
+		t.Fatalf("MkdirAll(%v) failed: %v", testSrcDir, err)
+	}
+	testSrcFile := filepath.Join(testSrcDir, "test.go")
+	if err := ioutil.WriteFile(testSrcFile, []byte(testProgram), os.FileMode(0600)); err != nil {
+		t.Fatalf("WriteFile(%v) failed: %v", testSrcFile, err)
+	}
+	var buildOut bytes.Buffer
+	buildArgs := []string{
+		"-veyron.credentials=" + credentials,
+		"-veyron.namespace.root=" + mtName,
+		"build", buildServerName, "test",
+	}
+	buildCmd := exec.Command(filepath.Join(binDir, "build"), buildArgs...)
+	buildCmd.Stdout = &buildOut
+	buildCmd.Stderr = &buildOut
+	buildCmd.Env = append(buildCmd.Env, "GOPATH="+testGoPath, "GOROOT="+goRoot, "TMPDIR="+testBinDir)
+	if err := buildCmd.Run(); err != nil {
+		t.Fatalf("%q failed: %v\n%v", strings.Join(buildCmd.Args, " "), err, buildOut.String())
+	}
+	var testOut bytes.Buffer
+	testCmd := exec.Command(testBinFile)
+	testCmd.Stdout = &testOut
+	testCmd.Stderr = &testOut
+	if err := testCmd.Run(); err != nil {
+		t.Fatalf("%q failed: %v\n%v", strings.Join(testCmd.Args, " "), err, testOut.String())
+	}
+	if got, want := strings.TrimSpace(testOut.String()), "Hello World!"; got != want {
+		t.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+}