storage: end-to-end query test with synthetic data
Change-Id: Ie4f48245e1f223486557921a4196570c430e5feb
Reviewed-on: https://go-review.googlesource.com/34934
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/storage/app/query_test.go b/storage/app/query_test.go
new file mode 100644
index 0000000..7c79e94
--- /dev/null
+++ b/storage/app/query_test.go
@@ -0,0 +1,85 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package app
+
+import (
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "testing"
+
+ "golang.org/x/perf/storage/benchfmt"
+)
+
+func TestQuery(t *testing.T) {
+ app := createTestApp(t)
+ defer app.Close()
+
+ // Write 1024 test results to the database. These results
+ // have labels named label0, label1, etc. Each label's value
+ // is an integer whose value is (record number) / (1 << label
+ // number). So 1 record has each value of label0, 2 records
+ // have each value of label1, 4 records have each value of
+ // label2, etc. This allows writing queries that match 2^n records.
+ app.uploadFiles(t, func(mpw *multipart.Writer) {
+ w, err := mpw.CreateFormFile("file", "1.txt")
+ if err != nil {
+ t.Errorf("CreateFormFile: %v", err)
+ }
+ bp := benchfmt.NewPrinter(w)
+ for i := 0; i < 1024; i++ {
+ r := &benchfmt.Result{Labels: make(map[string]string), NameLabels: make(map[string]string), Content: "BenchmarkName 1 ns/op"}
+ for j := uint(0); j < 10; j++ {
+ r.Labels[fmt.Sprintf("label%d", j)] = fmt.Sprintf("%d", i/(1<<j))
+ }
+ r.NameLabels["name"] = "Name"
+ if err := bp.Print(r); err != nil {
+ t.Fatalf("Print: %v", err)
+ }
+ }
+ })
+
+ tests := []struct {
+ q string
+ want []int
+ }{
+ {"label0:0", []int{0}},
+ {"label1:0", []int{0, 1}},
+ {"label0:5 name:Name", []int{5}},
+ {"label0:0 label0:5", nil},
+ }
+ for _, test := range tests {
+ t.Run("query="+test.q, func(t *testing.T) {
+ u := app.srv.URL + "/search?" + url.Values{"q": []string{test.q}}.Encode()
+ resp, err := http.Get(u)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ t.Fatalf("get /search: %v", resp.Status)
+ }
+ 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 r.Labels["label0"] != fmt.Sprintf("%d", num) {
+ t.Errorf("#%d: label0 = %q, want %d", i, r.Labels["label0"], num)
+ }
+ if r.NameLabels["name"] != "Name" {
+ t.Errorf("#%d: name = %q, want %q", i, r.NameLabels["name"], "Name")
+ }
+ }
+ _, err = br.Next()
+ if err != io.EOF {
+ t.Errorf("Next() = %v, want EOF", err)
+ }
+ })
+ }
+}
diff --git a/storage/app/upload_test.go b/storage/app/upload_test.go
index b2bc143..d4ef6aa 100644
--- a/storage/app/upload_test.go
+++ b/storage/app/upload_test.go
@@ -5,6 +5,7 @@
package app
import (
+ "encoding/json"
"fmt"
"io"
"io/ioutil"
@@ -18,46 +19,87 @@
"golang.org/x/perf/storage/fs"
)
-func TestUpload(t *testing.T) {
+type testApp struct {
+ db *db.DB
+ fs *fs.MemFS
+ app *App
+ srv *httptest.Server
+}
+
+func (app *testApp) Close() {
+ app.db.Close()
+ app.srv.Close()
+}
+
+// createTestApp returns a testApp corresponding to a new app
+// serving from an in-memory database and file system on an
+// isolated test HTTP server.
+//
+// When finished with app, the caller must call app.Close().
+func createTestApp(t *testing.T) *testApp {
db, err := db.OpenSQL("sqlite3", ":memory:")
if err != nil {
t.Fatalf("open database: %v", err)
}
- defer db.Close()
fs := fs.NewMemFS()
app := &App{DB: db, FS: fs}
- srv := httptest.NewServer(http.HandlerFunc(app.upload))
- defer srv.Close()
+ mux := http.NewServeMux()
+ app.RegisterOnMux(mux)
+
+ srv := httptest.NewServer(mux)
+
+ return &testApp{db, fs, app, srv}
+}
+
+// uploadFiles calls the /upload endpoint and executes f in a new
+// goroutine to write files to the POST request.
+func (app *testApp) uploadFiles(t *testing.T, f func(*multipart.Writer)) *uploadStatus {
pr, pw := io.Pipe()
mpw := multipart.NewWriter(pw)
+
go func() {
defer pw.Close()
defer mpw.Close()
- // Write the parts here
+ f(mpw)
+ }()
+
+ resp, err := http.Post(app.srv.URL+"/upload", mpw.FormDataContentType(), pr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ t.Fatalf("post /upload: %v", resp.Status)
+ }
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatalf("reading /upload response: %v", err)
+ }
+ t.Logf("/upload response:\n%s", body)
+
+ status := &uploadStatus{}
+ if err := json.Unmarshal(body, status); err != nil {
+ t.Fatalf("unmarshaling /upload response: %v", err)
+ }
+ return status
+}
+
+func TestUpload(t *testing.T) {
+ app := createTestApp(t)
+ defer app.Close()
+
+ app.uploadFiles(t, func(mpw *multipart.Writer) {
w, err := mpw.CreateFormFile("file", "1.txt")
if err != nil {
t.Errorf("CreateFormFile: %v", err)
}
fmt.Fprintf(w, "key: value\nBenchmarkOne 5 ns/op\nkey:value2\nBenchmarkTwo 10 ns/op\n")
- }()
- resp, err := http.Post(srv.URL, mpw.FormDataContentType(), pr)
- if err != nil {
- t.Fatalf("post /upload: %v", err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- t.Errorf("post /upload: %v", resp.Status)
- }
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Errorf("reading /upload response: %v", err)
- }
- t.Logf("/upload response:\n%s", body)
+ })
- if len(fs.Files()) != 1 {
- t.Errorf("/upload wrote %d files, want 1", len(fs.Files()))
+ if len(app.fs.Files()) != 1 {
+ t.Errorf("/upload wrote %d files, want 1", len(app.fs.Files()))
}
}