veyron/services/mgmt: port of binaryd integration test to Go

This CL ports the binaryd integration test from shell to Go. To
facilitate retrieval of the HTTP address used for URL-based binary
repository downloads, this CL adds a trivial implementation of the
DownloadURL RPC method.

Change-Id: I2e3b544241ae32fb9aa6daa821963aff5cd1e498
diff --git a/tools/binary/impl.go b/tools/binary/impl.go
index df34d6c..54d6d25 100644
--- a/tools/binary/impl.go
+++ b/tools/binary/impl.go
@@ -10,7 +10,7 @@
 var cmdDelete = &cmdline.Command{
 	Run:      runDelete,
 	Name:     "delete",
-	Short:    "Delete binary",
+	Short:    "Delete a binary",
 	Long:     "Delete connects to the binary repository and deletes the specified binary",
 	ArgsName: "<von>",
 	ArgsLong: "<von> is the veyron object name of the binary to delete",
@@ -31,7 +31,7 @@
 var cmdDownload = &cmdline.Command{
 	Run:   runDownload,
 	Name:  "download",
-	Short: "Download binary",
+	Short: "Download a binary",
 	Long: `
 Download connects to the binary repository, downloads the specified binary, and
 writes it to a file.
@@ -58,7 +58,7 @@
 var cmdUpload = &cmdline.Command{
 	Run:   runUpload,
 	Name:  "upload",
-	Short: "Upload binary",
+	Short: "Upload a binary",
 	Long: `
 Upload connects to the binary repository and uploads the binary of the specified
 file. When successful, it writes the name of the new binary to stdout.
@@ -83,6 +83,28 @@
 	return nil
 }
 
+var cmdURL = &cmdline.Command{
+	Run:      runURL,
+	Name:     "url",
+	Short:    "Fetch a download URL",
+	Long:     "Connect to the binary repository and fetch the download URL for the given veyron object name.",
+	ArgsName: "<von>",
+	ArgsLong: "<von> is the veyron object name of the binary repository",
+}
+
+func runURL(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.UsageErrorf("rooturl: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	von := args[0]
+	url, _, err := binary.DownloadURL(runtime.NewContext(), von)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(cmd.Stdout(), "%v\n", url)
+	return nil
+}
+
 func root() *cmdline.Command {
 	return &cmdline.Command{
 		Name:  "binary",
@@ -90,6 +112,6 @@
 		Long: `
 The binary tool facilitates interaction with the veyron binary repository.
 `,
-		Children: []*cmdline.Command{cmdDelete, cmdDownload, cmdUpload},
+		Children: []*cmdline.Command{cmdDelete, cmdDownload, cmdUpload, cmdURL},
 	}
 }
diff --git a/tools/binary/impl_test.go b/tools/binary/impl_test.go
index 865c35d..ba314cc 100644
--- a/tools/binary/impl_test.go
+++ b/tools/binary/impl_test.go
@@ -50,7 +50,10 @@
 
 func (s *server) DownloadURL(ipc.ServerContext) (string, int64, error) {
 	vlog.Infof("DownloadURL() was called. suffix=%v", s.suffix)
-	return "", 0, nil
+	if s.suffix != "" {
+		return "", 0, fmt.Errorf("non-empty suffix: %v", s.suffix)
+	}
+	return "test-download-url", 0, nil
 }
 
 func (s *server) Stat(ipc.ServerContext) ([]binary.PartInfo, repository.MediaInfo, error) {
@@ -119,19 +122,20 @@
 		return
 	}
 	defer stopServer(t, server)
+
 	// Setup the command-line.
 	cmd := root()
-	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	var out bytes.Buffer
+	cmd.Init(nil, &out, &out)
 
 	// Test the 'delete' command.
 	if err := cmd.Execute([]string{"delete", naming.JoinAddressName(endpoint.String(), "exists")}); err != nil {
-		t.Fatalf("%v", err)
+		t.Fatalf("%v failed: %v\n%v", "delete", err, out.String())
 	}
-	if expected, got := "Binary deleted successfully", strings.TrimSpace(stdout.String()); got != expected {
+	if expected, got := "Binary deleted successfully", strings.TrimSpace(out.String()); got != expected {
 		t.Errorf("Got %q, expected %q", got, expected)
 	}
-	stdout.Reset()
+	out.Reset()
 
 	// Test the 'download' command.
 	dir, err := ioutil.TempDir("", "binaryimpltest")
@@ -142,9 +146,9 @@
 	file := path.Join(dir, "testfile")
 	defer os.Remove(file)
 	if err := cmd.Execute([]string{"download", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
-		t.Fatalf("%v", err)
+		t.Fatalf("%v failed: %v\n%v", "download", err, out.String())
 	}
-	if expected, got := "Binary downloaded to file "+file, strings.TrimSpace(stdout.String()); got != expected {
+	if expected, got := "Binary downloaded to file "+file, strings.TrimSpace(out.String()); got != expected {
 		t.Errorf("Got %q, expected %q", got, expected)
 	}
 	buf, err := ioutil.ReadFile(file)
@@ -154,10 +158,19 @@
 	if expected := "HelloWorld"; string(buf) != expected {
 		t.Errorf("Got %q, expected %q", string(buf), expected)
 	}
-	stdout.Reset()
+	out.Reset()
 
 	// Test the 'upload' command.
 	if err := cmd.Execute([]string{"upload", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
-		t.Fatalf("%v", err)
+		t.Fatalf("%v failed: %v\n%v", "upload", err, out.String())
+	}
+	out.Reset()
+
+	// Test the 'url' command.
+	if err := cmd.Execute([]string{"url", naming.JoinAddressName(endpoint.String(), "")}); err != nil {
+		t.Fatalf("%v failed: %v\n%v", "url", err, out.String())
+	}
+	if expected, got := "test-download-url", strings.TrimSpace(out.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
 	}
 }