Merge "veyron/lib/expect: update ExpectSet(Eventually)?RE"
diff --git a/lib/expect/expect.go b/lib/expect/expect.go
index 01e9e35..8388d99 100644
--- a/lib/expect/expect.go
+++ b/lib/expect/expect.go
@@ -237,7 +237,7 @@
 }
 
 // ExpectRE asserts that the next line in the input matches the pattern using
-// regexp.MustCompile(pattern,n).FindAllStringSubmatch.
+// regexp.MustCompile(pattern).FindAllStringSubmatch(..., n).
 func (s *Session) ExpectRE(pattern string, n int) [][]string {
 	if s.Failed() {
 		return [][]string{}
@@ -279,7 +279,9 @@
 // patterns in the order that they are supplied as parameters. Consequently
 // the set may contain repetitions if the same pattern is expected multiple
 // times. The value returned is either:
-//   * nil in the case of an error or no match, or
+//   * nil in the case of an error, or
+//   * nil if n lines are read or EOF is encountered before all expressions are
+//       matched, 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
@@ -296,15 +298,17 @@
 	}
 }
 
-// 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. The value returned is either:
-//   * nil in the case of an error or no match, or
+// ExpectSetEventuallyRE is like ExpectSetRE except that it reads as much
+// output as required rather than just the next n lines. The value returned is
+// either:
+//   * nil in the case of an error, or
+//   * nil if EOF is encountered before all expressions are matched, 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
+// This function stops consuming output as soon as all regular expressions are
+// matched.
 func (s *Session) ExpectSetEventuallyRE(expected ...string) [][]string {
 	if s.Failed() {
 		return nil
@@ -318,9 +322,10 @@
 }
 
 // expectSetRE will look for the expected set of patterns in the next
-// numLines of output or in all remaining output.
+// numLines of output or in all remaining output. If all expressions are
+// matched, no more output is consumed.
 func (s *Session) expectSetRE(numLines int, expected ...string) ([][]string, error) {
-
+	matches := make([][]string, len(expected))
 	regexps := make([]*regexp.Regexp, len(expected))
 	for i, expRE := range expected {
 		re, err := regexp.Compile(expRE)
@@ -329,9 +334,12 @@
 		}
 		regexps[i] = re
 	}
-	actual := []string{}
 	i := 0
+	matchCount := 0
 	for {
+		if matchCount == len(expected) {
+			break
+		}
 		line, err := s.read(readLine)
 		line = strings.TrimRight(line, "\n")
 		s.log(err, "ExpectSetRE: %s", line)
@@ -341,34 +349,38 @@
 			}
 			break
 		}
-		actual = append(actual, line)
+
+		// Match the line against all regexp's and remove each regexp
+		// that matches.
+		for i, re := range regexps {
+			if re == nil {
+				continue
+			}
+			match := re.FindStringSubmatch(line)
+			if match != nil {
+				matchCount++
+				regexps[i] = nil
+				matches[i] = match
+				// Don't allow this line to be matched by more than one re.
+				break
+			}
+		}
+
 		i++
 		if numLines > 0 && i >= numLines {
 			break
 		}
 	}
 
-	matches := make([][]string, len(expected))
-	// Match each line against all regexp's and remove each regexp
-	// that matches.
-	for _, l := range actual {
-		for i, re := range regexps {
-			if re == nil {
-				continue
-			}
-			match := re.FindStringSubmatch(l)
-			if match != nil {
-				regexps[i] = nil
-				matches[i] = match
-				break
-			}
+	// It's an error if there are any unmatched regexps.
+	unmatchedRes := make([]string, 0)
+	for i, re := range regexps {
+		if re != nil {
+			unmatchedRes = append(unmatchedRes, expected[i])
 		}
 	}
-	// It's an error if there are any unmatched regexps.
-	for _, re := range regexps {
-		if re != nil {
-			return nil, fmt.Errorf("found no match for %q", re)
-		}
+	if len(unmatchedRes) > 0 {
+		return nil, fmt.Errorf("found no match for [%v]", strings.Join(unmatchedRes, ","))
 	}
 	return matches, nil
 }
diff --git a/lib/expect/expect_test.go b/lib/expect/expect_test.go
index b0fc48b..fb6f579 100644
--- a/lib/expect/expect_test.go
+++ b/lib/expect/expect_test.go
@@ -31,7 +31,6 @@
 	} else {
 		t.Log(s.Error())
 	}
-	s.ExpectEOF()
 }
 
 func TestExpectf(t *testing.T) {
@@ -40,10 +39,10 @@
 	buffer.WriteString("bar 22\n")
 	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
 	s.Expectf("bar %d", 22)
+	s.ExpectEOF()
 	if err := s.Error(); err != nil {
 		t.Error(err)
 	}
-	s.ExpectEOF()
 }
 
 func TestEOF(t *testing.T) {
@@ -84,7 +83,6 @@
 			t.Errorf("missing or wrong error: %v", s.Error())
 		}
 	}
-	s.ExpectEOF()
 }
 
 func TestExpectSetRE(t *testing.T) {
@@ -101,15 +99,14 @@
 	}
 	want := [][]string{{"bar=baz"}, {"def"}, {"abc"}, {"abc"}}
 	if !reflect.DeepEqual(got, want) {
-		t.Error("unexpected result from ExpectSetRE, got %v, want %v", got, want)
+		t.Errorf("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:108: found no match for \"bar=.*\""; got == nil || got.Error() != want {
-		t.Errorf("got %v, want %q", got, want)
+	if got, want := s.Error(), "found no match for [bar=.*,def]"; got == nil || !strings.Contains(got.Error(), want) {
+		t.Errorf("got %v, wanted something containing %q", got, want)
 	}
-	s.ExpectEOF()
 
 	buf = []byte{}
 	buffer = bytes.NewBuffer(buf)
@@ -120,6 +117,63 @@
 	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)
 	}
+
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("aaa\n")
+
+	// Expect 3 x aaa to match.
+	s.ExpectSetRE("aaa", "aaa", "aaa")
+	if s.Error() != nil {
+		t.Errorf("unexpected error: %v", s.Error())
+	}
+
+	// Expecting one more aaa should fail: the entire input should have been consumed.
+	s.ExpectSetRE("aaa")
+	if s.Error() == nil {
+		t.Errorf("expected error but got none")
+	}
+
+	// Test a buffer that contains a match but not within the number of lines we expect.
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("bbb\n")
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.ExpectSetRE("bbb")
+	if s.Error() == nil {
+		t.Fatalf("expected error but got none")
+	}
+
+	// Test a buffer that contains a match and leaves us with nothing more to read.
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("bbb\n")
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.ExpectSetRE("bbb")
+	if s.Error() == nil {
+		t.Fatalf("expected error but got none")
+	}
+
+	// Now ensure that each regular expression matches a unique line.
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	buffer.WriteString("a 1\n")
+	buffer.WriteString("a 2\n")
+	buffer.WriteString("a 3\n")
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	matches = s.ExpectSetRE("\\w (\\d)", "a (\\d)", "a (\\d)")
+	want = [][]string{{"a 1", "1"}, {"a 2", "2"}, {"a 3", "3"}}
+	if !reflect.DeepEqual(matches, want) {
+		t.Fatalf("unexpected result from ExpectSetRE, got %v, want %v", matches, want)
+	}
+	if s.ExpectEOF() != nil {
+		t.Fatalf("expected EOF but did not get it")
+	}
 }
 
 func TestExpectSetEventuallyRE(t *testing.T) {
@@ -135,10 +189,19 @@
 	if s.Error() != nil {
 		t.Errorf("unexpected error: %s", s.Error())
 	}
+
+	// Should see one more abc match after the we read def.
 	s.ExpectSetEventuallyRE("abc")
-	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)
+	if s.Error() != nil {
+		t.Errorf("unexpected error: %s", s.Error())
 	}
+
+	// Trying to match abc again should yield an error.
+	s.ExpectSetEventuallyRE("abc")
+	if got, want := s.Error(), "found no match for [abc]"; got == nil || !strings.Contains(got.Error(), want) {
+		t.Errorf("got %q, wanted something containing %q", got, want)
+	}
+
 	// Need to clear the EOF from the previous ExpectSetEventuallyRE call
 	buf = []byte{}
 	buffer = bytes.NewBuffer(buf)
@@ -146,10 +209,9 @@
 	buffer.WriteString("ooh\n")
 	buffer.WriteString("aah\n")
 	s.ExpectSetEventuallyRE("zzz")
-	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)
+	if got, want := s.Error(), "found no match for [zzz]"; got == nil || !strings.Contains(got.Error(), want) {
+		t.Errorf("got %q, wanted something containing %q", got, want)
 	}
-	s.ExpectEOF()
 
 	buf = []byte{}
 	buffer = bytes.NewBuffer(buf)
@@ -161,6 +223,18 @@
 	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)
 	}
+
+	// Test error output with multiple unmatched res.
+	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")
+	s.ExpectSetEventuallyRE("blargh", "blerg", "blorg")
+	if got, want := s.Error(), "found no match for [blargh,blerg,blorg]"; !strings.Contains(got.Error(), want) {
+		t.Errorf("got %q, wanted something containing %q", got, want)
+	}
 }
 
 func TestRead(t *testing.T) {
@@ -192,5 +266,7 @@
 	if got != want {
 		t.Errorf("got %q, want %q", got, want)
 	}
-	s.ExpectEOF()
+	if s.ExpectEOF() != nil {
+		t.Fatalf("expected EOF but did not get it")
+	}
 }