storage: add -cloud test flag to run tests on Cloud SQL

Change-Id: Ifb1baf9a94c28167bedf61c4c2495b724349bdf3
Reviewed-on: https://go-review.googlesource.com/35052
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/storage/app/upload_test.go b/storage/app/upload_test.go
index d4ef6aa..90581cc 100644
--- a/storage/app/upload_test.go
+++ b/storage/app/upload_test.go
@@ -15,19 +15,21 @@
 	"testing"
 
 	"golang.org/x/perf/storage/db"
+	"golang.org/x/perf/storage/db/dbtest"
 	_ "golang.org/x/perf/storage/db/sqlite3"
 	"golang.org/x/perf/storage/fs"
 )
 
 type testApp struct {
-	db  *db.DB
-	fs  *fs.MemFS
-	app *App
-	srv *httptest.Server
+	db        *db.DB
+	dbCleanup func()
+	fs        *fs.MemFS
+	app       *App
+	srv       *httptest.Server
 }
 
 func (app *testApp) Close() {
-	app.db.Close()
+	app.dbCleanup()
 	app.srv.Close()
 }
 
@@ -37,10 +39,7 @@
 //
 // 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)
-	}
+	db, cleanup := dbtest.NewDB(t)
 
 	fs := fs.NewMemFS()
 
@@ -51,7 +50,7 @@
 
 	srv := httptest.NewServer(mux)
 
-	return &testApp{db, fs, app, srv}
+	return &testApp{db, cleanup, fs, app, srv}
 }
 
 // uploadFiles calls the /upload endpoint and executes f in a new
diff --git a/storage/db/db.go b/storage/db/db.go
index ac61a75..3c43810 100644
--- a/storage/db/db.go
+++ b/storage/db/db.go
@@ -358,6 +358,13 @@
 	return q.err
 }
 
+// CountUploads returns the number of uploads in the database.
+func (db *DB) CountUploads() (int, error) {
+	var uploads int
+	err := db.sql.QueryRow("SELECT COUNT(*) FROM Uploads").Scan(&uploads)
+	return uploads, err
+}
+
 // Close closes the database connections, releasing any open resources.
 func (db *DB) Close() error {
 	if err := db.insertUpload.Close(); err != nil {
diff --git a/storage/db/db_test.go b/storage/db/db_test.go
index 84845a4..85c4820 100644
--- a/storage/db/db_test.go
+++ b/storage/db/db_test.go
@@ -13,7 +13,7 @@
 
 	"golang.org/x/perf/storage/benchfmt"
 	. "golang.org/x/perf/storage/db"
-	_ "golang.org/x/perf/storage/db/sqlite3"
+	"golang.org/x/perf/storage/db/dbtest"
 )
 
 // Most of the db package is tested via the end-to-end-tests in perf/storage/app.
@@ -38,11 +38,8 @@
 
 // TestNewUpload verifies that NewUpload and InsertRecord wrote the correct rows to the database.
 func TestNewUpload(t *testing.T) {
-	db, err := OpenSQL("sqlite3", ":memory:")
-	if err != nil {
-		t.Fatalf("open database: %v", err)
-	}
-	defer db.Close()
+	db, cleanup := dbtest.NewDB(t)
+	defer cleanup()
 
 	u, err := db.NewUpload(context.Background())
 	if err != nil {
@@ -104,11 +101,8 @@
 }
 
 func TestQuery(t *testing.T) {
-	db, err := OpenSQL("sqlite3", ":memory:")
-	if err != nil {
-		t.Fatalf("open database: %v", err)
-	}
-	defer db.Close()
+	db, cleanup := dbtest.NewDB(t)
+	defer cleanup()
 
 	u, err := db.NewUpload(context.Background())
 	if err != nil {
diff --git a/storage/db/dbtest/dbtest.go b/storage/db/dbtest/dbtest.go
new file mode 100644
index 0000000..027425c
--- /dev/null
+++ b/storage/db/dbtest/dbtest.go
@@ -0,0 +1,89 @@
+// 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 dbtest
+
+import (
+	"crypto/rand"
+	"database/sql"
+	"encoding/base64"
+	"flag"
+	"fmt"
+	"testing"
+
+	_ "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql"
+	"golang.org/x/perf/storage/db"
+	_ "golang.org/x/perf/storage/db/sqlite3"
+)
+
+var cloud = flag.Bool("cloud", false, "connect to Cloud SQL database instead of in-memory SQLite")
+var cloudsql = flag.String("cloudsql", "golang-org:us-central1:golang-org", "name of Cloud SQL instance to run tests on")
+
+// createEmptyCloudDB makes a new, empty database for the test.
+func createEmptyCloudDB(t *testing.T) (dsn string, cleanup func()) {
+	buf := make([]byte, 6)
+	if _, err := rand.Read(buf); err != nil {
+		t.Fatal(err)
+	}
+
+	name := "perfdata-test-" + base64.RawURLEncoding.EncodeToString(buf)
+
+	prefix := fmt.Sprintf("root:@cloudsql(%s)/", *cloudsql)
+
+	db, err := sql.Open("mysql", prefix)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := db.Exec(fmt.Sprintf("CREATE DATABASE `%s`", name)); err != nil {
+		db.Close()
+		t.Fatal(err)
+	}
+
+	t.Logf("Using database %q", name)
+
+	return prefix + name, func() {
+		if _, err := db.Exec(fmt.Sprintf("DROP DATABASE `%s`", name)); err != nil {
+			t.Error(err)
+		}
+		db.Close()
+	}
+}
+
+// NewDB makes a connection to a testing database, either sqlite3 or
+// Cloud SQL depending on the -cloud flag. cleanup must be called when
+// done with the testing database, instead of calling db.Close()
+func NewDB(t *testing.T) (*db.DB, func()) {
+	driverName, dataSourceName := "sqlite3", ":memory:"
+	var cloudCleanup func()
+	if *cloud {
+		driverName = "mysql"
+		dataSourceName, cloudCleanup = createEmptyCloudDB(t)
+	}
+	d, err := db.OpenSQL(driverName, dataSourceName)
+	if err != nil {
+		if cloudCleanup != nil {
+			cloudCleanup()
+		}
+		t.Fatalf("open database: %v", err)
+	}
+
+	cleanup := func() {
+		if cloudCleanup != nil {
+			cloudCleanup()
+		}
+		d.Close()
+	}
+	// Make sure the database really is empty.
+	uploads, err := d.CountUploads()
+	if err != nil {
+		cleanup()
+		t.Fatal(err)
+	}
+	if uploads != 0 {
+		cleanup()
+		t.Fatalf("found %d row(s) in Uploads, want 0", uploads)
+	}
+	return d, cleanup
+}