test/v23tests: added an option to set prefix arguments

Sometimes, one might want to run the same command in a test repeatedly
with different tail arguments. e.g. git checkout a; git checkout b.
Add a method to configure command-specific prefx arguments (checkout
here) that would be added to each command invocation.

Change-Id: I9b5eb777fcf9435b58bac1d1d8f458e00c5aae98
diff --git a/test/v23tests/binary.go b/test/v23tests/binary.go
index 9cbee4f..c4dd0cc 100644
--- a/test/v23tests/binary.go
+++ b/test/v23tests/binary.go
@@ -35,6 +35,9 @@
 	// Environment variables that will be used when creating invocations
 	// via Start.
 	envVars []string
+
+	// Optional prefix arguments are added to each invocation.
+	prefixArgs []string
 }
 
 // StartOpts returns the current the StartOpts
@@ -52,7 +55,10 @@
 	return b.start(1, args...)
 }
 
-func (b *Binary) start(skip int, args ...string) *Invocation {
+func (b *Binary) start(skip int, oargs ...string) *Invocation {
+	args := make([]string, len(b.prefixArgs), len(oargs)+len(b.prefixArgs))
+	copy(args, b.prefixArgs)
+	args = append(args, oargs...)
 	vlog.Infof("%s: starting %s %s", Caller(skip+1), b.Path(), strings.Join(args, " "))
 	opts := b.opts
 	if opts.ExecProtocol && opts.Credentials == nil {
@@ -134,3 +140,14 @@
 	newBin.opts = opts
 	return &newBin
 }
+
+// WithPrefixArgs returns a copy of this binary that, when Start or Run
+// is called, will use the given additional arguments. For example: given
+// a Binary b built from "git", then b2 := WithPrefixArgs("checkout")
+// will let one run git checkout a; git checkout b with b2.Run("a"),
+// b2.Run("b").
+func (b *Binary) WithPrefixArgs(prefixArgs ...string) *Binary {
+	newBin := *b
+	newBin.prefixArgs = prefixArgs
+	return &newBin
+}
diff --git a/test/v23tests/v23tests_test.go b/test/v23tests/v23tests_test.go
index f3c5382..4987ff3 100644
--- a/test/v23tests/v23tests_test.go
+++ b/test/v23tests/v23tests_test.go
@@ -234,6 +234,16 @@
 	if got, want := echo.Run("hello", "world"), "hello world"; got != want {
 		t.Fatalf("got %v, want %v", got, want)
 	}
+
+	sadEcho := echo.WithPrefixArgs("sad")
+	if got, want := sadEcho.Run("hello", "world"), "sad hello world"; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	happyEcho := echo.WithPrefixArgs("happy")
+	if got, want := happyEcho.Run("hello", "world"), "happy hello world"; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
 }
 
 type mockT struct {
@@ -286,7 +296,7 @@
 		msg := recover().(string)
 		// this, and the tests below are intended to ensure that line #s
 		// are captured and reported correctly.
-		if got, want := msg, "v23tests_test.go:296"; !strings.Contains(got, want) {
+		if got, want := msg, "v23tests_test.go:306"; !strings.Contains(got, want) {
 			t.Fatalf("%q does not contain %q", got, want)
 		}
 		if got, want := msg, "fork/exec /bin/echox: no such file or directory"; !strings.Contains(got, want) {
@@ -308,7 +318,7 @@
 	sh.SetDefaultStartOpts(opts)
 	defer func() {
 		msg := recover().(string)
-		if got, want := msg, "v23tests_test.go:318"; !strings.Contains(got, want) {
+		if got, want := msg, "v23tests_test.go:328"; !strings.Contains(got, want) {
 			t.Fatalf("%q does not contain %q", got, want)
 		}
 		if got, want := msg, "StartWithOpts"; !strings.Contains(got, want) {
@@ -332,7 +342,7 @@
 		if iterations == 0 {
 			t.Fatalf("our sleeper didn't get to run")
 		}
-		if got, want := recover().(string), "v23tests_test.go:339: timed out"; !strings.Contains(got, want) {
+		if got, want := recover().(string), "v23tests_test.go:349: timed out"; !strings.Contains(got, want) {
 			t.Fatalf("%q does not contain %q", got, want)
 		}
 	}()
@@ -354,7 +364,7 @@
 		if iterations != 0 {
 			t.Fatalf("our sleeper got to run")
 		}
-		if got, want := recover().(string), "v23tests_test.go:361: timed out"; !strings.Contains(got, want) {
+		if got, want := recover().(string), "v23tests_test.go:371: timed out"; !strings.Contains(got, want) {
 			t.Fatalf("%q does not contain %q", got, want)
 		}
 	}()