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)
+ }
+}