Repository: google/exposure-notifications-server
Pull request #1197 (2020-11-29 00:49:26)

Use a single container for database tests
Well, it's not exactly a single container, but more a single container per test package. Go tests are run in different processes, so it's not possible to share a single global container, but we share it with all tests in a single package, about 12 containers in total.

Previously we were spinning up a new container, starting the database, running migrations, seeding data, running tests, and stopping the container. This led to resource exhaustion and flakey tests. This new design involves slightly more manual work: once for each package that wants a database, you have to manually add 5 LOC to start/stop the container for that package. The tests run in 1/3 of the time though, and I've had to turn my furnace back on in the house since my CPU no longer provides enough enthalpy to heat the entire house when running the tests (joking, this is a good thing).

As an even further optimization, migrations are only run once per package. When the container first starts, we connect, run migrations, and then save that database as a "template" database. When an actual test needs a database, we clone that database (which has the complete schema and any test data) which is a near-instant operation. Each test is still fully isolated, but we only eat the cost of the database migration once (per package). The overhead of cloning a database amortizes over just 2 tests.

**Release Note**

<!-- Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". -->

```release-note
NONE
```

google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/1197#" title="Author self-approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/main/OWNERS)~~ [sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
sethvargo: 🙏 Prow
google-oss-robot: @sethvargo: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-en-server-release-unit | 1ad2d9434d97f4b20be259ebcead0b1f90756840 | [link](https://oss-prow.knative.dev/view/gs/oss-prow/pr-logs/pull/google_exposure-notifications-server/1197/pull-en-server-release-unit/1332849119890247680) | `/test pull-en-server-release-unit`

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
google-oss-robot: @sethvargo: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-en-server-release-unit | 2947a2c83ccb4361ded059cdb8ef149b0442a134 | [link](https://oss-prow.knative.dev/view/gs/oss-prow/pr-logs/pull/google_exposure-notifications-server/1197/pull-en-server-release-unit/1332849534023241728) | `/test pull-en-server-release-unit`

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sethvargo: Okay, I've run these tests 10 times now and got no failures, so I'm 10/10 confident this will fix our flakes.

/cc @mikehelmick 
/cc @chaodaiG 
/cc @whaught 
chaodaiG: This is awesome!
/lgtm
chaodaiG: /approve
google-oss-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/1197#issuecomment-735313868" title="Approved">chaodaiG</a>*, *<a href="https://github.com/google/exposure-notifications-server/pull/1197#" title="Author self-approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/main/OWNERS)~~ [chaodaiG,sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/Makefile b/Makefile
index 0b31544..dbaaf99 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,15 @@
 
 VETTERS = "asmdecl,assign,atomic,bools,buildtag,cgocall,composites,copylocks,errorsas,httpresponse,loopclosure,lostcancel,nilfunc,printf,shift,stdmethods,structtag,tests,unmarshal,unreachable,unsafeptr,unusedresult"
 GOFMT_FILES = $(shell go list -f '{{.Dir}}' ./... | grep -v '/pb')
+GO_FILES = $(shell find . -name \*.go)
+
+copyrightcheck:
+	@CHANGES="$$(grep -L "Copyright" $(GO_FILES))"; \
+		if [ -n "$${CHANGES}" ]; then \
+			echo "$${CHANGES}\n\n"; \
+			exit 1; \
+		fi
+.PHONY: copyrightcheck
 
 fmtcheck:
 	@command -v goimports > /dev/null 2>&1 || go get golang.org/x/tools/cmd/goimports
@@ -35,9 +44,10 @@ spellcheck:
 	@misspell -locale="US" -error -source="text" **/*
 .PHONY: spellcheck
 
+# SA3000 is not required in Go 1.15+: https://github.com/dominikh/go-tools/issues/708
 staticcheck:
 	@command -v staticcheck > /dev/null 2>&1 || go get honnef.co/go/tools/cmd/staticcheck
-	@staticcheck -checks="all" -tests $(GOFMT_FILES)
+	@staticcheck -checks="all,-SA3000" -tests $(GOFMT_FILES)
 .PHONY: staticcheck
 
 test:
diff --git a/internal/database/database_util.go b/internal/database/database_util.go
index 12f069d..c726291 100644
--- a/internal/database/database_util.go
+++ b/internal/database/database_util.go
@@ -16,7 +16,10 @@ package database
 
 import (
 	"context"
+	"crypto/rand"
+	"encoding/hex"
 	"errors"
+	"flag"
 	"fmt"
 	"net"
 	"net/url"
@@ -25,12 +28,17 @@ import (
 	"runtime"
 	"strconv"
 	"strings"
+	"sync"
 	"testing"
 	"time"
 
 	"github.com/golang-migrate/migrate/v4"
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+	"github.com/jackc/pgx/v4"
 	"github.com/jackc/pgx/v4/pgxpool"
 	"github.com/ory/dockertest"
+	"github.com/ory/dockertest/docker"
 	"github.com/sethvargo/go-retry"
 
 	// imported to register the postgres migration driver
@@ -40,128 +48,284 @@ import (
 	// imported to register the "postgres" database driver for migrate
 )
 
-// NewTestDatabaseWithConfig creates a new database suitable for use in testing.
-// This should not be used outside of testing, but it is exposed in the main
-// package so it can be shared with other packages.
+const (
+	// databaseName is the name of the template database to clone.
+	databaseName = "test-db-template"
+
+	// databaseUser and databasePassword are the username and password for
+	// connecting to the database. These values are only used for testing.
+	databaseUser     = "test-user"
+	databasePassword = "testing123"
+
+	// defaultPostgresImageRef is the default database container to use if none is
+	// specified.
+	defaultPostgresImageRef = "postgres:13-alpine"
+)
+
+var (
+	// ApproxTime is a compare helper for clock skew.
+	ApproxTime = cmp.Options{cmpopts.EquateApproxTime(1 * time.Second)}
+)
+
+// TestInstance is a wrapper around the Docker-based database instance.
+type TestInstance struct {
+	pool      *dockertest.Pool
+	container *dockertest.Resource
+	url       *url.URL
+
+	conn     *pgx.Conn
+	connLock sync.Mutex
+
+	skipReason string
+}
+
+// MustTestInstance is NewTestInstance, except it prints errors to stderr and
+// calls os.Exit when finished. Callers can call Close or MustClose().
+func MustTestInstance() *TestInstance {
+	testDatabaseInstance, err := NewTestInstance()
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+	return testDatabaseInstance
+}
+
+// NewTestInstance creates a new Docker-based database instance. It also creates
+// an initial database, runs the migrations, and sets that database as a
+// template to be cloned by future tests.
+//
+// This should not be used outside of testing, but it is exposed in the package
+// so it can be shared with other packages. It should be called and instantiated
+// in TestMain.
 //
 // All database tests can be skipped by running `go test -short` or by setting
 // the `SKIP_DATABASE_TESTS` environment variable.
-func NewTestDatabaseWithConfig(tb testing.TB) (*DB, *Config) {
-	tb.Helper()
+func NewTestInstance() (*TestInstance, error) {
+	// Querying for -short requires flags to be parsed.
+	if !flag.Parsed() {
+		flag.Parse()
+	}
 
+	// Do not create an instance in -short mode.
 	if testing.Short() {
-		tb.Skipf("🚧 Skipping database tests (short!")
+		return &TestInstance{
+			skipReason: "🚧 Skipping database tests (-short flag provided)!",
+		}, nil
 	}
 
+	// Do not create an instance if database tests are explicitly skipped.
 	if skip, _ := strconv.ParseBool(os.Getenv("SKIP_DATABASE_TESTS")); skip {
-		tb.Skipf("🚧 Skipping database tests (SKIP_DATABASE_TESTS is set)!")
+		return &TestInstance{
+			skipReason: "🚧 Skipping database tests (SKIP_DATABASE_TESTS is set)!",
+		}, nil
 	}
 
-	// Context.
 	ctx := context.Background()
 
-	// Create the pool (docker instance).
+	// Create the pool.
 	pool, err := dockertest.NewPool("")
 	if err != nil {
-		tb.Fatalf("failed to create Docker pool: %s", err)
+		return nil, fmt.Errorf("failed to create database docker pool: %w", err)
 	}
 
 	// Determine the container image to use.
-	repo, tag := postgresRepo(tb)
+	repository, tag, err := postgresRepo()
+	if err != nil {
+		return nil, fmt.Errorf("failed to determine database repository: %w", err)
+	}
 
-	// Start the container.
-	dbname, username, password := "en-server", "my-username", "abcd1234"
+	// Start the actual container.
 	container, err := pool.RunWithOptions(&dockertest.RunOptions{
-		Repository: repo,
+		Repository: repository,
 		Tag:        tag,
 		Env: []string{
 			"LANG=C",
-			"POSTGRES_DB=" + dbname,
-			"POSTGRES_USER=" + username,
-			"POSTGRES_PASSWORD=" + password,
+			"POSTGRES_DB=" + databaseName,
+			"POSTGRES_USER=" + databaseUser,
+			"POSTGRES_PASSWORD=" + databasePassword,
 		},
+	}, func(c *docker.HostConfig) {
+		c.AutoRemove = true
+		c.RestartPolicy = docker.RestartPolicy{Name: "no"}
 	})
 	if err != nil {
-		tb.Fatalf("failed to start postgres container: %s", err)
+		return nil, fmt.Errorf("failed to start database container: %w", err)
 	}
 
-	// Force the database container to stop.
+	// Stop the container after its been running for too long. No since test suite
+	// should take super long.
 	if err := container.Expire(120); err != nil {
-		tb.Fatalf("failed to force-stop container: %v", err)
+		return nil, fmt.Errorf("failed to expire database container: %w", err)
 	}
 
-	// Ensure container is cleaned up.
-	tb.Cleanup(func() {
-		if err := pool.Purge(container); err != nil {
-			tb.Fatalf("failed to cleanup postgres container: %s", err)
-		}
-	})
-
-	// Get the host. On Mac, Docker runs in a VM.
-	host := container.GetBoundIP("5432/tcp")
-	port := container.GetPort("5432/tcp")
-	addr := net.JoinHostPort(host, port)
-
 	// Build the connection URL.
-	connURL := &url.URL{
-		Scheme: "postgres",
-		User:   url.UserPassword(username, password),
-		Host:   addr,
-		Path:   dbname,
+	connectionURL := &url.URL{
+		Scheme:   "postgres",
+		User:     url.UserPassword(databaseUser, databasePassword),
+		Host:     container.GetHostPort("5432/tcp"),
+		Path:     databaseName,
+		RawQuery: "sslmode=disable",
 	}
-	q := connURL.Query()
-	q.Add("sslmode", "disable")
-	connURL.RawQuery = q.Encode()
 
-	// Wait for the container to start.
+	// Create retryable.
 	b, err := retry.NewConstant(1 * time.Second)
 	if err != nil {
-		tb.Fatalf("failed to configure backoff: %v", err)
+		return nil, fmt.Errorf("failed to configure backoff: %w", err)
 	}
 	b = retry.WithMaxRetries(30, b)
 
-	// Establish a connection to the database. Use a Fibonacci backoff instead of
-	// exponential so wait times scale appropriately.
-	var dbpool *pgxpool.Pool
+	// Try to establish a connection to the database, with retries.
+	var conn *pgx.Conn
 	if err := retry.Do(ctx, b, func(ctx context.Context) error {
 		var err error
-		dbpool, err = pgxpool.Connect(ctx, connURL.String())
+		conn, err = pgx.Connect(ctx, connectionURL.String())
 		if err != nil {
 			return retry.RetryableError(err)
 		}
+		if err := conn.Ping(ctx); err != nil {
+			return retry.RetryableError(err)
+		}
 		return nil
 	}); err != nil {
-		tb.Fatalf("failed to start postgres: %s", err)
+		return nil, fmt.Errorf("failed waiting for database container to be ready: %w", err)
 	}
 
 	// Run the migrations.
-	if err := dbMigrate(connURL.String()); err != nil {
-		tb.Fatalf("failed to migrate database: %s", err)
+	if err := dbMigrate(connectionURL.String()); err != nil {
+		return nil, fmt.Errorf("failed to migrate database: %w", err)
+	}
+
+	// Return the instance.
+	return &TestInstance{
+		pool:      pool,
+		container: container,
+		conn:      conn,
+		url:       connectionURL,
+	}, nil
+}
+
+// MustClose is like Close except it prints the error to stderr and calls os.Exit.
+func (i *TestInstance) MustClose() error {
+	if err := i.Close(); err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
 	}
+	return nil
+}
+
+// Close terminates the test database instance, cleaning up any resources.
+func (i *TestInstance) Close() (retErr error) {
+	// Do not attempt to close  things when there's nothing to close.
+	if i.skipReason != "" {
+		return
+	}
+
+	defer func() {
+		if err := i.pool.Purge(i.container); err != nil {
+			retErr = fmt.Errorf("failed to purge database container: %w", err)
+			return
+		}
+	}()
 
-	// Create the db instance.
+	ctx := context.Background()
+	if err := i.conn.Close(ctx); err != nil {
+		retErr = fmt.Errorf("failed to close connection: %w", err)
+		return
+	}
+
+	return
+}
+
+// NewDatabase creates a new database suitable for use in testing. It returns an
+// established database connection and the configuration.
+func (i *TestInstance) NewDatabase(tb testing.TB) (*DB, *Config) {
+	tb.Helper()
+
+	// Ensure we should actually create the database.
+	if i.skipReason != "" {
+		tb.Skip(i.skipReason)
+	}
+
+	// Clone the template database.
+	newDatabaseName, err := i.clone()
+	if err != nil {
+		tb.Fatal(err)
+	}
+
+	// Build the new connection URL for the new database name. Query params are
+	// dropped with ResolveReference, so we have to re-add disabling SSL over
+	// localhost.
+	connectionURL := i.url.ResolveReference(&url.URL{Path: newDatabaseName})
+	connectionURL.RawQuery = "sslmode=disable"
+
+	// Establish a connection to the database.
+	ctx := context.Background()
+	dbpool, err := pgxpool.Connect(ctx, connectionURL.String())
+	if err != nil {
+		tb.Fatalf("failed to connect to database %q: %s", newDatabaseName, err)
+	}
+
+	// Create the Go database instance.
 	db := &DB{Pool: dbpool}
 
-	// Close db when done.
+	// Close connection and delete database when done.
 	tb.Cleanup(func() {
-		db.Close(context.Background())
+		ctx := context.Background()
+
+		// Close connection first. It is an error to drop a database with active
+		// connections.
+		db.Close(ctx)
+
+		// Drop the database to keep the container from running out of resources.
+		q := fmt.Sprintf(`DROP DATABASE IF EXISTS "%s" WITH (FORCE);`, newDatabaseName)
+
+		i.connLock.Lock()
+		defer i.connLock.Unlock()
+
+		if _, err := i.conn.Exec(ctx, q); err != nil {
+			tb.Errorf("failed to drop database %q: %s", newDatabaseName, err)
+		}
 	})
 
+	host, port, err := net.SplitHostPort(i.url.Host)
+	if err != nil {
+		tb.Errorf("failed to split host/port %q: %w", i.url.Host, err)
+	}
+
 	return db, &Config{
-		Name:     dbname,
-		User:     username,
-		Host:     container.GetBoundIP("5432/tcp"),
-		Port:     container.GetPort("5432/tcp"),
+		Name:     newDatabaseName,
+		User:     databaseUser,
+		Host:     host,
+		Port:     port,
 		SSLMode:  "disable",
-		Password: password,
+		Password: databasePassword,
 	}
 }
 
-func NewTestDatabase(tb testing.TB) *DB {
-	tb.Helper()
+// clone creates a new database with a random name from the template instance.
+func (i *TestInstance) clone() (string, error) {
+	// Generate a random database name.
+	name, err := randomDatabaseName()
+	if err != nil {
+		return "", fmt.Errorf("failed to generate random database name: %w", err)
+	}
+
+	// Setup context and create SQL command. Unfortunately we cannot use parameter
+	// injection here as that's only valid for prepared statements, for which this
+	// is not. Fortunately both inputs can be trusted in this case.
+	ctx := context.Background()
+	q := fmt.Sprintf(`CREATE DATABASE "%s" WITH TEMPLATE "%s";`, name, databaseName)
+
+	// Unfortunately postgres does not allow parallel database creation from the
+	// same template, so this is guarded with a lock.
+	i.connLock.Lock()
+	defer i.connLock.Unlock()
 
-	db, _ := NewTestDatabaseWithConfig(tb)
-	return db
+	// Clone the template database as the new random database name.
+	if _, err := i.conn.Exec(ctx, q); err != nil {
+		return "", fmt.Errorf("failed to clone template database: %w", err)
+	}
+	return name, nil
 }
 
 // dbMigrate runs the migrations. u is the connection URL string (e.g.
@@ -197,15 +361,27 @@ func dbMigrationsDir() string {
 	return filepath.Join(filepath.Dir(filename), "../../migrations")
 }
 
-func postgresRepo(tb testing.TB) (string, string) {
-	postgresImageRef := os.Getenv("CI_POSTGRES_IMAGE")
-	if postgresImageRef == "" {
-		postgresImageRef = "postgres:13-alpine"
+// postgresRepo returns the postgres container image name based on an
+// environment variable, or the default value if the environment variable is
+// unset.
+func postgresRepo() (string, string, error) {
+	ref := os.Getenv("CI_POSTGRES_IMAGE")
+	if ref == "" {
+		ref = defaultPostgresImageRef
 	}
 
-	parts := strings.SplitN(postgresImageRef, ":", 2)
+	parts := strings.SplitN(ref, ":", 2)
 	if len(parts) != 2 {
-		tb.Fatalf("invalid postgres ref %v", postgresImageRef)
+		return "", "", fmt.Errorf("invalid reference for database container: %q", ref)
+	}
+	return parts[0], parts[1], nil
+}
+
+// randomDatabaseName returns a random database name.
+func randomDatabaseName() (string, error) {
+	b := make([]byte, 4)
+	if _, err := rand.Read(b); err != nil {
+		return "", err
 	}
-	return parts[0], parts[1]
+	return hex.EncodeToString(b), nil
 }
diff --git a/internal/integration/helpers.go b/internal/integration/helpers.go
index 56a37c0..6ccfdb8 100644
--- a/internal/integration/helpers.go
+++ b/internal/integration/helpers.go
@@ -95,7 +95,14 @@ func NewTestServer(tb testing.TB) (*serverenv.ServerEnv, *Client) {
 			tb.Fatalf("unable to connect to database: %v", err)
 		}
 	} else {
-		db = database.NewTestDatabase(tb)
+		testDatabaseInstance := database.MustTestInstance()
+		tb.Cleanup(func() {
+			if err := testDatabaseInstance.Close(); err != nil {
+				tb.Fatal(err)
+			}
+		})
+
+		db, _ = testDatabaseInstance.NewDatabase(tb)
 	}
 
 	aap, err := authorizedapp.NewDatabaseProvider(ctx, db, &authorizedapp.Config{CacheDuration: time.Nanosecond})
diff --git a/internal/metrics/cleanup/measurements.go b/internal/metrics/cleanup/measurements.go
index 0385d25..cdac30d 100644
--- a/internal/metrics/cleanup/measurements.go
+++ b/internal/metrics/cleanup/measurements.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package cleanup contains OpenCensus metrics and views for cleanup operations
 package cleanup
 
diff --git a/internal/metrics/cleanup/views.go b/internal/metrics/cleanup/views.go
index 99932c5..134d5f8 100644
--- a/internal/metrics/cleanup/views.go
+++ b/internal/metrics/cleanup/views.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package cleanup
 
 import (
diff --git a/internal/metrics/export/measurements.go b/internal/metrics/export/measurements.go
index b70dbde..94e9597 100644
--- a/internal/metrics/export/measurements.go
+++ b/internal/metrics/export/measurements.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package export contains OpenCensus metrics and views for export operations
 package export
 
diff --git a/internal/metrics/export/views.go b/internal/metrics/export/views.go
index ac3b022..9741d41 100644
--- a/internal/metrics/export/views.go
+++ b/internal/metrics/export/views.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package export
 
 import (
diff --git a/internal/metrics/federationin/measurements.go b/internal/metrics/federationin/measurements.go
index 82369ee..bc6f991 100644
--- a/internal/metrics/federationin/measurements.go
+++ b/internal/metrics/federationin/measurements.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package federationin contains OpenCensus metrics and views for federationin operations
 package federationin
 
diff --git a/internal/metrics/federationin/views.go b/internal/metrics/federationin/views.go
index 9c338a7..a26fc23 100644
--- a/internal/metrics/federationin/views.go
+++ b/internal/metrics/federationin/views.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package federationin
 
 import (
diff --git a/internal/metrics/federationout/measurements.go b/internal/metrics/federationout/measurements.go
index e0fcff2..b4b0e23 100644
--- a/internal/metrics/federationout/measurements.go
+++ b/internal/metrics/federationout/measurements.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package federationout contains OpenCensus metrics and views for federationout operations
 package federationout
 
diff --git a/internal/metrics/federationout/views.go b/internal/metrics/federationout/views.go
index 0b831a9..4c5f694 100644
--- a/internal/metrics/federationout/views.go
+++ b/internal/metrics/federationout/views.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package federationout
 
 import (
diff --git a/internal/metrics/meta.go b/internal/metrics/meta.go
index cd974c3..7b219bc 100644
--- a/internal/metrics/meta.go
+++ b/internal/metrics/meta.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package metrics
 
 const MetricRoot = "en-server/"
diff --git a/internal/metrics/metricsware/cleanup.go b/internal/metrics/metricsware/cleanup.go
index ab0a7bf..d2a013e 100644
--- a/internal/metrics/metricsware/cleanup.go
+++ b/internal/metrics/metricsware/cleanup.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package metricsware
 
 import (
diff --git a/internal/metrics/metricsware/export.go b/internal/metrics/metricsware/export.go
index 73f5c4c..cb3b30f 100644
--- a/internal/metrics/metricsware/export.go
+++ b/internal/metrics/metricsware/export.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package metricsware
 
 import (
diff --git a/internal/metrics/metricsware/federationin.go b/internal/metrics/metricsware/federationin.go
index ab257f8..9bf990c 100644
--- a/internal/metrics/metricsware/federationin.go
+++ b/internal/metrics/metricsware/federationin.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package metricsware
 
 import (
diff --git a/internal/metrics/metricsware/federationout.go b/internal/metrics/metricsware/federationout.go
index 4dcf711..ed034ed 100644
--- a/internal/metrics/metricsware/federationout.go
+++ b/internal/metrics/metricsware/federationout.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package metricsware
 
 import (
diff --git a/internal/metrics/metricsware/middleware.go b/internal/metrics/metricsware/middleware.go
index e46f12b..0e2c42a 100644
--- a/internal/metrics/metricsware/middleware.go
+++ b/internal/metrics/metricsware/middleware.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package metricsware provides a middleware for recording metrics of different kinds
 package metricsware
 
diff --git a/internal/metrics/metricsware/publish.go b/internal/metrics/metricsware/publish.go
index 3f1e26a..269dd73 100644
--- a/internal/metrics/metricsware/publish.go
+++ b/internal/metrics/metricsware/publish.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package metricsware
 
 import (
diff --git a/internal/metrics/metricsware/rotate.go b/internal/metrics/metricsware/rotate.go
index 669ef8c..b993606 100644
--- a/internal/metrics/metricsware/rotate.go
+++ b/internal/metrics/metricsware/rotate.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package metricsware
 
 import (
diff --git a/internal/metrics/publish/measurements.go b/internal/metrics/publish/measurements.go
index b955b61..4b6ebb4 100644
--- a/internal/metrics/publish/measurements.go
+++ b/internal/metrics/publish/measurements.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package publish contains OpenCensus metrics and views for publish operations
 package publish
 
diff --git a/internal/metrics/publish/views.go b/internal/metrics/publish/views.go
index 66f3b9a..68ace31 100644
--- a/internal/metrics/publish/views.go
+++ b/internal/metrics/publish/views.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package publish
 
 import (
diff --git a/internal/metrics/rotate/measurements.go b/internal/metrics/rotate/measurements.go
index 8fe4cc7..64d4398 100644
--- a/internal/metrics/rotate/measurements.go
+++ b/internal/metrics/rotate/measurements.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package rotate contains OpenCensus metrics and views for rotate operations
 package rotate
 
diff --git a/internal/metrics/rotate/views.go b/internal/metrics/rotate/views.go
index 2972eea..1a1682f 100644
--- a/internal/metrics/rotate/views.go
+++ b/internal/metrics/rotate/views.go
@@ -1,3 +1,17 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package rotate
 
 import (
diff --git a/scripts/presubmit.sh b/scripts/presubmit.sh
index f34f837..5080542 100755
--- a/scripts/presubmit.sh
+++ b/scripts/presubmit.sh
@@ -17,28 +17,39 @@
 set -eEuo pipefail
 
 ROOT="$(cd "$(dirname "$0")/.." &>/dev/null; pwd -P)"
-SOURCE_DIRS="cmd internal tools"
-
 
 # Note: other environment variables may be set by the test infrastructure. See:
 # https://github.com/GoogleCloudPlatform/oss-test-infra/tree/master/prow/prowjobs/google/exposure-notifications-server.
 echo "🌳 Set up environment variables"
 export GOMAXPROCS=7
-# TODO(sethvargo): configure more
 
 
-echo "🚒 Verify Protobufs are up to date"
-${ROOT}/scripts/dev protoc
+# Authenticate and pull private Docker repos.
+if [ -n "${CI:-}" ]; then
+  gcloud --quiet auth configure-docker us-docker.pkg.dev
+fi
+if [ -n "${CI_POSTGRES_IMAGE:-}" ]; then
+  docker pull --quiet "${CI_POSTGRES_IMAGE}"
+fi
+if [ -n "${CI_REDIS_IMAGE:-}" ]; then
+  docker pull --quiet "${CI_REDIS_IMAGE}"
+fi
 
 
-echo "📚 Fetch dependencies"
-OUT="$(go get -t ./... 2>&1)" || {
+OUT="$(go get -t -tags=performance,e2e ./... 2>&1)" || {
   echo "✋ Error fetching dependencies"
   echo "\n\n${OUT}\n\n"
   exit 1
 }
 
 
+OUT="$(go test -i -tags=performance,e2e ./... 2>&1)" || {
+  echo "✋ Error fetching test dependencies"
+  echo "\n\n${OUT}\n\n"
+  exit 1
+}
+
+
 echo "🧹 Verify formatting"
 make fmtcheck || {
   echo "✋ Found formatting errors."
@@ -59,9 +70,13 @@ make spellcheck || {
   exit 1
 }
 
+echo "👩‍⚖️ Verify copyrights"
+make copyrightcheck || {
+  echo "✋ Missing copyrights."
+  exit 1
+}
 
 echo "🔨 Building"
-go build ./...
 # Compiling *_test.go files with performance tag
 go test -c -tags=performance ./internal/performance/...
 


Test patch:
diff --git a/internal/authorizedapp/database/authorized_app_test.go b/internal/authorizedapp/database/authorized_app_test.go
index 18759ed..8628cae 100644
--- a/internal/authorizedapp/database/authorized_app_test.go
+++ b/internal/authorizedapp/database/authorized_app_test.go
@@ -20,7 +20,6 @@ import (
 	"testing"
 
 	"github.com/google/exposure-notifications-server/internal/authorizedapp/model"
-	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 )
@@ -28,8 +27,8 @@ import (
 func TestAuthorizedAppLifecycle(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	aadb := New(testDB)
 
 	source := &model.AuthorizedApp{
@@ -128,7 +127,7 @@ func TestGetAuthorizedApp(t *testing.T) {
 		t.Run(c.name, func(t *testing.T) {
 			t.Parallel()
 
-			testDB := database.NewTestDatabase(t)
+			testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 			// Acquire a connection
 			conn, err := testDB.Pool.Acquire(ctx)
diff --git a/internal/authorizedapp/database/database_test.go b/internal/authorizedapp/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/authorizedapp/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/cleanup/cleanup_test.go b/internal/cleanup/cleanup_test.go
index 5169f87..15d9c58 100644
--- a/internal/cleanup/cleanup_test.go
+++ b/internal/cleanup/cleanup_test.go
@@ -25,11 +25,19 @@ import (
 	"github.com/google/exposure-notifications-server/internal/storage"
 )
 
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
+
 func TestNewExposureHandler(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	testCases := []struct {
 		name string
@@ -50,6 +58,7 @@ func TestNewExposureHandler(t *testing.T) {
 
 	for _, tc := range testCases {
 		tc := tc
+
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
@@ -76,7 +85,7 @@ func TestNewExportHandler(t *testing.T) {
 	t.Parallel()
 
 	ctx := context.Background()
-	testDB := database.NewTestDatabase(t)
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	noopBlobstore, _ := storage.NewNoop(ctx)
 
 	testCases := []struct {
@@ -103,6 +112,7 @@ func TestNewExportHandler(t *testing.T) {
 
 	for _, tc := range testCases {
 		tc := tc
+
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
@@ -126,8 +136,11 @@ func TestNewExportHandler(t *testing.T) {
 }
 
 func TestCutoffDate(t *testing.T) {
+	t.Parallel()
+
 	now := time.Now()
-	for _, test := range []struct {
+
+	cases := []struct {
 		name     string
 		d        time.Duration
 		wantDur  time.Duration // if zero, then expect an error
@@ -137,23 +150,29 @@ func TestCutoffDate(t *testing.T) {
 		{"negative", -10 * time.Minute, 0, false},                                // negative
 		{"long_enough", 241 * time.Hour, (10*24 + 1) * time.Hour, false},         // 10 days, 1 hour: OK
 		{"too_short_with_override", 216 * time.Hour, (9 * 24) * time.Hour, true}, // too short, but override backstop.
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			got, err := cutoffDate(context.Background(), test.d, test.override)
-			if test.wantDur == 0 {
+	}
+
+	for _, tc := range cases {
+		tc := tc
+
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
+			got, err := cutoffDate(context.Background(), tc.d, tc.override)
+			if tc.wantDur == 0 {
 				if err == nil {
-					t.Errorf("%q: got no error, wanted one", test.d)
+					t.Errorf("%q: got no error, wanted one", tc.d)
 				}
 			} else if err != nil {
-				t.Errorf("%q: got error %v", test.d, err)
+				t.Errorf("%q: got error %v", tc.d, err)
 			} else {
-				want := now.Add(-test.wantDur)
+				want := now.Add(-tc.wantDur)
 				diff := got.Sub(want)
 				if diff < 0 {
 					diff = -diff
 				}
 				if diff > time.Second {
-					t.Errorf("%q: got %s, want %s", test.d, got, want)
+					t.Errorf("%q: got %s, want %s", tc.d, got, want)
 				}
 			}
 		})
diff --git a/internal/database/database_test.go b/internal/database/database_test.go
new file mode 100644
index 0000000..18acc77
--- /dev/null
+++ b/internal/database/database_test.go
@@ -0,0 +1,27 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+)
+
+var testDatabaseInstance *TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/database/lock_test.go b/internal/database/lock_test.go
index 68bb4be..95b91cd 100644
--- a/internal/database/lock_test.go
+++ b/internal/database/lock_test.go
@@ -24,8 +24,8 @@ import (
 func TestLock(t *testing.T) {
 	t.Parallel()
 
-	testDB := NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	const (
 		id1 = "test1"
@@ -87,8 +87,8 @@ func TestLock(t *testing.T) {
 func TestMultiLock(t *testing.T) {
 	t.Parallel()
 
-	testDB := NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	neededLocks := []string{"traveler", "US", "CA", "MX"}
 
diff --git a/internal/export/database/database_test.go b/internal/export/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/export/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/export/database/export_test.go b/internal/export/database/export_test.go
index cf0a9c7..1a24e55 100644
--- a/internal/export/database/export_test.go
+++ b/internal/export/database/export_test.go
@@ -28,19 +28,14 @@ import (
 	publishmodel "github.com/google/exposure-notifications-server/internal/publish/model"
 
 	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-cmp/cmp/cmpopts"
 	pgx "github.com/jackc/pgx/v4"
 )
 
-var (
-	approxTime = cmp.Options{cmpopts.EquateApproxTime(time.Millisecond * 250)}
-)
-
 func TestAddRetrieveUpdateSignatureInfo(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	want := &model.SignatureInfo{
 		SigningKey:        "/kms/project/key/1",
@@ -73,7 +68,7 @@ func TestAddRetrieveUpdateSignatureInfo(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if diff := cmp.Diff(want, got, approxTime); diff != "" {
+	if diff := cmp.Diff(want, got, database.ApproxTime); diff != "" {
 		t.Fatalf("mismatch (-want, +got):\n%s", diff)
 	}
 }
@@ -81,8 +76,8 @@ func TestAddRetrieveUpdateSignatureInfo(t *testing.T) {
 func TestLookupSignatureInfos(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	testTime := time.Now().UTC()
 	create := []*model.SignatureInfo{
@@ -123,7 +118,7 @@ func TestLookupSignatureInfos(t *testing.T) {
 		create[1],
 	}
 
-	if diff := cmp.Diff(want, got, approxTime); diff != "" {
+	if diff := cmp.Diff(want, got, database.ApproxTime); diff != "" {
 		t.Errorf("mismatch (-want, +got):\n%v", diff)
 	}
 }
@@ -131,8 +126,8 @@ func TestLookupSignatureInfos(t *testing.T) {
 func TestAddGetUpdateExportConfig(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportDB := New(testDB)
 
 	fromTime := time.Now()
@@ -158,7 +153,7 @@ func TestAddGetUpdateExportConfig(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if diff := cmp.Diff(want, got, approxTime); diff != "" {
+	if diff := cmp.Diff(want, got, database.ApproxTime); diff != "" {
 		t.Errorf("mismatch (-want, +got):\n%s", diff)
 	}
 
@@ -176,7 +171,7 @@ func TestAddGetUpdateExportConfig(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if diff := cmp.Diff(want, got, approxTime); diff != "" {
+	if diff := cmp.Diff(want, got, database.ApproxTime); diff != "" {
 		t.Errorf("mismatch (-want, +got):\n%s", diff)
 	}
 }
@@ -184,8 +179,8 @@ func TestAddGetUpdateExportConfig(t *testing.T) {
 func TestIterateExportConfigs(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	now := time.Now().Truncate(time.Microsecond)
 	ecs := []*model.ExportConfig{
@@ -239,8 +234,8 @@ func TestIterateExportConfigs(t *testing.T) {
 func TestBatches(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	now := time.Now().Truncate(time.Microsecond)
 	config := &model.ExportConfig{
@@ -350,9 +345,9 @@ func TestBatches(t *testing.T) {
 func TestFinalizeBatch(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	exportDB := New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	exportDB := New(testDB)
 	now := time.Now().Truncate(time.Microsecond)
 
 	// Add a config.
@@ -454,8 +449,8 @@ func TestFinalizeBatch(t *testing.T) {
 func TestTravelerKeys(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	now := time.Now()
 
 	// Add a config.
@@ -581,7 +576,7 @@ func TestExcludeRegions(t *testing.T) {
 		CreatedAt:   startTimestamp,
 	}
 
-	tests := []struct {
+	cases := []struct {
 		name        string
 		users       []*publishmodel.Exposure
 		inclRegions []string
@@ -595,13 +590,14 @@ func TestExcludeRegions(t *testing.T) {
 		{"1main,2ext,notravel,2regions", []*publishmodel.Exposure{mainUser, extUser, extTravUser}, bothRegions, nil, true, []string{"aaa", "ccc"}},
 	}
 
-	for _, test := range tests {
-		test := test
-		t.Run(test.name, func(t *testing.T) {
+	for _, tc := range cases {
+		tc := tc
+
+		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
-			testDB := database.NewTestDatabase(t)
 			ctx := context.Background()
+			testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 			// Add a config.
 			ec := &model.ExportConfig{
@@ -657,11 +653,11 @@ func TestExcludeRegions(t *testing.T) {
 			// Lookup the keys; they must be only the key created_at the startTimestamp
 			// (because start is inclusive, end is exclusive).
 			criteria := publishdb.IterateExposuresCriteria{
-				IncludeRegions:   test.inclRegions,
+				IncludeRegions:   tc.inclRegions,
 				SinceTimestamp:   leased.StartTimestamp,
 				UntilTimestamp:   leased.EndTimestamp,
-				ExcludeRegions:   test.exclRegions,
-				OnlyNonTravelers: test.nonTraveler,
+				ExcludeRegions:   tc.exclRegions,
+				OnlyNonTravelers: tc.nonTraveler,
 			}
 
 			var got []*publishmodel.Exposure
@@ -678,8 +674,8 @@ func TestExcludeRegions(t *testing.T) {
 				keys = append(keys, string(exp.ExposureKey))
 			}
 			sort.Strings(keys)
-			if !reflect.DeepEqual(test.want, keys) {
-				t.Fatalf("%v got = %v, want %v", test.name, keys, test.want)
+			if !reflect.DeepEqual(tc.want, keys) {
+				t.Fatalf("%v got = %v, want %v", tc.name, keys, tc.want)
 			}
 		})
 	}
@@ -689,8 +685,8 @@ func TestExcludeRegions(t *testing.T) {
 func TestKeysInBatch(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	now := time.Now()
 
 	// Add a config.
@@ -788,9 +784,9 @@ func TestKeysInBatch(t *testing.T) {
 func TestAddExportFileSkipsDuplicates(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	exportDB := New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	exportDB := New(testDB)
 
 	// Add foreign key records.
 	ec := &model.ExportConfig{Period: time.Hour}
diff --git a/internal/export/export_test.go b/internal/export/export_test.go
new file mode 100644
index 0000000..cd40324
--- /dev/null
+++ b/internal/export/export_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package export
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/export/worker_test.go b/internal/export/worker_test.go
index bb630f8..9c51494 100644
--- a/internal/export/worker_test.go
+++ b/internal/export/worker_test.go
@@ -19,7 +19,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/export/model"
 	publishdb "github.com/google/exposure-notifications-server/internal/publish/database"
 	publishmodel "github.com/google/exposure-notifications-server/internal/publish/model"
@@ -31,6 +30,8 @@ import (
 )
 
 func TestRandomInt(t *testing.T) {
+	t.Parallel()
+
 	expected := make(map[int]bool)
 	for i := verifyapi.MinTransmissionRisk; i <= verifyapi.MaxTransmissionRisk; i++ {
 		expected[i] = true
@@ -54,6 +55,8 @@ func TestRandomInt(t *testing.T) {
 }
 
 func TestDoNotPadZeroLength(t *testing.T) {
+	t.Parallel()
+
 	exposures := make([]*publishmodel.Exposure, 0)
 	exposures, generated, err := ensureMinNumExposures(exposures, "US", 1000, 100, time.Now())
 	if err != nil {
@@ -68,6 +71,8 @@ func TestDoNotPadZeroLength(t *testing.T) {
 }
 
 func TestEnsureMinExposures(t *testing.T) {
+	t.Parallel()
+
 	// Insert a few exposures - that will be used to base the interval information off of.
 	exposures := []*publishmodel.Exposure{
 		{
@@ -143,9 +148,9 @@ func getKey(t *testing.T) []byte {
 func TestBatchExposures(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	testPublishDB := publishdb.New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	testPublishDB := publishdb.New(testDB)
 
 	config := Config{
 		MinRecords:         1,
@@ -363,9 +368,9 @@ func TestBatchExposures(t *testing.T) {
 func TestVariableBatchMaxSize(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	testPublishDB := publishdb.New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	testPublishDB := publishdb.New(testDB)
 
 	// Using a 1 hour truncate window
 	baseTime := time.Date(2020, 10, 28, 1, 0, 0, 0, time.UTC).Truncate(time.Hour)
diff --git a/internal/exportimport/database/database_test.go b/internal/exportimport/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/exportimport/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/exportimport/database/exportimport_test.go b/internal/exportimport/database/exportimport_test.go
index b1d07a3..4a3a75b 100644
--- a/internal/exportimport/database/exportimport_test.go
+++ b/internal/exportimport/database/exportimport_test.go
@@ -30,15 +30,11 @@ import (
 	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
-var (
-	approxTime = cmp.Options{cmpopts.EquateApproxTime(time.Second)}
-)
-
 func TestAddGetUpdateExportConfig(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := New(testDB)
 
 	fromTime := time.Now().UTC().Add(-1 * time.Second)
@@ -62,7 +58,7 @@ func TestAddGetUpdateExportConfig(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if diff := cmp.Diff(want, got, approxTime); diff != "" {
+	if diff := cmp.Diff(want, got, database.ApproxTime); diff != "" {
 		t.Errorf("mismatch (-want, +got):\n%s", diff)
 	}
 }
@@ -70,8 +66,8 @@ func TestAddGetUpdateExportConfig(t *testing.T) {
 func TestAddImportFiles(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := New(testDB)
 
 	now := time.Now().UTC()
@@ -122,8 +118,8 @@ func TestAddImportFiles(t *testing.T) {
 func TestLeaseAndCompleteImportFile(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := New(testDB)
 
 	lockDuration, retryRate := 2*time.Second, time.Hour
@@ -187,8 +183,8 @@ func TestLeaseAndCompleteImportFile(t *testing.T) {
 func TestImportFilePublicKey(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := New(testDB)
 
 	now := time.Now().UTC()
@@ -236,7 +232,7 @@ func TestImportFilePublicKey(t *testing.T) {
 		t.Fatalf("error reading public keys: %v", err)
 	}
 
-	if diff := cmp.Diff(&want, got[0], approxTime); diff != "" {
+	if diff := cmp.Diff(&want, got[0], database.ApproxTime); diff != "" {
 		t.Errorf("mismatch (-want, +got):\n%s", diff)
 	}
 
@@ -256,8 +252,8 @@ func TestImportFilePublicKey(t *testing.T) {
 func TestRetryToClose(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := New(testDB)
 
 	now := time.Now().UTC()
diff --git a/internal/exportimport/exportimport_test.go b/internal/exportimport/exportimport_test.go
new file mode 100644
index 0000000..7826274
--- /dev/null
+++ b/internal/exportimport/exportimport_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package exportimport
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/exportimport/importer_test.go b/internal/exportimport/importer_test.go
index 7727567..0dcedc2 100644
--- a/internal/exportimport/importer_test.go
+++ b/internal/exportimport/importer_test.go
@@ -21,7 +21,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/database"
 	exportimportdb "github.com/google/exposure-notifications-server/internal/exportimport/database"
 	"github.com/google/exposure-notifications-server/internal/exportimport/model"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
@@ -30,16 +29,14 @@ import (
 func TestImportingRetries(t *testing.T) {
 	t.Parallel()
 
-	// Create a test http server.
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-	}))
-	defer ts.Close()
-
-	// Setup the database.
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := exportimportdb.New(testDB)
 
+	// Create a test http server.
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+	defer ts.Close()
+
 	now := time.Now().UTC()
 	eiConfig := model.ExportImport{
 		IndexFile:  ts.URL + "/index.txt",
diff --git a/internal/exportimport/scheduler_test.go b/internal/exportimport/scheduler_test.go
index 9371b97..fcd8f0b 100644
--- a/internal/exportimport/scheduler_test.go
+++ b/internal/exportimport/scheduler_test.go
@@ -21,7 +21,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/database"
 	exportimportdb "github.com/google/exposure-notifications-server/internal/exportimport/database"
 	"github.com/google/exposure-notifications-server/internal/exportimport/model"
 	"github.com/google/go-cmp/cmp"
@@ -31,8 +30,8 @@ import (
 func TestSyncFileFromIndexErrorsInExportRoot(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := exportimportdb.New(testDB)
 
 	fromTime := time.Now().UTC().Add(-1 * time.Second)
@@ -60,8 +59,8 @@ func TestSyncFileFromIndexErrorsInExportRoot(t *testing.T) {
 func TestSyncFilenameShapes(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := exportimportdb.New(testDB)
 
 	fromTime := time.Now().UTC().Add(-1 * time.Second)
@@ -194,6 +193,8 @@ func TestSyncFilenameShapes(t *testing.T) {
 	}
 
 	for _, tc := range cases {
+		tc := tc
+
 		t.Run(tc.name, func(t *testing.T) {
 			config := tc.config
 			if err := exportImportDB.AddConfig(ctx, config); err != nil {
@@ -236,8 +237,8 @@ func TestSyncFilenameShapes(t *testing.T) {
 func TestSyncFileFromIndex(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	exportImportDB := exportimportdb.New(testDB)
 
 	fromTime := time.Now().UTC().Add(-1 * time.Second)
@@ -257,8 +258,9 @@ func TestSyncFileFromIndex(t *testing.T) {
 	}
 
 	for _, tc := range cases {
-		t.Run(tc.name, func(t *testing.T) {
+		tc := tc
 
+		t.Run(tc.name, func(t *testing.T) {
 			config := &model.ExportImport{
 				IndexFile:  "https://mysever/exports/index.txt",
 				ExportRoot: tc.exportRoot,
@@ -268,7 +270,6 @@ func TestSyncFileFromIndex(t *testing.T) {
 			}
 			if err := exportImportDB.AddConfig(ctx, config); err != nil {
 				t.Fatal(err)
-
 			}
 
 			// test data ensures that URL parsing stripps extra slashes.
diff --git a/internal/federationin/database/database_test.go b/internal/federationin/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/federationin/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/federationin/database/federationin_test.go b/internal/federationin/database/federationin_test.go
index 946e017..25f097f 100644
--- a/internal/federationin/database/federationin_test.go
+++ b/internal/federationin/database/federationin_test.go
@@ -32,9 +32,9 @@ import (
 func TestFederationIn(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	db := New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	db := New(testDB)
 
 	want := &model.FederationInQuery{
 		QueryID:             "qid",
diff --git a/internal/federationout/database/database_test.go b/internal/federationout/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/federationout/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/federationout/database/federationout_test.go b/internal/federationout/database/federationout_test.go
index 6c16817..1e42e98 100644
--- a/internal/federationout/database/federationout_test.go
+++ b/internal/federationout/database/federationout_test.go
@@ -29,8 +29,8 @@ import (
 func TestFederationOutAuthorization(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	want := &model.FederationOutAuthorization{
 		Issuer:         "iss",
diff --git a/internal/generate/generate_test.go b/internal/generate/generate_test.go
index 49b582e..2bde8c4 100644
--- a/internal/generate/generate_test.go
+++ b/internal/generate/generate_test.go
@@ -32,11 +32,19 @@ import (
 	"github.com/sethvargo/go-envconfig"
 )
 
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
+
 func testHandler(tb testing.TB) (*Config, *serverenv.ServerEnv) {
 	tb.Helper()
 
 	ctx := context.Background()
-	_, dbConfig := database.NewTestDatabaseWithConfig(tb)
+	_, dbConfig := testDatabaseInstance.NewDatabase(tb)
 
 	config := &Config{
 		Database: *dbConfig,
diff --git a/internal/keyrotation/keyrotation_test.go b/internal/keyrotation/keyrotation_test.go
new file mode 100644
index 0000000..b6b56a6
--- /dev/null
+++ b/internal/keyrotation/keyrotation_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package keyrotation
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/keyrotation/rotate_test.go b/internal/keyrotation/rotate_test.go
index d9e0ecd..9a323dc 100644
--- a/internal/keyrotation/rotate_test.go
+++ b/internal/keyrotation/rotate_test.go
@@ -151,10 +151,12 @@ func TestRotateKeys(t *testing.T) {
 
 	for _, tc := range testCases {
 		tc := tc
+
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
-			testDB := database.NewTestDatabase(t)
+			testDB, _ := testDatabaseInstance.NewDatabase(t)
+
 			env := serverenv.New(ctx, serverenv.WithKeyManager(kms), serverenv.WithDatabase(testDB))
 			server, err := NewServer(config, env)
 			if err != nil {
diff --git a/internal/keyrotation/server_test.go b/internal/keyrotation/server_test.go
index f243771..321c816 100644
--- a/internal/keyrotation/server_test.go
+++ b/internal/keyrotation/server_test.go
@@ -19,7 +19,6 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/revision"
 	"github.com/google/exposure-notifications-server/internal/serverenv"
 	"github.com/google/exposure-notifications-server/pkg/keys"
@@ -29,7 +28,7 @@ func TestNewRotationHandler(t *testing.T) {
 	t.Parallel()
 
 	ctx := context.Background()
-	testDB := database.NewTestDatabase(t)
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	kms := keys.TestKeyManager(t)
 
diff --git a/internal/mirror/database/database_test.go b/internal/mirror/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/mirror/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/mirror/database/mirror_test.go b/internal/mirror/database/mirror_test.go
index e9fde48..618ed5f 100644
--- a/internal/mirror/database/mirror_test.go
+++ b/internal/mirror/database/mirror_test.go
@@ -18,7 +18,6 @@ import (
 	"context"
 	"testing"
 
-	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/mirror/model"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
@@ -27,8 +26,8 @@ import (
 func TestMirror_Lifecycle(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	mirrorDB := New(testDB)
 
 	want := []*model.Mirror{
@@ -106,8 +105,8 @@ func TestMirror_Lifecycle(t *testing.T) {
 func TestFileLifecycle(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	mirrorDB := New(testDB)
 
 	mirror := model.Mirror{
@@ -183,8 +182,8 @@ func TestFileLifecycle(t *testing.T) {
 func TestFileLifecycleWithRewrite(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	mirrorDB := New(testDB)
 
 	mirror := model.Mirror{
diff --git a/internal/publish/database/database_test.go b/internal/publish/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/publish/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/publish/database/exposure_test.go b/internal/publish/database/exposure_test.go
index c01b31d..363666a 100644
--- a/internal/publish/database/exposure_test.go
+++ b/internal/publish/database/exposure_test.go
@@ -37,16 +37,15 @@ import (
 )
 
 var (
-	approxTime               = cmp.Options{cmpopts.EquateApproxTime(time.Second)}
 	ignoreUnexportedExposure = cmpopts.IgnoreUnexported(model.Exposure{})
 )
 
 func TestReadExposures(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	testPublishDB := New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	testPublishDB := New(testDB)
 
 	// Insert some Exposures.
 	createdAt := time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC).Truncate(time.Microsecond)
@@ -104,7 +103,7 @@ func TestReadExposures(t *testing.T) {
 		return out
 	})
 
-	if diff := cmp.Diff(exposures, got, approxTime, sorter, ignoreUnexportedExposure); diff != "" {
+	if diff := cmp.Diff(exposures, got, database.ApproxTime, sorter, ignoreUnexportedExposure); diff != "" {
 		t.Errorf("ReadExposures mismatch (-want, +got):\n%s", diff)
 	}
 
@@ -146,9 +145,9 @@ func TestReadExposures(t *testing.T) {
 func TestExposures(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	testPublishDB := New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	testPublishDB := New(testDB)
 
 	// Insert some Exposures.
 	batchTime := time.Date(2020, 5, 1, 0, 0, 0, 0, time.UTC).Truncate(time.Microsecond)
@@ -327,7 +326,7 @@ func TestReviseExposures(t *testing.T) {
 	t.Parallel()
 
 	ctx := context.Background()
-	testDB := database.NewTestDatabase(t)
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	pubDB := New(testDB)
 
 	// Attempt to revise without a revision token - this should fail.
@@ -723,9 +722,9 @@ func TestReviseExposures(t *testing.T) {
 func TestIterateExposuresCursor(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	testPublishDB := New(testDB)
 	ctx, cancel := context.WithCancel(context.Background())
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	testPublishDB := New(testDB)
 
 	// Insert some Exposures.
 	exposures := []*model.Exposure{
diff --git a/internal/publish/database/performance_test_utils_test.go b/internal/publish/database/performance_test_utils_test.go
index a9d0423..3f21679 100644
--- a/internal/publish/database/performance_test_utils_test.go
+++ b/internal/publish/database/performance_test_utils_test.go
@@ -17,11 +17,12 @@ import (
 	"context"
 	"testing"
 
-	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/publish/model"
 )
 
 func TestBulkInsertExposures(t *testing.T) {
+	t.Parallel()
+
 	testCases := []struct {
 		name      string
 		exposures []*model.Exposure
@@ -52,14 +53,17 @@ func TestBulkInsertExposures(t *testing.T) {
 			},
 			want: 3},
 	}
-	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	testPublishDB := New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	testPublishDB := New(testDB)
 
 	for _, tc := range testCases {
+		tc := tc
+
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
 			count, err := testPublishDB.BulkInsertExposures(ctx, tc.exposures)
 			if err != nil {
 				t.Fatal(err)
diff --git a/internal/publish/publish_test.go b/internal/publish/publish_test.go
index d123fe0..9ce0d09 100644
--- a/internal/publish/publish_test.go
+++ b/internal/publish/publish_test.go
@@ -32,7 +32,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/authorizedapp"
 	aadb "github.com/google/exposure-notifications-server/internal/authorizedapp/database"
 	aamodel "github.com/google/exposure-notifications-server/internal/authorizedapp/model"
-	coredb "github.com/google/exposure-notifications-server/internal/database"
+	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/pb"
 	pubdb "github.com/google/exposure-notifications-server/internal/publish/database"
 	"github.com/google/exposure-notifications-server/internal/publish/model"
@@ -57,6 +57,14 @@ import (
 	"google.golang.org/protobuf/proto"
 )
 
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
+
 type version int
 
 const (
@@ -420,7 +428,7 @@ func TestPublishWithBypass(t *testing.T) {
 		ctx := context.Background()
 
 		// Database init for all modules that will be used.
-		testDB := coredb.NewTestDatabase(t)
+		testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 		kms := keys.TestKeyManager(t)
 		keyID := keys.TestEncryptionKey(t, kms)
@@ -753,7 +761,7 @@ func TestKeyRevision(t *testing.T) {
 	ctx := context.Background()
 
 	// Database init for all modules that will be used.
-	testDB := coredb.NewTestDatabase(t)
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	kms := keys.TestKeyManager(t)
 	keyID := keys.TestEncryptionKey(t, kms)
@@ -777,7 +785,9 @@ func TestKeyRevision(t *testing.T) {
 	defaultSymptomOnsetDaysAgo := uint(1)
 	// And set up publish handler up front.
 	config := Config{}
-	envconfig.ProcessWith(ctx, &config, envconfig.OsLookuper())
+	if err := envconfig.ProcessWith(ctx, &config, envconfig.OsLookuper()); err != nil {
+		t.Fatal(err)
+	}
 	config.AuthorizedApp.CacheDuration = time.Nanosecond
 	config.CreatedAtTruncateWindow = time.Second
 	config.MaxKeysOnPublish = 20
diff --git a/internal/revision/database/database_test.go b/internal/revision/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/revision/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/revision/database/revision_test.go b/internal/revision/database/revision_test.go
index 2c4bd46..9699096 100644
--- a/internal/revision/database/revision_test.go
+++ b/internal/revision/database/revision_test.go
@@ -25,15 +25,11 @@ import (
 	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
-var (
-	timeCmp = cmpopts.EquateApproxTime(time.Millisecond)
-)
-
 func TestRevisionKey(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	kms := keys.TestKeyManager(t)
 	keyID := keys.TestEncryptionKey(t, kms)
@@ -54,7 +50,7 @@ func TestRevisionKey(t *testing.T) {
 		t.Fatalf("unable to read effective revision keyL: %v", err)
 	}
 
-	if diff := cmp.Diff(want, got, timeCmp); diff != "" {
+	if diff := cmp.Diff(want, got, database.ApproxTime); diff != "" {
 		t.Fatalf("mismatch (-want, +got):\n%s", diff)
 	}
 }
@@ -62,8 +58,8 @@ func TestRevisionKey(t *testing.T) {
 func TestMultipleRevisionKeys(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	kms := keys.TestKeyManager(t)
 	keyID := keys.TestEncryptionKey(t, kms)
@@ -91,7 +87,7 @@ func TestMultipleRevisionKeys(t *testing.T) {
 		if err != nil {
 			t.Fatalf("unable to read effective keys: %v", err)
 		}
-		if diff := cmp.Diff(key2, got, timeCmp); diff != "" {
+		if diff := cmp.Diff(key2, got, database.ApproxTime); diff != "" {
 			t.Fatalf("wrong effective key (-want, +got):\n%s", diff)
 		}
 	}
@@ -108,7 +104,7 @@ func TestMultipleRevisionKeys(t *testing.T) {
 			t.Fatalf("unable to read all keys: %v", err)
 		}
 		want := []*RevisionKey{key1, key2}
-		if diff := cmp.Diff(want, got, sorter, timeCmp); diff != "" {
+		if diff := cmp.Diff(want, got, sorter, database.ApproxTime); diff != "" {
 			t.Fatalf("mismatch (-want, +got):\n%s", diff)
 		}
 		if gotID != key2.KeyID {
@@ -144,7 +140,7 @@ func TestMultipleRevisionKeys(t *testing.T) {
 		if err != nil {
 			t.Fatalf("unable to read effective keys: %v", err)
 		}
-		if diff := cmp.Diff(key1, got, timeCmp); diff != "" {
+		if diff := cmp.Diff(key1, got, database.ApproxTime); diff != "" {
 			t.Fatalf("wrong effective key (-want, +got):\n%s", diff)
 		}
 	}
@@ -156,7 +152,7 @@ func TestMultipleRevisionKeys(t *testing.T) {
 			t.Fatalf("unable to read all keys: %v", err)
 		}
 		want := []*RevisionKey{key1}
-		if diff := cmp.Diff(want, got, sorter, timeCmp); diff != "" {
+		if diff := cmp.Diff(want, got, sorter, database.ApproxTime); diff != "" {
 			t.Fatalf("mismatch (-want, +got):\n%s", diff)
 		}
 		if gotID != key1.KeyID {
diff --git a/internal/revision/revision_test.go b/internal/revision/revision_test.go
index ca88498..7191b40 100644
--- a/internal/revision/revision_test.go
+++ b/internal/revision/revision_test.go
@@ -29,7 +29,17 @@ import (
 	"github.com/google/go-cmp/cmp/cmpopts"
 )
 
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
+
 func TestBuildTokenBuffer(t *testing.T) {
+	t.Parallel()
+
 	cases := []struct {
 		name     string
 		publish  []*model.Exposure
@@ -135,7 +145,11 @@ func TestBuildTokenBuffer(t *testing.T) {
 	}
 
 	for _, tc := range cases {
+		tc := tc
+
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+
 			got := buildTokenBufer(tc.previous, tc.publish)
 			if diff := cmp.Diff(tc.want, got, cmpopts.IgnoreUnexported(pb.RevisionTokenData{}), cmpopts.IgnoreUnexported(pb.RevisableKey{})); diff != "" {
 				t.Fatalf("mismatch (-want, +got):\n%s", diff)
@@ -146,8 +160,9 @@ func TestBuildTokenBuffer(t *testing.T) {
 
 func TestEncryptDecrypt(t *testing.T) {
 	t.Parallel()
-	testDB := database.NewTestDatabase(t)
+
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	kms := keys.TestKeyManager(t)
 	keyID := keys.TestEncryptionKey(t, kms)
@@ -236,8 +251,9 @@ func TestEncryptDecrypt(t *testing.T) {
 
 func TestExpandRevisionToken(t *testing.T) {
 	t.Parallel()
-	testDB := database.NewTestDatabase(t)
+
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	kms := keys.TestKeyManager(t)
 	keyID := keys.TestEncryptionKey(t, kms)
diff --git a/internal/setup/setup_test.go b/internal/setup/setup_test.go
index c1d41f7..15056ce 100644
--- a/internal/setup/setup_test.go
+++ b/internal/setup/setup_test.go
@@ -31,6 +31,14 @@ import (
 	"github.com/sethvargo/go-envconfig"
 )
 
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
+
 var _ setup.AuthorizedAppConfigProvider = (*testConfig)(nil)
 var _ setup.BlobstoreConfigProvider = (*testConfig)(nil)
 var _ setup.DatabaseConfigProvider = (*testConfig)(nil)
@@ -93,7 +101,7 @@ func TestSetupWith(t *testing.T) {
 		t.Parallel()
 
 		ctx := context.Background()
-		_, dbconfig := database.NewTestDatabaseWithConfig(t)
+		_, dbconfig := testDatabaseInstance.NewDatabase(t)
 
 		config := &testConfig{Database: dbconfig}
 		env, err := setup.SetupWith(ctx, config, lookuper)
@@ -107,7 +115,7 @@ func TestSetupWith(t *testing.T) {
 		t.Parallel()
 
 		ctx := context.Background()
-		_, dbconfig := database.NewTestDatabaseWithConfig(t)
+		_, dbconfig := testDatabaseInstance.NewDatabase(t)
 
 		config := &testConfig{Database: dbconfig}
 		env, err := setup.SetupWith(ctx, config, lookuper)
@@ -126,7 +134,7 @@ func TestSetupWith(t *testing.T) {
 		t.Parallel()
 
 		ctx := context.Background()
-		_, dbconfig := database.NewTestDatabaseWithConfig(t)
+		_, dbconfig := testDatabaseInstance.NewDatabase(t)
 
 		config := &testConfig{Database: dbconfig}
 		env, err := setup.SetupWith(ctx, config, lookuper)
@@ -149,7 +157,7 @@ func TestSetupWith(t *testing.T) {
 		t.Parallel()
 
 		ctx := context.Background()
-		_, dbconfig := database.NewTestDatabaseWithConfig(t)
+		_, dbconfig := testDatabaseInstance.NewDatabase(t)
 
 		config := &testConfig{Database: dbconfig}
 		env, err := setup.SetupWith(ctx, config, lookuper)
@@ -172,7 +180,7 @@ func TestSetupWith(t *testing.T) {
 		t.Parallel()
 
 		ctx := context.Background()
-		_, dbconfig := database.NewTestDatabaseWithConfig(t)
+		_, dbconfig := testDatabaseInstance.NewDatabase(t)
 
 		config := &testConfig{Database: dbconfig}
 		env, err := setup.SetupWith(ctx, config, lookuper)
@@ -195,7 +203,7 @@ func TestSetupWith(t *testing.T) {
 		t.Parallel()
 
 		ctx := context.Background()
-		_, dbconfig := database.NewTestDatabaseWithConfig(t)
+		_, dbconfig := testDatabaseInstance.NewDatabase(t)
 
 		config := &testConfig{Database: dbconfig}
 		env, err := setup.SetupWith(ctx, config, lookuper)
@@ -218,7 +226,7 @@ func TestSetupWith(t *testing.T) {
 		t.Parallel()
 
 		ctx := context.Background()
-		_, dbconfig := database.NewTestDatabaseWithConfig(t)
+		_, dbconfig := testDatabaseInstance.NewDatabase(t)
 
 		config := &testConfig{Database: dbconfig}
 		env, err := setup.SetupWith(ctx, config, lookuper)
diff --git a/internal/verification/database/database_test.go b/internal/verification/database/database_test.go
new file mode 100644
index 0000000..469042c
--- /dev/null
+++ b/internal/verification/database/database_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package database
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/internal/verification/database/health_authority_db_test.go b/internal/verification/database/health_authority_db_test.go
index e813e42..982b0e7 100644
--- a/internal/verification/database/health_authority_db_test.go
+++ b/internal/verification/database/health_authority_db_test.go
@@ -20,7 +20,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/verification/model"
 
 	"github.com/google/go-cmp/cmp"
@@ -36,9 +35,9 @@ IBSEEHOdgpAynz0yrHpkWL6vxjNHxRdWcImZxPgL0NVHMdY4TlsL7qaxBQ==
 func TestMissingHealthAuthority(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
-	haDB := New(testDB)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
+	haDB := New(testDB)
 
 	_, err := haDB.GetHealthAuthority(ctx, "does-not-exist")
 	if err == nil {
@@ -52,8 +51,8 @@ func TestMissingHealthAuthority(t *testing.T) {
 func TestAddRetrieveHealthAuthority(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	want := &model.HealthAuthority{
 		Issuer:   "doh.mystate.gov",
@@ -89,8 +88,8 @@ func TestAddRetrieveHealthAuthority(t *testing.T) {
 func TestAddRetrieveHealthAuthorityKeys(t *testing.T) {
 	t.Parallel()
 
-	testDB := database.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 
 	want := &model.HealthAuthority{
 		Issuer:   "doh.mystate.gov",
diff --git a/internal/verification/phaverify_test.go b/internal/verification/phaverify_test.go
index 8ec7cc2..7362d10 100644
--- a/internal/verification/phaverify_test.go
+++ b/internal/verification/phaverify_test.go
@@ -33,7 +33,6 @@ import (
 	"github.com/dgrijalva/jwt-go"
 
 	aamodel "github.com/google/exposure-notifications-server/internal/authorizedapp/model"
-	coredb "github.com/google/exposure-notifications-server/internal/database"
 	"github.com/google/exposure-notifications-server/internal/verification/database"
 	"github.com/google/exposure-notifications-server/internal/verification/model"
 	"github.com/google/go-cmp/cmp"
@@ -91,8 +90,8 @@ func TestVerifyCertificate(t *testing.T) {
 	}
 
 	// Set up database. Create HealthAuthority + HAKey for the test.
-	testDB := coredb.NewTestDatabase(t)
 	ctx := context.Background()
+	testDB, _ := testDatabaseInstance.NewDatabase(t)
 	haDB := database.New(testDB)
 
 	for iteration := 0; iteration < 2; iteration++ {
diff --git a/internal/verification/verification_test.go b/internal/verification/verification_test.go
new file mode 100644
index 0000000..c1fd5aa
--- /dev/null
+++ b/internal/verification/verification_test.go
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package verification
+
+import (
+	"testing"
+
+	"github.com/google/exposure-notifications-server/internal/database"
+)
+
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
diff --git a/pkg/jwks/jwks_test.go b/pkg/jwks/jwks_test.go
index 5c7b148..8cefa7e 100644
--- a/pkg/jwks/jwks_test.go
+++ b/pkg/jwks/jwks_test.go
@@ -29,6 +29,14 @@ import (
 	"github.com/google/exposure-notifications-server/internal/verification/model"
 )
 
+var testDatabaseInstance *database.TestInstance
+
+func TestMain(m *testing.M) {
+	testDatabaseInstance = database.MustTestInstance()
+	defer testDatabaseInstance.MustClose()
+	m.Run()
+}
+
 var key1 = `{"kid":"r2v1","kty":"EC","crv":"P-256","x":"qcMMcLX1Z2afVAzypTMw1g3KN_OcdgvRDwOgpDWiswU","y":"RjK8Hc7pLLO_JADNhwZIxCXjCH95VHuWPoKVaCGkXiA"}`
 var key2 = `{"kid":"r2v2","kty":"EC","crv":"P-256","x":"F2MgtKg_cm-JfcJlrEUJMgXqXq1vHWRPMbBWEjzmN0U","y":"4U6g0nX9mVOGaHL8kYX10gL4Fsj-wNb4V9GMSJ7iLKk"}`
 var enc1 = `MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqcMMcLX1Z2afVAzypTMw1g3KN/Oc
@@ -116,8 +124,10 @@ func TestUpdateHA(t *testing.T) {
 	// "end-to-end" test is runn where the DB is validated.
 	for i, test := range tests {
 		test := test
+
 		t.Run(test.name, func(t *testing.T) {
 			t.Parallel()
+
 			// Start a local server to serve JSON data for the test.
 			ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				fmt.Fprintf(w, test.resp)
@@ -127,7 +137,7 @@ func TestUpdateHA(t *testing.T) {
 
 			// Set up the test.
 			ctx := context.Background()
-			testDB := database.NewTestDatabase(t)
+			testDB, _ := testDatabaseInstance.NewDatabase(t)
 			mgr, err := NewManager(ctx, testDB)
 			if err != nil {
 				t.Fatalf("[%d] unexpected error: %v", i, err)