veyron/lib/expect: return matching groups from ExpectSet(Eventually)?RE

This CL allows users to capture matching groups from their regular
expressions which can be helpful in certain circumstances.

Change-Id: I92813d4c9c0aa72d8dbf0bd1017f6e2813863a6f
diff --git a/lib/expect/expect.go b/lib/expect/expect.go
index 2c6c70a..01e9e35 100644
--- a/lib/expect/expect.go
+++ b/lib/expect/expect.go
@@ -27,7 +27,7 @@
 //     buffer.WriteString("foo\n")
 //     buffer.WriteString("bar\n")
 //     buffer.WriteString("baz\n")
-//     s := expect.New(t, bufio.NewReader(buffer), time.Second)
+//     s := expect.NewSession(t, bufio.NewReader(buffer), time.Second)
 //     s.Expect("foo")
 //     s.Expect("bars)
 //     if got, want := s.ReadLine(), "baz"; got != want {
@@ -67,6 +67,7 @@
 
 type Testing interface {
 	Error(args ...interface{})
+	Errorf(format string, args ...interface{})
 	Log(args ...interface{})
 }
 
@@ -277,38 +278,54 @@
 // lines of input. Each line is read and matched against the supplied
 // patterns in the order that they are supplied as parameters. Consequently
 // the set may contain repetitions if the same pattern is expected multiple
-// times.
-func (s *Session) ExpectSetRE(expected ...string) {
+// times. The value returned is either:
+//   * nil in the case of an error or no match, or
+//   * an array of length len(expected), whose ith element contains the result
+//       of FindStringSubmatch of expected[i] on the matching string (never
+//       nil). If there are no capturing groups in expected[i], the return
+//       value's [i][0] element will be the entire matching string
+func (s *Session) ExpectSetRE(expected ...string) [][]string {
 	if s.Failed() {
-		return
+		return nil
 	}
-	if err := s.expectSetRE(len(expected), expected...); err != nil {
+	if match, err := s.expectSetRE(len(expected), expected...); err != nil {
 		s.error(err)
+		return nil
+	} else {
+		return match
 	}
 }
 
 // ExpectSetEventuallyRE is like ExpectSetRE except that it reads
 // all remaining output rather than just the next n lines and thus
 // can be used to look for a set of patterns that occur within that
-// output.
-func (s *Session) ExpectSetEventuallyRE(expected ...string) {
+// output. The value returned is either:
+//   * nil in the case of an error or no match, or
+//   * an array of length len(expected), whose ith element contains the result
+//       of FindStringSubmatch of expected[i] on the matching string (never
+//       nil). If there are no capturing groups in expected[i], the return
+//       value's [i][0] will contain the entire matching string
+func (s *Session) ExpectSetEventuallyRE(expected ...string) [][]string {
 	if s.Failed() {
-		return
+		return nil
 	}
-	if err := s.expectSetRE(-1, expected...); err != nil {
+	if matches, err := s.expectSetRE(-1, expected...); err != nil {
 		s.error(err)
+		return nil
+	} else {
+		return matches
 	}
 }
 
 // expectSetRE will look for the expected set of patterns in the next
 // numLines of output or in all remaining output.
-func (s *Session) expectSetRE(numLines int, expected ...string) error {
+func (s *Session) expectSetRE(numLines int, expected ...string) ([][]string, error) {
 
 	regexps := make([]*regexp.Regexp, len(expected))
 	for i, expRE := range expected {
 		re, err := regexp.Compile(expRE)
 		if err != nil {
-			return err
+			return nil, err
 		}
 		regexps[i] = re
 	}
@@ -320,7 +337,7 @@
 		s.log(err, "ExpectSetRE: %s", line)
 		if err != nil {
 			if numLines >= 0 {
-				return err
+				return nil, err
 			}
 			break
 		}
@@ -331,6 +348,7 @@
 		}
 	}
 
+	matches := make([][]string, len(expected))
 	// Match each line against all regexp's and remove each regexp
 	// that matches.
 	for _, l := range actual {
@@ -338,8 +356,10 @@
 			if re == nil {
 				continue
 			}
-			if re.MatchString(l) {
+			match := re.FindStringSubmatch(l)
+			if match != nil {
 				regexps[i] = nil
+				matches[i] = match
 				break
 			}
 		}
@@ -347,10 +367,10 @@
 	// It's an error if there are any unmatched regexps.
 	for _, re := range regexps {
 		if re != nil {
-			return fmt.Errorf("found no match for %q", re)
+			return nil, fmt.Errorf("found no match for %q", re)
 		}
 	}
-	return nil
+	return matches, nil
 }
 
 // ReadLine reads the next line, if any, from the input stream. It will set
diff --git a/lib/expect/expect_test.go b/lib/expect/expect_test.go
index 44422fa..b0fc48b 100644
--- a/lib/expect/expect_test.go
+++ b/lib/expect/expect_test.go
@@ -95,17 +95,31 @@
 	buffer.WriteString("def\n")
 	buffer.WriteString("abc\n")
 	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
-	s.ExpectSetRE("^bar=.*$", "def$", "^abc$", "^a..$")
+	got := s.ExpectSetRE("^bar=.*$", "def$", "^abc$", "^a..$")
 	if s.Error() != nil {
 		t.Errorf("unexpected error: %s", s.Error())
 	}
+	want := [][]string{{"bar=baz"}, {"def"}, {"abc"}, {"abc"}}
+	if !reflect.DeepEqual(got, want) {
+		t.Error("unexpected result from ExpectSetRE, got %v, want %v", got, want)
+	}
 	buffer.WriteString("ooh\n")
 	buffer.WriteString("aah\n")
 	s.ExpectSetRE("bar=.*", "def")
-	if got, want := s.Error(), "expect_test.go:104: found no match for \"bar=.*\""; got == nil || got.Error() != want {
+	if got, want := s.Error(), "expect_test.go:108: found no match for \"bar=.*\""; got == nil || got.Error() != want {
 		t.Errorf("got %v, want %q", got, want)
 	}
 	s.ExpectEOF()
+
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("hello world\n")
+	buffer.WriteString("this is a test\n")
+	matches := s.ExpectSetRE("hello (world)", "this (is) (a|b) test")
+	if want := [][]string{{"hello world", "world"}, {"this is a test", "is", "a"}}; !reflect.DeepEqual(want, matches) {
+		t.Errorf("unexpected result from ExpectSetRE, got %v, want %v", matches, want)
+	}
 }
 
 func TestExpectSetEventuallyRE(t *testing.T) {
@@ -122,7 +136,7 @@
 		t.Errorf("unexpected error: %s", s.Error())
 	}
 	s.ExpectSetEventuallyRE("abc")
-	if got, want := s.Error(), "expect_test.go:124: found no match for \"abc\""; got == nil || got.Error() != want {
+	if got, want := s.Error(), "expect_test.go:138: found no match for \"abc\""; got == nil || got.Error() != want {
 		t.Errorf("got %q, want %q", got, want)
 	}
 	// Need to clear the EOF from the previous ExpectSetEventuallyRE call
@@ -132,10 +146,21 @@
 	buffer.WriteString("ooh\n")
 	buffer.WriteString("aah\n")
 	s.ExpectSetEventuallyRE("zzz")
-	if got, want := s.Error(), "expect_test.go:134: found no match for \"zzz\""; got == nil || got.Error() != want {
+	if got, want := s.Error(), "expect_test.go:148: found no match for \"zzz\""; got == nil || got.Error() != want {
 		t.Errorf("got %q, want %q", got, want)
 	}
 	s.ExpectEOF()
+
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("not expected\n")
+	buffer.WriteString("hello world\n")
+	buffer.WriteString("this is a test\n")
+	matches := s.ExpectSetEventuallyRE("hello (world)", "this (is) (a|b) test")
+	if want := [][]string{{"hello world", "world"}, {"this is a test", "is", "a"}}; !reflect.DeepEqual(want, matches) {
+		t.Errorf("unexpected result from ExpectSetRE, got %v, want %v", matches, want)
+	}
 }
 
 func TestRead(t *testing.T) {