feat(flags): add '-prefix=name,serial,none' flag

The '-prefix' flag allows wsers to control the console output prefix
to a certain extent.

Change-Id: I11c20203d235ff0e4fd756dfabc0d57c9a13fc6c
Closes #11
diff --git a/doc.go b/doc.go
index 972fc06..5b02494 100644
--- a/doc.go
+++ b/doc.go
@@ -39,6 +39,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
@@ -96,6 +103,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
@@ -143,6 +157,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
@@ -204,6 +225,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
@@ -302,6 +330,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
@@ -385,6 +420,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
@@ -439,6 +481,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
@@ -492,6 +541,13 @@
    '@2'), or nicknames (set by 'madb name'). A device index is specified by an
    '@' sign followed by the index of the device in the output of 'adb devices'
    command, starting from 1. Command will be run only on specified devices.
+ -prefix=name
+   Specify which output prefix to use. You can choose from the following
+   options:
+       name   - Display the nickname of the device. The serial number is used instead if the
+                nickname is not set for the given device.
+       serial - Display the serial number of the device.
+       none   - Do not display the output prefix.
  -seq=false
    Run the command sequentially, instead of running it in parallel.
 
diff --git a/madb.go b/madb.go
index 9b2fe79..a335899 100644
--- a/madb.go
+++ b/madb.go
@@ -18,6 +18,7 @@
 	"encoding/json"
 	"flag"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 	"path"
@@ -37,6 +38,7 @@
 	allEmulatorsFlag bool
 	devicesFlag      string
 	sequentialFlag   bool
+	prefixFlag       string
 
 	clearCacheFlag bool
 	moduleFlag     string
@@ -52,6 +54,11 @@
 	cmdMadb.Flags.BoolVar(&allEmulatorsFlag, "e", false, `Restrict the command to only run on emulators.`)
 	cmdMadb.Flags.StringVar(&devicesFlag, "n", "", `Comma-separated device serials, qualifiers, device indices (e.g., '@1', '@2'), or nicknames (set by 'madb name'). A device index is specified by an '@' sign followed by the index of the device in the output of 'adb devices' command, starting from 1. Command will be run only on specified devices.`)
 	cmdMadb.Flags.BoolVar(&sequentialFlag, "seq", false, `Run the command sequentially, instead of running it in parallel.`)
+	cmdMadb.Flags.StringVar(&prefixFlag, "prefix", "name", `Specify which output prefix to use. You can choose from the following options:
+    name   - Display the nickname of the device. The serial number is used instead if the
+             nickname is not set for the given device.
+    serial - Display the serial number of the device.
+    none   - Do not display the output prefix.`)
 
 	// Store the current working directory.
 	var err error
@@ -362,6 +369,11 @@
 
 // Invokes the sub command on all the devices in parallel.
 func (r subCommandRunner) Run(env *cmdline.Env, args []string) error {
+	prefixFlag = strings.ToLower(prefixFlag)
+	if prefixFlag != "auto" && prefixFlag != "serial" && prefixFlag != "none" {
+		return fmt.Errorf(`The -prefix flag value must be one of "auto", "serial", or "none".`)
+	}
+
 	if err := startAdbServer(); err != nil {
 		return err
 	}
@@ -432,20 +444,29 @@
 }
 
 func runGoshCommandForDevice(cmd *gosh.Cmd, d device, printUserID bool) error {
-	var prefix string
-	if printUserID && d.UserID != "" {
-		prefix = "[" + d.displayName() + ":" + d.UserID + "]\t"
-	} else {
-		prefix = "[" + d.displayName() + "]\t"
+	return runGoshCommandForDeviceWithWriters(cmd, d, printUserID, os.Stdout, os.Stderr)
+}
+
+func runGoshCommandForDeviceWithWriters(cmd *gosh.Cmd, d device, printUserID bool, stdout, stderr io.Writer) error {
+	prefix := ""
+	if prefixFlag != "none" {
+		name := d.Serial
+		if prefixFlag == "name" {
+			name = d.displayName()
+		}
+		if printUserID && d.UserID != "" {
+			name = name + ":" + d.UserID
+		}
+		prefix = "[" + name + "]\t"
 	}
 
-	stdout := textutil.PrefixLineWriter(os.Stdout, prefix)
-	stderr := textutil.PrefixLineWriter(os.Stderr, prefix)
-	cmd.AddStdoutWriter(stdout)
-	cmd.AddStderrWriter(stderr)
+	prefixedStdout := textutil.PrefixLineWriter(stdout, prefix)
+	prefixedStderr := textutil.PrefixLineWriter(stderr, prefix)
+	cmd.AddStdoutWriter(prefixedStdout)
+	cmd.AddStderrWriter(prefixedStderr)
 	cmd.Run()
-	stdout.Flush()
-	stderr.Flush()
+	prefixedStdout.Flush()
+	prefixedStderr.Flush()
 
 	return cmd.Shell().Err
 }
diff --git a/madb_test.go b/madb_test.go
index 769137c..0ef5843 100644
--- a/madb_test.go
+++ b/madb_test.go
@@ -5,13 +5,22 @@
 package main
 
 import (
+	"bytes"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
 	"testing"
+
+	"v.io/x/lib/gosh"
 )
 
+func TestMain(m *testing.M) {
+	gosh.InitMain()
+	os.Exit(m.Run())
+}
+
 func tempFilename(t *testing.T) string {
 	f, err := ioutil.TempFile("", "madb_test")
 	if err != nil {
@@ -434,3 +443,73 @@
 		}
 	}
 }
+
+var helloFunc = gosh.RegisterFunc("helloFunc", func() {
+	fmt.Println("Hello, World!")
+})
+
+func TestOutputPrefix(t *testing.T) {
+	// Sample device.
+	d1 := device{
+		Serial:     "deviceid01",
+		Type:       realDevice,
+		Qualifiers: nil,
+		Nickname:   "Alice",
+		Index:      1,
+		UserID:     "",
+	}
+
+	d2 := device{
+		Serial:     "deviceid02",
+		Type:       realDevice,
+		Qualifiers: nil,
+		Nickname:   "Bob",
+		Index:      2,
+		UserID:     "10",
+	}
+
+	d3 := device{
+		Serial:     "deviceid03",
+		Type:       realDevice,
+		Qualifiers: nil,
+		Nickname:   "",
+		Index:      3,
+		UserID:     "",
+	}
+
+	sh := gosh.NewShell(nil)
+	defer sh.Cleanup()
+
+	tests := []struct {
+		prefixType string
+		d          device
+		want       string
+	}{
+		{"name", d1, "[Alice]\tHello, World!\n"},
+		{"name", d2, "[Bob:10]\tHello, World!\n"},
+		{"name", d3, "[deviceid03]\tHello, World!\n"},
+		{"serial", d1, "[deviceid01]\tHello, World!\n"},
+		{"serial", d2, "[deviceid02:10]\tHello, World!\n"},
+		{"serial", d3, "[deviceid03]\tHello, World!\n"},
+		{"none", d1, "Hello, World!\n"},
+		{"none", d2, "Hello, World!\n"},
+		{"none", d3, "Hello, World!\n"},
+	}
+
+	for i, test := range tests {
+		var b1, b2 bytes.Buffer
+
+		helloCmd := sh.FuncCmd(helloFunc)
+		prefixFlag = test.prefixType
+		if err := runGoshCommandForDeviceWithWriters(helloCmd, test.d, true, &b1, &b2); err != nil {
+			t.Fatalf("error occurred while running gosh command: %v", err)
+		}
+
+		if got, want := b1.String(), test.want; got != want {
+			t.Fatalf("unmatched results for tests[%v]: got %v, want %v", i, got, want)
+		}
+		if b2.String() != "" {
+			t.Fatalf("unexpected output to stderr for tests[%v]: %v", i, b2.String())
+		}
+	}
+}