storage: make benchfmt.Reader use Scanner-style interface

Change-Id: I052c2b3cc3440f081ef11ba5d999bf8e7f230c6a
Reviewed-on: https://go-review.googlesource.com/35450
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/storage/app/query_test.go b/storage/app/query_test.go
index ac689a9..d91e56c 100644
--- a/storage/app/query_test.go
+++ b/storage/app/query_test.go
@@ -6,7 +6,6 @@
 
 import (
 	"fmt"
-	"io"
 	"mime/multipart"
 	"net/http"
 	"net/url"
@@ -65,10 +64,10 @@
 			}
 			br := benchfmt.NewReader(resp.Body)
 			for i, num := range test.want {
-				r, err := br.Next()
-				if err != nil {
-					t.Fatalf("#%d: Next() = %v, want nil", i, err)
+				if !br.Next() {
+					t.Fatalf("#%d: Next() = false, want true (Err() = %v)", i, br.Err())
 				}
+				r := br.Result()
 				if r.Labels["label0"] != fmt.Sprintf("%d", num) {
 					t.Errorf("#%d: label0 = %q, want %d", i, r.Labels["label0"], num)
 				}
@@ -79,9 +78,11 @@
 					t.Errorf("#%d: by = %q, want %q", i, r.Labels["uploader"], "user")
 				}
 			}
-			_, err = br.Next()
-			if err != io.EOF {
-				t.Errorf("Next() = %v, want EOF", err)
+			if br.Next() {
+				t.Fatalf("Next() = true, want false")
+			}
+			if err := br.Err(); err != nil {
+				t.Errorf("Err() = %v, want nil", err)
 			}
 		})
 	}
diff --git a/storage/app/upload.go b/storage/app/upload.go
index 475ffca..4759f59 100644
--- a/storage/app/upload.go
+++ b/storage/app/upload.go
@@ -194,20 +194,17 @@
 	br := benchfmt.NewReader(tr)
 	br.AddLabels(meta)
 	i := 0
-	for {
-		result, err := br.Next()
-		if err != nil {
-			if err != io.EOF {
-				return err
-			}
-			if i == 0 {
-				return errors.New("no valid benchmark lines found")
-			}
-			return nil
-		}
+	for br.Next() {
 		i++
-		if err := upload.InsertRecord(result); err != nil {
+		if err := upload.InsertRecord(br.Result()); err != nil {
 			return err
 		}
 	}
+	if err := br.Err(); err != nil {
+		return err
+	}
+	if i == 0 {
+		return errors.New("no valid benchmark lines found")
+	}
+	return nil
 }
diff --git a/storage/benchfmt/benchfmt.go b/storage/benchfmt/benchfmt.go
index e31b580..00e696a 100644
--- a/storage/benchfmt/benchfmt.go
+++ b/storage/benchfmt/benchfmt.go
@@ -18,6 +18,15 @@
 )
 
 // Reader reads benchmark results from an io.Reader.
+// Use Next to advance through the results.
+//
+//   br := benchfmt.NewReader(r)
+//   for br.Next() {
+//     res := br.Result()
+//     ...
+//   }
+//   err = br.Err() // get any error encountered during iteration
+//   ...
 type Reader struct {
 	s      *bufio.Scanner
 	labels Labels
@@ -25,10 +34,11 @@
 	// file or provided by AddLabels. They cannot be overridden.
 	permLabels Labels
 	lineNum    int
+	// cached from the last call to Next
+	result *Result
+	err    error
 }
 
-// TODO(quentin): Make Reader have a Scanner-style interface instead, to match db.Query.
-
 // NewReader creates a BenchmarkReader that reads from r.
 func NewReader(r io.Reader) *Reader {
 	return &Reader{
@@ -175,7 +185,10 @@
 
 // Next returns the next benchmark result from the file. If there are
 // no further results, it returns nil, io.EOF.
-func (r *Reader) Next() (*Result, error) {
+func (r *Reader) Next() bool {
+	if r.err != nil {
+		return false
+	}
 	copied := false
 	havePerm := r.permLabels != nil
 	for r.s.Scan() {
@@ -208,13 +221,29 @@
 			}
 		}
 		if fullName, ok := parseBenchmarkLine(line); ok {
-			return newResult(r.labels, r.lineNum, fullName, line), nil
+			r.result = newResult(r.labels, r.lineNum, fullName, line)
+			return true
 		}
 	}
 	if err := r.s.Err(); err != nil {
-		return nil, err
+		r.err = err
+		return false
 	}
-	return nil, io.EOF
+	r.err = io.EOF
+	return false
+}
+
+// Result returns the most recent result generated by a call to Next.
+func (r *Reader) Result() *Result {
+	return r.result
+}
+
+// Err returns the error state of the reader.
+func (r *Reader) Err() error {
+	if r.err == io.EOF {
+		return nil
+	}
+	return r.err
 }
 
 // parseKeyValueLine attempts to parse line as a key: value pair. ok
diff --git a/storage/benchfmt/benchfmt_test.go b/storage/benchfmt/benchfmt_test.go
index 2bde473..17c9340 100644
--- a/storage/benchfmt/benchfmt_test.go
+++ b/storage/benchfmt/benchfmt_test.go
@@ -7,7 +7,6 @@
 import (
 	"bytes"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"os"
 	"os/exec"
@@ -18,17 +17,13 @@
 
 func readAllResults(t *testing.T, r *Reader) []*Result {
 	var out []*Result
-	for {
-		result, err := r.Next()
-		switch err {
-		case io.EOF:
-			return out
-		case nil:
-			out = append(out, result)
-		default:
-			t.Fatal(err)
-		}
+	for r.Next() {
+		out = append(out, r.Result())
 	}
+	if err := r.Err(); err != nil {
+		t.Fatal(err)
+	}
+	return out
 }
 
 func TestBenchmarkReader(t *testing.T) {
diff --git a/storage/cmd/reindex/reindex.go b/storage/cmd/reindex/reindex.go
index e18a5c6..b1020ba 100644
--- a/storage/cmd/reindex/reindex.go
+++ b/storage/cmd/reindex/reindex.go
@@ -16,7 +16,6 @@
 	"context"
 	"flag"
 	"fmt"
-	"io"
 	"log"
 	"os"
 	"strings"
@@ -131,17 +130,13 @@
 	}
 	defer r.Close()
 	br := benchfmt.NewReader(r)
-	for {
-		res, err := br.Next()
-		if err == io.EOF {
-			break
-		}
-		if err != nil {
+	for br.Next() {
+		if err := u.InsertRecord(br.Result()); err != nil {
 			return err
 		}
-		if err := u.InsertRecord(res); err != nil {
-			return err
-		}
+	}
+	if err := br.Err(); err != nil {
+		return err
 	}
 	return nil
 }
diff --git a/storage/db/db.go b/storage/db/db.go
index 385dc09..a87be2e 100644
--- a/storage/db/db.go
+++ b/storage/db/db.go
@@ -474,7 +474,15 @@
 		return false
 	}
 	// TODO(quentin): Needs to change when one row contains multiple Results.
-	q.result, q.err = benchfmt.NewReader(bytes.NewReader(content)).Next()
+	br := benchfmt.NewReader(bytes.NewReader(content))
+	if !br.Next() {
+		q.err = br.Err()
+		if q.err == nil {
+			q.err = io.ErrUnexpectedEOF
+		}
+		return false
+	}
+	q.result = br.Result()
 	return q.err == nil
 }
 
diff --git a/storage/db/db_test.go b/storage/db/db_test.go
index 13ee26a..e376cda 100644
--- a/storage/db/db_test.go
+++ b/storage/db/db_test.go
@@ -188,13 +188,11 @@
 key: value
 BenchmarkName 1 ns/op
 `))
-
-	r, err := br.Next()
-	if err != nil {
-		t.Fatalf("BenchmarkReader.Next: %v", err)
+	if !br.Next() {
+		t.Fatalf("unable to read test string: %v", br.Err())
 	}
 
-	if err := u.InsertRecord(r); err != nil {
+	if err := u.InsertRecord(br.Result()); err != nil {
 		t.Fatalf("InsertRecord: %v", err)
 	}