From c6cb7c85128a6d22298246d5522ec38a09addc59 Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Fri, 30 Jul 2021 07:56:37 -0400 Subject: [PATCH 1/5] chore: Define custom docker image for tests This images is built from the official postgres docker image, but with updates to optimize the test execution: - A config file for postgres to increase the performance of the test server. - An initialization script to create a template database with all migrations run against it. There are new make targets to build and publish this docker image. The image can be pulled with: docker pull hashicorpboundary/postgres:13-alpine To use this image for tests start the container: make -C internal/db/docker database-up --- testing/dbtest/docker/Dockerfile.11-alpine | 6 +++ testing/dbtest/docker/Dockerfile.12-alpine | 6 +++ testing/dbtest/docker/Dockerfile.13-alpine | 6 +++ testing/dbtest/docker/Dockerfile.alpine | 6 +++ testing/dbtest/docker/Makefile | 50 ++++++++++++++++++++++ testing/dbtest/docker/init-db.sh | 44 +++++++++++++++++++ testing/dbtest/docker/postgresql.conf | 35 +++++++++++++++ 7 files changed, 153 insertions(+) create mode 100644 testing/dbtest/docker/Dockerfile.11-alpine create mode 100644 testing/dbtest/docker/Dockerfile.12-alpine create mode 100644 testing/dbtest/docker/Dockerfile.13-alpine create mode 100644 testing/dbtest/docker/Dockerfile.alpine create mode 100644 testing/dbtest/docker/Makefile create mode 100755 testing/dbtest/docker/init-db.sh create mode 100644 testing/dbtest/docker/postgresql.conf diff --git a/testing/dbtest/docker/Dockerfile.11-alpine b/testing/dbtest/docker/Dockerfile.11-alpine new file mode 100644 index 000000000..f569cac04 --- /dev/null +++ b/testing/dbtest/docker/Dockerfile.11-alpine @@ -0,0 +1,6 @@ +FROM postgres:11-alpine + +ADD init-db.sh /docker-entrypoint-initdb.d/00-init-db.sh +ADD postgresql.conf /etc/postgresql/postgresql.conf + +CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] diff --git a/testing/dbtest/docker/Dockerfile.12-alpine b/testing/dbtest/docker/Dockerfile.12-alpine new file mode 100644 index 000000000..3ffdb3d35 --- /dev/null +++ b/testing/dbtest/docker/Dockerfile.12-alpine @@ -0,0 +1,6 @@ +FROM postgres:12-alpine + +ADD init-db.sh /docker-entrypoint-initdb.d/00-init-db.sh +ADD postgresql.conf /etc/postgresql/postgresql.conf + +CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] diff --git a/testing/dbtest/docker/Dockerfile.13-alpine b/testing/dbtest/docker/Dockerfile.13-alpine new file mode 100644 index 000000000..ddab38a16 --- /dev/null +++ b/testing/dbtest/docker/Dockerfile.13-alpine @@ -0,0 +1,6 @@ +FROM postgres:13-alpine + +ADD init-db.sh /docker-entrypoint-initdb.d/00-init-db.sh +ADD postgresql.conf /etc/postgresql/postgresql.conf + +CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] diff --git a/testing/dbtest/docker/Dockerfile.alpine b/testing/dbtest/docker/Dockerfile.alpine new file mode 100644 index 000000000..1655beab9 --- /dev/null +++ b/testing/dbtest/docker/Dockerfile.alpine @@ -0,0 +1,6 @@ +FROM postgres:alpine + +ADD init-db.sh /docker-entrypoint-initdb.d/00-init-db.sh +ADD postgresql.conf /etc/postgresql/postgresql.conf + +CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] diff --git a/testing/dbtest/docker/Makefile b/testing/dbtest/docker/Makefile new file mode 100644 index 000000000..f1b8ec515 --- /dev/null +++ b/testing/dbtest/docker/Makefile @@ -0,0 +1,50 @@ +all: docker-build + +CWD := $(shell pwd) +DOCKER_ARGS ?= -d + +REGISTRY_NAME?=docker.io/hashicorpboundary +IMAGE_NAME=postgres +IMAGE_TAG ?= $(REGISTRY_NAME)/$(IMAGE_NAME):11-alpine +PG_OPTS ?= +TEST_DB_PORT ?= 5432 + +# Generate targets from dockerfiles +dockerfiles = $(wildcard Dockerfile.*) +docker-builds = $(patsubst Dockerfile.%,%-build, $(dockerfiles)) +docker-publishes = $(patsubst Dockerfile.%,%-publish, $(dockerfiles)) + +${docker-builds}: %-build: + docker build -t $(REGISTRY_NAME)/$(IMAGE_NAME):$* -f Dockerfile.$* . + +docker-build: ${docker-builds} + +docker-publish: ${docker-publishes} + +${docker-publishes}: %-publish: + docker push $(REGISTRY_NAME)/$(IMAGE_NAME):$* + +database-up: + @echo "Using image: $(IMAGE_TAG)" + @echo "Additional postgres configuration: $(PG_OPTS)" + @docker run \ + $(DOCKER_ARGS) \ + --name boundary-sql-tests \ + -p $(TEST_DB_PORT):5432 \ + -e POSTGRES_PASSWORD=boundary \ + -e POSTGRES_USER=boundary \ + -e POSTGRES_DB=boundary \ + -e PGDATA=/pgdata \ + --mount type=tmpfs,destination=/pgdata \ + -v "$(CWD)/../../../internal/db/schema/migrations/postgres":/migrations \ + $(IMAGE_TAG) \ + -c 'config_file=/etc/postgresql/postgresql.conf' \ + $(PG_OPTS) 1> /dev/null + @echo "Test database available at: 127.0.0.1:$(TEST_DB_PORT)" + @echo "For database logs run:" + @echo " docker logs boundary-sql-test" +clean: + docker stop boundary-sql-tests || true + docker rm -v boundary-sql-tests || true + +.PHONY: all docker-build database-up ${docker-builds} ${docker-publishes} clean diff --git a/testing/dbtest/docker/init-db.sh b/testing/dbtest/docker/init-db.sh new file mode 100755 index 000000000..ae18f8edc --- /dev/null +++ b/testing/dbtest/docker/init-db.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -e +shopt -s globstar + +psql -v "ON_ERROR_STOP=1" --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -q < Date: Fri, 30 Jul 2021 08:45:40 -0400 Subject: [PATCH 2/5] test: Add dbtest package This is modeled after the internal/docker package to make it easy to use as a replacement. Instead of creating a new docker container for each test, it will create a new database in an existing database container, using a template database as a source. This assumes that the template database exists and already has the schema migrations run against it. The default template is `boundary_template` which should already have all migrations applied. However, some tests require an empty database to test the migration code. In these cases the `template1` template database should be used, ie: dbtest.StartWithTemplate("postgres", dbtest.WithTemplate("template1")) --- testing/dbtest/docs.go | 25 ++++++ testing/dbtest/option.go | 30 +++++++ testing/dbtest/template.go | 150 ++++++++++++++++++++++++++++++++ testing/dbtest/template_test.go | 72 +++++++++++++++ 4 files changed, 277 insertions(+) create mode 100644 testing/dbtest/docs.go create mode 100644 testing/dbtest/option.go create mode 100644 testing/dbtest/template.go create mode 100644 testing/dbtest/template_test.go diff --git a/testing/dbtest/docs.go b/testing/dbtest/docs.go new file mode 100644 index 000000000..ada0d0321 --- /dev/null +++ b/testing/dbtest/docs.go @@ -0,0 +1,25 @@ +// Package dbtest provides a way to create a clean database for tests using +// template databases. For it to function properly a postgres database server +// must be running with the expected templates. This is typically done using +// the docker image provided in the docker directory of this package. +// +// To use this package in a test add something like the following in the +// beginning of the test: +// +// c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres) +// require.NoError(t, err) +// t.Cleanup(func() { +// require.NoError(t, c()) +// }) +// // use u to get a connection to the new database +// dBase, err := sql.Open("postgres", u) +// +// By default this uses a template that already has all of the boundary +// migrations run. If a test needs a database without any migrations, like in +// the case where the migration code needs to be tested, a different template +// can be specified. This should generally be template1: +// +// c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) +// +// See https://www.postgresql.org/docs/13/manage-ag-templatedbs.html +package dbtest diff --git a/testing/dbtest/option.go b/testing/dbtest/option.go new file mode 100644 index 000000000..679228420 --- /dev/null +++ b/testing/dbtest/option.go @@ -0,0 +1,30 @@ +package dbtest + +// GetOpts - iterate the inbound Options and return a struct. +func GetOpts(opt ...Option) Options { + opts := getDefaultOptions() + for _, o := range opt { + o(&opts) + } + return opts +} + +// Option - how Options are passed as arguments. +type Option func(*Options) + +// Options - how Options are represented. +type Options struct { + withTemplate string +} + +func getDefaultOptions() Options { + return Options{} +} + +// WithTemplate tells the command with database template to use +// when creating a new test database. +func WithTemplate(template string) Option { + return func(o *Options) { + o.withTemplate = template + } +} diff --git a/testing/dbtest/template.go b/testing/dbtest/template.go new file mode 100644 index 000000000..37f2b606f --- /dev/null +++ b/testing/dbtest/template.go @@ -0,0 +1,150 @@ +package dbtest + +import ( + "database/sql" + "fmt" + "math/rand" + "os" + "time" + + // postgres dialect + _ "github.com/lib/pq" +) + +var testDBPort = "5432" + +var ( + // StartUsingTemplate creates a new test database from a postgres template database. + StartUsingTemplate func(dialect string, opt ...Option) (func() error, string, string, error) = startUsingTemplate +) + +func init() { + rand.Seed(time.Now().UnixNano()) + + p := os.Getenv("TEST_DB_PORT") + if p != "" { + testDBPort = p + } +} + +// Dialects +const ( + Postgres = "postgres" +) + +// Template Database Names +const ( + BoundaryTemplate = "boundary_template" + Template1 = "template1" +) + +var supportedDialects = map[string]struct{}{ + Postgres: struct{}{}, +} + +var supportedTemplates = map[string]struct{}{ + BoundaryTemplate: struct{}{}, + Template1: struct{}{}, +} + +const letterBytes = "abcdefghijklmnopqrstuvwxyz" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = rand.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} + +func startUsingTemplate(dialect string, opt ...Option) (cleanup func() error, retURL, dbname string, err error) { + if _, ok := supportedDialects[dialect]; !ok { + return func() error { return nil }, "", "", fmt.Errorf("unsupported dialect: %s", dialect) + } + + db, err := sql.Open(dialect, fmt.Sprintf("postgres://boundary:boundary@127.0.0.1:%s/boundary?sslmode=disable", testDBPort)) + if err != nil { + return func() error { return nil }, "", "", fmt.Errorf("could not connect to source database: %w", err) + } + defer db.Close() + + dbname = fmt.Sprintf("boundary_test_%s", randStr(16)) + template := BoundaryTemplate + + opts := GetOpts(opt...) + if opts.withTemplate != "" { + template = opts.withTemplate + } + if _, ok := supportedTemplates[template]; !ok { + return func() error { return nil }, "", "", fmt.Errorf("unsupported database template: %s", template) + } + + _, err = db.Exec(fmt.Sprintf("create database %s template %s", dbname, template)) + if err != nil { + return func() error { return nil }, "", "", fmt.Errorf("could not create test database: %w", err) + } + + url := fmt.Sprintf("postgres://boundary:boundary@127.0.0.1:%s/%s?sslmode=disable", testDBPort, dbname) + + cleanup = func() error { + return dropDatabase(dialect, dbname) + } + + tdb, err := sql.Open(dialect, url) + if err != nil { + return func() error { return nil }, "", "", fmt.Errorf("could not create test database: %w", err) + } + defer tdb.Close() + + if err := tdb.Ping(); err != nil { + return func() error { return nil }, "", "", fmt.Errorf("could not ping test database: %w", err) + } + + return cleanup, url, dbname, nil +} + +const killconns = ` +select + pg_terminate_backend(pg_stat_activity.pid) +from + pg_stat_activity +where pg_stat_activity.datname = $1 + and pid <> pg_backend_pid(); +` + +func dropDatabase(dialect, dbname string) error { + if dbname == "" { + return fmt.Errorf("empty dbname") + } + + db, err := sql.Open(dialect, fmt.Sprintf("postgres://boundary:boundary@127.0.0.1:%s/boundary?sslmode=disable", testDBPort)) + if err != nil { + return err + } + defer db.Close() + + _, err = db.Exec(killconns, dbname) + if err != nil { + return err + } + _, err = db.Exec(fmt.Sprintf("drop database %s", dbname)) + if err != nil { + return err + } + return nil +} diff --git a/testing/dbtest/template_test.go b/testing/dbtest/template_test.go new file mode 100644 index 000000000..dcf305199 --- /dev/null +++ b/testing/dbtest/template_test.go @@ -0,0 +1,72 @@ +package dbtest_test + +import ( + "database/sql" + "errors" + "testing" + + "github.com/hashicorp/boundary/testing/dbtest" + "github.com/stretchr/testify/require" +) + +func TestStartUsingTemplate(t *testing.T) { + tests := []struct { + name string + dialect string + options []dbtest.Option + err error + }{ + { + "PostgresNoOptions", + dbtest.Postgres, + []dbtest.Option{}, + nil, + }, + { + "PostgresTemplate1", + dbtest.Postgres, + []dbtest.Option{ + dbtest.WithTemplate(dbtest.Template1), + }, + nil, + }, + { + "PostgresUnsupportedTemplate", + dbtest.Postgres, + []dbtest.Option{ + dbtest.WithTemplate("unsupportedTemplate"), + }, + errors.New("unsupported database template: unsupportedTemplate"), + }, + { + "UnsupportedDialect", + "unsupportedDialect", + []dbtest.Option{}, + errors.New("unsupported dialect: unsupportedDialect"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, u, _, err := dbtest.StartUsingTemplate(tt.dialect, tt.options...) + + if tt.err != nil { + require.EqualError(t, err, tt.err.Error()) + require.NoError(t, c()) + } else { + require.NoError(t, err) + + db, err := sql.Open(tt.dialect, u) + require.NoError(t, err) + require.NoError(t, db.Ping()) + db.Close() + + require.NoError(t, c()) + + // ensure that database is gone after calling c + db, _ = sql.Open(tt.dialect, u) + require.Error(t, db.Ping()) + } + }) + } +} From 75388f727a640eaee0656001a5575853707fc482 Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Fri, 30 Jul 2021 08:49:56 -0400 Subject: [PATCH 3/5] test: Refactor to use dbtest package instead of docker This swaps the test initialization to use the dbtest package so each test will get a fresh database created from a template database, instead of a completely new docker container. --- internal/cmd/base/dev.go | 7 +- internal/cmd/base/option.go | 9 + internal/cmd/commands/database/funcs_test.go | 20 +- internal/cmd/commands/server/server_test.go | 2 +- internal/db/db.go | 3 - internal/db/db_test.go | 3 +- internal/db/schema/manager_test.go | 41 +- .../postgres/11/01_server_type_enum_test.go | 6 +- .../12/01_timestamp_sub_funcs_test.go | 6 +- .../postgres/14/warehouse_user_dim_test.go | 6 +- .../migrations/postgres/2/07_iam_test.go | 6 +- .../migrations/postgres/2/10_auth_test.go | 2 +- .../postgres/8/08_connection_test.go | 2 +- internal/db/schema/postgres/postgres_test.go | 720 +++++++----------- internal/db/schema/schema_test.go | 10 +- internal/db/testing.go | 12 +- internal/oplog/testing.go | 4 +- .../credentialstore_service_test.go | 2 +- internal/servers/controller/testing.go | 1 + 19 files changed, 372 insertions(+), 490 deletions(-) diff --git a/internal/cmd/base/dev.go b/internal/cmd/base/dev.go index fa7d7e4f9..4ab7a9267 100644 --- a/internal/cmd/base/dev.go +++ b/internal/cmd/base/dev.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/testing/dbtest" capoidc "github.com/hashicorp/cap/oidc" "github.com/hashicorp/go-multierror" ) @@ -39,7 +40,11 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { switch b.DatabaseUrl { case "": - c, url, container, err = docker.StartDbInDocker(dialect, docker.WithContainerImage(opts.withContainerImage)) + if opts.withDatabaseTemplate != "" { + c, url, _, err = dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(opts.withDatabaseTemplate)) + } else { + c, url, container, err = docker.StartDbInDocker(dialect, docker.WithContainerImage(opts.withContainerImage)) + } // In case of an error, run the cleanup function. If we pass all errors, c should be set to a noop // function before returning from this method defer func() { diff --git a/internal/cmd/base/option.go b/internal/cmd/base/option.go index d83119f53..f528d6e28 100644 --- a/internal/cmd/base/option.go +++ b/internal/cmd/base/option.go @@ -28,6 +28,7 @@ type Options struct { withSkipTargetCreation bool withContainerImage string withDialect string + withDatabaseTemplate string withEventerConfig *event.EventerConfig withEventFlags *EventFlags withAttributeFieldPrefix string @@ -149,3 +150,11 @@ func WithStatusCode(statusCode int) Option { o.withStatusCode = statusCode } } + +// WithDatabaseTemplate allows for using an existing database template for +// initializing the boundary database. +func WithDatabaseTemplate(template string) Option { + return func(o *Options) { + o.withDatabaseTemplate = template + } +} diff --git a/internal/cmd/commands/database/funcs_test.go b/internal/cmd/commands/database/funcs_test.go index 2ff72110f..fe41b4656 100644 --- a/internal/cmd/commands/database/funcs_test.go +++ b/internal/cmd/commands/database/funcs_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/schema" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/mitchellh/cli" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,7 +15,7 @@ import ( func TestMigrateDatabase(t *testing.T) { ctx := context.Background() - dialect := "postgres" + dialect := dbtest.Postgres cases := []struct { name string @@ -29,7 +29,7 @@ func TestMigrateDatabase(t *testing.T) { name: "not_initialized_expected_not_intialized", initialized: false, urlProvider: func() string { - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -43,7 +43,7 @@ func TestMigrateDatabase(t *testing.T) { name: "basic_initialized_expects_initialized", initialized: true, urlProvider: func() string { - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -69,7 +69,7 @@ func TestMigrateDatabase(t *testing.T) { name: "basic_initialized_expects_not_initialized", initialized: false, urlProvider: func() string { - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -95,7 +95,7 @@ func TestMigrateDatabase(t *testing.T) { name: "old_version_table_used_intialized", initialized: true, urlProvider: func() string { - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -115,7 +115,7 @@ func TestMigrateDatabase(t *testing.T) { name: "old_version_table_used_not_intialized", initialized: false, urlProvider: func() string { - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -149,7 +149,7 @@ func TestMigrateDatabase(t *testing.T) { name: "cant_get_lock_initialized", initialized: true, urlProvider: func() string { - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -172,7 +172,7 @@ func TestMigrateDatabase(t *testing.T) { name: "cant_get_lock_not_initialized", initialized: false, urlProvider: func() string { - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -210,7 +210,7 @@ func TestVerifyOplogIsEmpty(t *testing.T) { dialect := "postgres" ctx := context.Background() - c, u, _, err := db.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) diff --git a/internal/cmd/commands/server/server_test.go b/internal/cmd/commands/server/server_test.go index 0b4f81ce7..2c51f3890 100644 --- a/internal/cmd/commands/server/server_test.go +++ b/internal/cmd/commands/server/server_test.go @@ -67,7 +67,7 @@ func testServerCommand(t *testing.T, opts testServerCommandOpts) *Command { cmd.Server.DevTargetId = defaultTestTargetId } - err = cmd.CreateDevDatabase(cmd.Context, base.WithContainerImage("postgres"), base.WithSkipOidcAuthMethodCreation()) + err = cmd.CreateDevDatabase(cmd.Context, base.WithDatabaseTemplate("boundary_template"), base.WithSkipOidcAuthMethodCreation()) if err != nil { if cmd.DevDatabaseCleanupFunc != nil { require.NoError(cmd.DevDatabaseCleanupFunc()) diff --git a/internal/db/db.go b/internal/db/db.go index d3fd54b57..f506c9845 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -7,7 +7,6 @@ import ( "math" "time" - "github.com/hashicorp/boundary/internal/docker" "github.com/hashicorp/boundary/internal/observability/event" "github.com/hashicorp/go-hclog" "github.com/jinzhu/gorm" @@ -23,8 +22,6 @@ func init() { pq.EnableInfinityTs(NegativeInfinityTS, PositiveInfinityTS) } -var StartDbInDocker = docker.StartDbInDocker - type DbType int const ( diff --git a/internal/db/db_test.go b/internal/db/db_test.go index 75fc50758..7329ceb17 100644 --- a/internal/db/db_test.go +++ b/internal/db/db_test.go @@ -3,12 +3,13 @@ package db import ( "testing" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/lib/pq" "github.com/stretchr/testify/assert" ) func TestOpen(t *testing.T) { - cleanup, url, _, err := StartDbInDocker("postgres") + cleanup, url, _, err := dbtest.StartUsingTemplate(dbtest.Postgres) if err != nil { t.Fatal(err) } diff --git a/internal/db/schema/manager_test.go b/internal/db/schema/manager_test.go index 3213bcb13..84b522d56 100644 --- a/internal/db/schema/manager_test.go +++ b/internal/db/schema/manager_test.go @@ -8,16 +8,16 @@ import ( "testing" "github.com/hashicorp/boundary/internal/db/schema/postgres" - "github.com/hashicorp/boundary/internal/docker" "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewManager(t *testing.T) { - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -37,18 +37,15 @@ func TestNewManager(t *testing.T) { } func TestCurrentState(t *testing.T) { - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) t.Cleanup(func() { if err := c(); err != nil { t.Fatalf("Got error at cleanup: %v", err) } }) require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, c()) - }) ctx := context.Background() d, err := sql.Open(dialect, u) require.NoError(t, err) @@ -79,9 +76,9 @@ func TestCurrentState(t *testing.T) { } func TestRollForward(t *testing.T) { - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -104,13 +101,13 @@ func TestRollForward(t *testing.T) { } func TestRollForward_NotFromFresh(t *testing.T) { - dialect := "postgres" + dialect := dbtest.Postgres oState := migrationStates[dialect] nState := createPartialMigrationState(oState, 8) migrationStates[dialect] = nState - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -142,9 +139,9 @@ func TestRollForward_NotFromFresh(t *testing.T) { } func TestRunMigration_canceledContext(t *testing.T) { - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -163,7 +160,7 @@ func TestRunMigration_canceledContext(t *testing.T) { } func TestRollForward_BadSQL(t *testing.T) { - dialect := "postgres" + dialect := dbtest.Postgres oState := migrationStates[dialect] defer func() { migrationStates[dialect] = oState }() @@ -172,7 +169,7 @@ func TestRollForward_BadSQL(t *testing.T) { nState.upMigrations[10] = []byte("SELECT 1 FROM NonExistantTable;") migrationStates[dialect] = nState - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -194,9 +191,9 @@ func TestRollForward_BadSQL(t *testing.T) { func TestManager_ExclusiveLock(t *testing.T) { ctx := context.Background() - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -219,9 +216,9 @@ func TestManager_ExclusiveLock(t *testing.T) { func TestManager_SharedLock(t *testing.T) { ctx := context.Background() - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -248,9 +245,9 @@ func TestManager_SharedLock(t *testing.T) { func Test_GetMigrationLog(t *testing.T) { t.Parallel() ctx := context.Background() - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) diff --git a/internal/db/schema/migrations/postgres/11/01_server_type_enum_test.go b/internal/db/schema/migrations/postgres/11/01_server_type_enum_test.go index bd7706841..6d8779519 100644 --- a/internal/db/schema/migrations/postgres/11/01_server_type_enum_test.go +++ b/internal/db/schema/migrations/postgres/11/01_server_type_enum_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/hashicorp/boundary/internal/db/schema" - "github.com/hashicorp/boundary/internal/docker" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/stretchr/testify/require" ) @@ -40,10 +40,10 @@ func Test_ServerEnumChanges(t *testing.T) { require := require.New(t) const priorMigration = 10007 const serverEnumMigration = 11001 - dialect := "postgres" + dialect := dbtest.Postgres ctx := context.Background() - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(err) t.Cleanup(func() { require.NoError(c()) diff --git a/internal/db/schema/migrations/postgres/12/01_timestamp_sub_funcs_test.go b/internal/db/schema/migrations/postgres/12/01_timestamp_sub_funcs_test.go index 3eda4f90f..eb9fac8d7 100644 --- a/internal/db/schema/migrations/postgres/12/01_timestamp_sub_funcs_test.go +++ b/internal/db/schema/migrations/postgres/12/01_timestamp_sub_funcs_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/hashicorp/boundary/internal/db/schema" - "github.com/hashicorp/boundary/internal/docker" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/stretchr/testify/require" ) @@ -34,9 +34,9 @@ func testSetupDb(ctx context.Context, t *testing.T) *sql.DB { t.Helper() require := require.New(t) - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(err) t.Cleanup(func() { require.NoError(c()) diff --git a/internal/db/schema/migrations/postgres/14/warehouse_user_dim_test.go b/internal/db/schema/migrations/postgres/14/warehouse_user_dim_test.go index cb7410b49..1b40812f2 100644 --- a/internal/db/schema/migrations/postgres/14/warehouse_user_dim_test.go +++ b/internal/db/schema/migrations/postgres/14/warehouse_user_dim_test.go @@ -9,12 +9,12 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/schema" - "github.com/hashicorp/boundary/internal/docker" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/testing/dbtest" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" @@ -30,9 +30,9 @@ func TestMigrations_UserDimension(t *testing.T) { t.Parallel() assert, require := assert.New(t), require.New(t) ctx := context.Background() - dialect := "postgres" + dialect := dbtest.Postgres - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(err) t.Cleanup(func() { require.NoError(c()) diff --git a/internal/db/schema/migrations/postgres/2/07_iam_test.go b/internal/db/schema/migrations/postgres/2/07_iam_test.go index 97b1dbb69..e24549b33 100644 --- a/internal/db/schema/migrations/postgres/2/07_iam_test.go +++ b/internal/db/schema/migrations/postgres/2/07_iam_test.go @@ -8,8 +8,8 @@ import ( "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/schema" - "github.com/hashicorp/boundary/internal/docker" "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,10 +30,10 @@ func Test_PrimaryAuthMethodChanges(t *testing.T) { // // 4) asserting some bits about the state of the db. assert, require := assert.New(t), require.New(t) - dialect := "postgres" + dialect := dbtest.Postgres ctx := context.Background() - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(err) t.Cleanup(func() { require.NoError(c()) diff --git a/internal/db/schema/migrations/postgres/2/10_auth_test.go b/internal/db/schema/migrations/postgres/2/10_auth_test.go index 8d7fc74b4..9fe69827b 100644 --- a/internal/db/schema/migrations/postgres/2/10_auth_test.go +++ b/internal/db/schema/migrations/postgres/2/10_auth_test.go @@ -18,7 +18,7 @@ func Test_AuthMethodSubtypes(t *testing.T) { t.Parallel() assert, require := assert.New(t), require.New(t) ctx := context.Background() - conn, _ := db.TestSetup(t, "postgres") + conn, _ := db.TestSetup(t, "postgres", db.WithTemplate("template1")) rw := db.New(conn) rootWrapper := db.TestWrapper(t) kmsCache := kms.TestKms(t, conn, rootWrapper) diff --git a/internal/db/schema/migrations/postgres/8/08_connection_test.go b/internal/db/schema/migrations/postgres/8/08_connection_test.go index bdbb09324..35367ce9f 100644 --- a/internal/db/schema/migrations/postgres/8/08_connection_test.go +++ b/internal/db/schema/migrations/postgres/8/08_connection_test.go @@ -38,7 +38,7 @@ func TestMigration(t *testing.T) { selectQuery = `select session_id, server_id from session_connection_testing order by session_id` ) - conn, _ := db.TestSetup(t, "postgres") + conn, _ := db.TestSetup(t, "postgres", db.WithTemplate("template1")) db := conn.DB() _, err := db.Exec(createTables) require.NoError(err) diff --git a/internal/db/schema/postgres/postgres_test.go b/internal/db/schema/postgres/postgres_test.go index b56c8010f..2b16c7317 100644 --- a/internal/db/schema/postgres/postgres_test.go +++ b/internal/db/schema/postgres/postgres_test.go @@ -33,490 +33,352 @@ package postgres import ( "bytes" "context" - "database/sql" - sqldriver "database/sql/driver" "fmt" - "io" - "log" "strconv" "strings" "testing" - "github.com/dhui/dktest" - "github.com/golang-migrate/migrate/v4/dktesting" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const ( - pgPassword = "postgres" -) - -var ( - opts = dktest.Options{ - Env: map[string]string{"POSTGRES_PASSWORD": pgPassword}, - PortRequired: true, ReadyFunc: isReady, - } - // Supported versions: https://www.postgresql.org/support/versioning/ - specs = []dktesting.ContainerSpec{ - {ImageName: "postgres:12", Options: opts}, - } -) - -func pgConnectionString(host, port string) string { - return fmt.Sprintf("postgres://postgres:%s@%s:%s/postgres?sslmode=disable", pgPassword, host, port) -} - -func isReady(ctx context.Context, c dktest.ContainerInfo) bool { - ip, port, err := c.FirstPort() - if err != nil { - return false - } - - db, err := sql.Open("postgres", pgConnectionString(ip, port)) - if err != nil { - return false - } - defer func() { - if err := db.Close(); err != nil { - log.Println("close error:", err) - } - }() - if err = db.PingContext(ctx); err != nil { - switch err { - case sqldriver.ErrBadConn, io.EOF: - return false - default: - log.Println(err) - } - return false - } - - return true -} - func TestDbStuff(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - d, err := open(t, ctx, addr) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := d.close(t); err != nil { - t.Error(err) - } - }() - test(t, d, []byte("SELECT 1")) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + test(t, d, []byte("SELECT 1")) } func TestCurrentState_NoVersionTable(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - d, err := open(t, ctx, addr) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := d.close(t); err != nil { - t.Error(err) - } - }() - // Drop the version table so calls to CurrentState don't rely on that - require.NoError(t, d.drop(ctx)) - - v, alreadyRan, dirt, err := d.CurrentState(ctx) - assert.NoError(t, err) - assert.False(t, alreadyRan) - assert.Equal(t, v, nilVersion) - assert.False(t, dirt) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + // Drop the version table so calls to CurrentState don't rely on that + require.NoError(t, d.drop(ctx)) + v, alreadyRan, dirt, err := d.CurrentState(ctx) + assert.NoError(t, err) + assert.False(t, alreadyRan) + assert.Equal(t, v, nilVersion) + assert.False(t, dirt) } func TestCurrentState_ToManyTables(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - d, err := open(t, ctx, addr) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := d.close(t); err != nil { - t.Error(err) - } - }() - - // Create the most recent table - require.NoError(t, d.EnsureVersionTable(ctx)) - - // Create the legacy version of the table. - oldTableCreate := `create table if not exists schema_migrations (version bigint primary key, dirty boolean not null)` - _, err = d.conn.ExecContext(ctx, oldTableCreate) - require.NoError(t, err) - v, alreadyRan, dirt, err := d.CurrentState(ctx) - assert.Error(t, err) - assert.True(t, alreadyRan) - assert.Equal(t, v, nilVersion) - assert.False(t, dirt) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + // Create the most recent table + require.NoError(t, d.EnsureVersionTable(ctx)) + + // Create the legacy version of the table. + oldTableCreate := `create table if not exists schema_migrations (version bigint primary key, dirty boolean not null)` + _, err = d.conn.ExecContext(ctx, oldTableCreate) + require.NoError(t, err) + v, alreadyRan, dirt, err := d.CurrentState(ctx) + assert.Error(t, err) + assert.True(t, alreadyRan) + assert.Equal(t, v, nilVersion) + assert.False(t, dirt) } func TestMultiStatement(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - d, err := open(t, ctx, addr) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := d.close(t); err != nil { - t.Error(err) - } - }() - if err := d.EnsureVersionTable(ctx); err != nil { - t.Fatalf("expected err to be nil, got %v", err) - } - - if err := d.Run(ctx, strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);"), 2); err != nil { - t.Fatalf("expected err to be nil, got %v", err) - } - - // make sure second table exists - var exists bool - if err := d.conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))").Scan(&exists); err != nil { - t.Fatal(err) - } - if !exists { - t.Fatalf("expected table bar to exist") - } + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + if err := d.EnsureVersionTable(ctx); err != nil { + t.Fatalf("expected err to be nil, got %v", err) + } + + if err := d.Run(ctx, strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);"), 2); err != nil { + t.Fatalf("expected err to be nil, got %v", err) + } + + // make sure second table exists + var exists bool + if err := d.conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))").Scan(&exists); err != nil { + t.Fatal(err) + } + if !exists { + t.Fatalf("expected table bar to exist") + } } func TestTransaction(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - d, err := open(t, ctx, addr) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := d.close(t); err != nil { - t.Error(err) - } - }() - - v, alreadyRan, dirty, err := d.CurrentState(ctx) - assert.NoError(t, err) - assert.False(t, alreadyRan) - assert.False(t, dirty) - assert.Equal(t, -1, v) - - // Fail the initial setup of the db. - assert.NoError(t, d.StartRun(ctx)) - assert.NoError(t, d.EnsureVersionTable(ctx)) - assert.Error(t, d.Run(ctx, strings.NewReader("SELECT 1 from nonExistantTable"), 3)) - assert.Error(t, d.CommitRun()) - - v, alreadyRan, dirty, err = d.CurrentState(ctx) - assert.NoError(t, err) - assert.False(t, alreadyRan) - assert.False(t, dirty) - assert.Equal(t, -1, v) - - assert.NoError(t, d.StartRun(ctx)) - assert.NoError(t, d.EnsureVersionTable(ctx)) - assert.NoError(t, d.Run(ctx, strings.NewReader("CREATE TABLE foo (foo text);"), 2)) - assert.NoError(t, d.Run(ctx, strings.NewReader("SELECT 1;"), 3)) - assert.NoError(t, d.CommitRun()) - - v, alreadyRan, dirty, err = d.CurrentState(ctx) - assert.NoError(t, err) - assert.True(t, alreadyRan) - assert.False(t, dirty) - assert.Equal(t, 3, v) - - assert.NoError(t, d.StartRun(ctx)) - assert.NoError(t, d.Run(ctx, strings.NewReader("CREATE TABLE bar (bar text);"), 20)) - assert.Error(t, d.Run(ctx, strings.NewReader("SELECT 1 FROM NonExistingTable"), 30)) - assert.Error(t, d.CommitRun()) - - v, alreadyRan, dirty, err = d.CurrentState(ctx) - assert.NoError(t, err) - assert.True(t, alreadyRan) - assert.False(t, dirty) - assert.Equal(t, 3, v) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + v, alreadyRan, dirty, err := d.CurrentState(ctx) + assert.NoError(t, err) + assert.False(t, alreadyRan) + assert.False(t, dirty) + assert.Equal(t, -1, v) + + // Fail the initial setup of the db. + assert.NoError(t, d.StartRun(ctx)) + assert.NoError(t, d.EnsureVersionTable(ctx)) + assert.Error(t, d.Run(ctx, strings.NewReader("SELECT 1 from nonExistantTable"), 3)) + assert.Error(t, d.CommitRun()) + + v, alreadyRan, dirty, err = d.CurrentState(ctx) + assert.NoError(t, err) + assert.False(t, alreadyRan) + assert.False(t, dirty) + assert.Equal(t, -1, v) + + assert.NoError(t, d.StartRun(ctx)) + assert.NoError(t, d.EnsureVersionTable(ctx)) + assert.NoError(t, d.Run(ctx, strings.NewReader("CREATE TABLE foo (foo text);"), 2)) + assert.NoError(t, d.Run(ctx, strings.NewReader("SELECT 1;"), 3)) + assert.NoError(t, d.CommitRun()) + + v, alreadyRan, dirty, err = d.CurrentState(ctx) + assert.NoError(t, err) + assert.True(t, alreadyRan) + assert.False(t, dirty) + assert.Equal(t, 3, v) + + assert.NoError(t, d.StartRun(ctx)) + assert.NoError(t, d.Run(ctx, strings.NewReader("CREATE TABLE bar (bar text);"), 20)) + assert.Error(t, d.Run(ctx, strings.NewReader("SELECT 1 FROM NonExistingTable"), 30)) + assert.Error(t, d.CommitRun()) + + v, alreadyRan, dirty, err = d.CurrentState(ctx) + assert.NoError(t, err) + assert.True(t, alreadyRan) + assert.False(t, dirty) + assert.Equal(t, 3, v) } func TestWithSchema(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - require.NoError(t, err) - - addr := pgConnectionString(ip, port) - d, err := open(t, ctx, addr) - require.NoError(t, err) - defer func() { - if err := d.close(t); err != nil { - t.Fatal(err) - } - }() - require.NoError(t, d.EnsureVersionTable(ctx)) - - // create foobar schema - require.NoError(t, d.Run(ctx, strings.NewReader("CREATE SCHEMA foobar AUTHORIZATION postgres"), 1)) - - // re-connect using that schema - d2, err := open(t, ctx, fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foobar", - pgPassword, ip, port)) - require.NoError(t, err) - defer func() { - if err := d2.close(t); err != nil { - t.Fatal(err) - } - }() - - version, alreadyRan, _, err := d2.CurrentState(ctx) - require.NoError(t, err) - require.Equal(t, nilVersion, version) - assert.False(t, alreadyRan) - - // now update CurrentState and compare - require.NoError(t, d2.EnsureVersionTable(ctx)) - require.NoError(t, d2.setVersion(ctx, 2, false)) - version, alreadyRan, _, err = d2.CurrentState(ctx) - require.NoError(t, err) - require.Equal(t, 2, version) - assert.True(t, alreadyRan) - - // meanwhile, the public schema still has the other CurrentState - version, alreadyRan, _, err = d.CurrentState(ctx) - require.NoError(t, err) - require.Equal(t, 1, version) - assert.True(t, alreadyRan) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + require.NoError(t, d.EnsureVersionTable(ctx)) + + // create foobar schema + require.NoError(t, d.Run(ctx, strings.NewReader("CREATE SCHEMA foobar AUTHORIZATION postgres"), 1)) + + // re-connect using that schema + d2, err := open(t, ctx, fmt.Sprintf("%s&search_path=foobar", u)) + require.NoError(t, err) + defer func() { + if err := d2.close(t); err != nil { + t.Fatal(err) + } + }() + + version, alreadyRan, _, err := d2.CurrentState(ctx) + require.NoError(t, err) + require.Equal(t, nilVersion, version) + assert.False(t, alreadyRan) + + // now update CurrentState and compare + require.NoError(t, d2.EnsureVersionTable(ctx)) + require.NoError(t, d2.setVersion(ctx, 2, false)) + version, alreadyRan, _, err = d2.CurrentState(ctx) + require.NoError(t, err) + require.Equal(t, 2, version) + assert.True(t, alreadyRan) + + // meanwhile, the public schema still has the other CurrentState + version, alreadyRan, _, err = d.CurrentState(ctx) + require.NoError(t, err) + require.Equal(t, 1, version) + assert.True(t, alreadyRan) } func TestPostgres_Lock(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - ps, err := open(t, ctx, addr) - if err != nil { - t.Fatal(err) - } - - test(t, ps, []byte("SELECT 1")) - - err = ps.Lock(ctx) - if err != nil { - t.Fatal(err) - } - - err = ps.Unlock(ctx) - if err != nil { - t.Fatal(err) - } - - err = ps.Lock(ctx) - if err != nil { - t.Fatal(err) - } - - err = ps.Unlock(ctx) - if err != nil { - t.Fatal(err) - } - - // make sure we call call Unlock in an idempotent manner. - err = ps.Unlock(ctx) - if err != nil { - t.Fatal(err) - } + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + test(t, d, []byte("SELECT 1")) + + err = d.Lock(ctx) + if err != nil { + t.Fatal(err) + } + + err = d.Unlock(ctx) + if err != nil { + t.Fatal(err) + } + + err = d.Lock(ctx) + if err != nil { + t.Fatal(err) + } + + err = d.Unlock(ctx) + if err != nil { + t.Fatal(err) + } + + // make sure we call call Unlock in an idempotent manner. + err = d.Unlock(ctx) + if err != nil { + t.Fatal(err) + } } func TestEnsureTable_Fresh(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - p, err := open(t, ctx, addr) - if err != nil { - require.NoError(t, err) - } - t.Cleanup(func() { - require.NoError(t, p.close(t)) - }) - - tableCreated := false - query := "SELECT exists (SELECT 1 FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_name = '" + defaultMigrationsTable + "')" - assert.NoError(t, p.db.QueryRowContext(ctx, query).Scan(&tableCreated)) - assert.False(t, tableCreated) - - assert.NoError(t, p.EnsureVersionTable(ctx)) - assert.NoError(t, p.db.QueryRowContext(ctx, query).Scan(&tableCreated)) - assert.True(t, tableCreated) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + tableCreated := false + query := "SELECT exists (SELECT 1 FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_name = '" + defaultMigrationsTable + "')" + assert.NoError(t, d.db.QueryRowContext(ctx, query).Scan(&tableCreated)) + assert.False(t, tableCreated) + + assert.NoError(t, d.EnsureVersionTable(ctx)) + assert.NoError(t, d.db.QueryRowContext(ctx, query).Scan(&tableCreated)) + assert.True(t, tableCreated) } func TestEnsureTable_ExistingTable(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - p, err := open(t, ctx, addr) - if err != nil { - require.NoError(t, err) - } - t.Cleanup(func() { - require.NoError(t, p.close(t)) - }) - assert.NoError(t, p.EnsureVersionTable(ctx)) - - oldTableCreate := `CREATE TABLE IF NOT EXISTS schema_migrations (version bigint primary key, dirty boolean not null)` - _, err = p.db.ExecContext(ctx, oldTableCreate) - assert.NoError(t, err) - - assert.NoError(t, p.EnsureVersionTable(ctx)) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + assert.NoError(t, d.EnsureVersionTable(ctx)) + + oldTableCreate := `CREATE TABLE IF NOT EXISTS schema_migrations (version bigint primary key, dirty boolean not null)` + _, err = d.db.ExecContext(ctx, oldTableCreate) + assert.NoError(t, err) + + assert.NoError(t, d.EnsureVersionTable(ctx)) } func TestEnsureTable_OldTable(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - p, err := open(t, ctx, addr) - if err != nil { - require.NoError(t, err) - } - t.Cleanup(func() { - require.NoError(t, p.close(t)) - }) - - oldTableCreate := `CREATE TABLE IF NOT EXISTS schema_migrations (version bigint primary key, dirty boolean not null)` - _, err = p.db.ExecContext(ctx, oldTableCreate) - assert.NoError(t, err) - - tableExists := false - oldTableCheck := "SELECT exists (SELECT 1 FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_name = 'schema_migrations')" - assert.NoError(t, p.db.QueryRowContext(ctx, oldTableCheck).Scan(&tableExists)) - assert.True(t, tableExists) - - query := "SELECT exists (SELECT 1 FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_name = '" + defaultMigrationsTable + "')" - assert.NoError(t, p.db.QueryRowContext(ctx, query).Scan(&tableExists)) - assert.False(t, tableExists) - - assert.NoError(t, p.EnsureVersionTable(ctx)) - - assert.NoError(t, p.db.QueryRowContext(ctx, oldTableCheck).Scan(&tableExists)) - assert.False(t, tableExists) - assert.NoError(t, p.db.QueryRowContext(ctx, query).Scan(&tableExists)) - assert.True(t, tableExists) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + oldTableCreate := `CREATE TABLE IF NOT EXISTS schema_migrations (version bigint primary key, dirty boolean not null)` + _, err = d.db.ExecContext(ctx, oldTableCreate) + assert.NoError(t, err) + + tableExists := false + oldTableCheck := "SELECT exists (SELECT 1 FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_name = 'schema_migrations')" + assert.NoError(t, d.db.QueryRowContext(ctx, oldTableCheck).Scan(&tableExists)) + assert.True(t, tableExists) + + query := "SELECT exists (SELECT 1 FROM information_schema.tables WHERE table_schema=(SELECT current_schema()) AND table_name = '" + defaultMigrationsTable + "')" + assert.NoError(t, d.db.QueryRowContext(ctx, query).Scan(&tableExists)) + assert.False(t, tableExists) + + assert.NoError(t, d.EnsureVersionTable(ctx)) + + assert.NoError(t, d.db.QueryRowContext(ctx, oldTableCheck).Scan(&tableExists)) + assert.False(t, tableExists) + assert.NoError(t, d.db.QueryRowContext(ctx, query).Scan(&tableExists)) + assert.True(t, tableExists) } func TestRollback(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - p, err := open(t, ctx, addr) - if err != nil { - require.NoError(t, err) - } - t.Cleanup(func() { - require.NoError(t, p.close(t)) - }) - - assert.NoError(t, p.StartRun(ctx)) - assert.NoError(t, p.EnsureVersionTable(ctx)) - assert.NoError(t, p.Run(ctx, bytes.NewReader([]byte("create table if not exists foo (foo text)")), 2)) - var exists bool - query := "select exists (select 1 from information_schema.tables where table_name = 'foo' and table_schema = (select current_schema()))" - assert.NoError(t, p.conn.QueryRowContext(context.Background(), query).Scan(&exists)) - assert.True(t, exists) - assert.NoError(t, p.Rollback()) - - assert.NoError(t, p.conn.QueryRowContext(context.Background(), query).Scan(&exists)) - assert.False(t, exists) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + assert.NoError(t, d.StartRun(ctx)) + assert.NoError(t, d.EnsureVersionTable(ctx)) + assert.NoError(t, d.Run(ctx, bytes.NewReader([]byte("create table if not exists foo (foo text)")), 2)) + var exists bool + query := "select exists (select 1 from information_schema.tables where table_name = 'foo' and table_schema = (select current_schema()))" + assert.NoError(t, d.conn.QueryRowContext(context.Background(), query).Scan(&exists)) + assert.True(t, exists) + assert.NoError(t, d.Rollback()) + + assert.NoError(t, d.conn.QueryRowContext(context.Background(), query).Scan(&exists)) + assert.False(t, exists) } func TestRun_Error(t *testing.T) { - dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { - ctx := context.Background() - ip, port, err := c.FirstPort() - if err != nil { - t.Fatal(err) - } - - addr := pgConnectionString(ip, port) - p, err := open(t, ctx, addr) - if err != nil { - require.NoError(t, err) - } - t.Cleanup(func() { - require.NoError(t, p.close(t)) - }) - - err = p.Run(ctx, bytes.NewReader([]byte("SELECT *\nFROM foo")), 2) - assert.Error(t, err) + t.Parallel() + ctx := context.Background() + c, u, _, err := dbtest.StartUsingTemplate(dbtest.Postgres, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) }) + d, err := open(t, ctx, u) + require.NoError(t, err) + + err = d.Run(ctx, bytes.NewReader([]byte("SELECT *\nFROM foo")), 2) + assert.Error(t, err) } func Test_computeLineFromPos(t *testing.T) { diff --git a/internal/db/schema/schema_test.go b/internal/db/schema/schema_test.go index e921c9060..634e881ef 100644 --- a/internal/db/schema/schema_test.go +++ b/internal/db/schema/schema_test.go @@ -5,16 +5,16 @@ import ( "database/sql" "testing" - "github.com/hashicorp/boundary/internal/docker" + "github.com/hashicorp/boundary/testing/dbtest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMigrateStore(t *testing.T) { - dialect := "postgres" + dialect := dbtest.Postgres ctx := context.Background() - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) @@ -45,10 +45,10 @@ func TestMigrateStore(t *testing.T) { func Test_MigrateStore_WithMigrationStates(t *testing.T) { assert, require := assert.New(t), require.New(t) - dialect := "postgres" + dialect := dbtest.Postgres ctx := context.Background() - c, u, _, err := docker.StartDbInDocker(dialect) + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) require.NoError(err) t.Cleanup(func() { require.NoError(c()) diff --git a/internal/db/testing.go b/internal/db/testing.go index 440f748c0..915695103 100644 --- a/internal/db/testing.go +++ b/internal/db/testing.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/boundary/internal/db/schema" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/oplog/store" + "github.com/hashicorp/boundary/testing/dbtest" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-kms-wrapping/wrappers/aead" "github.com/jinzhu/gorm" @@ -30,7 +31,7 @@ func TestSetup(t *testing.T, dialect string, opt ...TestOption) (*gorm.DB, strin switch opts.withTestDatabaseUrl { case "": - cleanup, url, _, err = StartDbInDocker(dialect) + cleanup, url, _, err = dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(opts.withTemplate)) if err != nil { t.Fatal(err) } @@ -172,6 +173,7 @@ type testOptions struct { withOperation oplog.OpType withTestDatabaseUrl string withResourcePrivateId bool + withTemplate string } func getDefaultTestOptions() testOptions { @@ -211,3 +213,11 @@ func WithResourcePrivateId(enable bool) TestOption { o.withResourcePrivateId = enable } } + +// WithTemplate provides a way to specify the source database template for creating +// a database. +func WithTemplate(template string) TestOption { + return func(o *testOptions) { + o.withTemplate = template + } +} diff --git a/internal/oplog/testing.go b/internal/oplog/testing.go index 054fec0e0..5476b6b91 100644 --- a/internal/oplog/testing.go +++ b/internal/oplog/testing.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/hashicorp/boundary/internal/db/schema" - "github.com/hashicorp/boundary/internal/docker" "github.com/hashicorp/boundary/internal/oplog/oplog_test" + "github.com/hashicorp/boundary/testing/dbtest" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-kms-wrapping/wrappers/aead" "github.com/hashicorp/go-uuid" @@ -57,7 +57,7 @@ func testId(t *testing.T) string { func testInitDbInDocker(t *testing.T) (cleanup func() error, retURL string, err error) { t.Helper() - cleanup, retURL, _, err = docker.StartDbInDocker("postgres") + cleanup, retURL, _, err = dbtest.StartUsingTemplate(dbtest.Postgres) if err != nil { t.Fatal(err) } diff --git a/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go b/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go index d152109cd..041ffcc63 100644 --- a/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go +++ b/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go @@ -124,7 +124,7 @@ func TestList(t *testing.T) { return } require.NoError(t, gErr) - assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform())) + assert.ElementsMatch(t, got.Items, tc.res.Items) // Test anonymous listing got, gErr = s.ListCredentialStores(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req) diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index 7e5258865..79bfa0ed1 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -582,6 +582,7 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController { if opts.DisableOidcAuthMethodCreation { createOpts = append(createOpts, base.WithSkipOidcAuthMethodCreation()) } + createOpts = append(createOpts, base.WithDatabaseTemplate("boundary_template")) if err := tc.b.CreateDevDatabase(ctx, createOpts...); err != nil { t.Fatal(err) } From ecdcf72e0e61496beb68dfa8d428ea3de3df1ba5 Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Tue, 24 Aug 2021 14:48:11 -0400 Subject: [PATCH 4/5] ci: Update Makefile with new targets for testing This updates the test-ci target to startup the test database container first, wait for it to be ready, and then start running the tests. It also creates some new targets for local devs. Prior to running any tests either via `make test` or `go test`, the database should be started with: make test-database-up It can take a few seconds for the database to fully initialize, the status can be checked with: docker logs -f boundary-sql-tests This database can be stopped and destroyed with: make test-database-down In general it should only need to be restarted when new migrations are added. But it might also be necessary to restart if some tests fail in an unexpected way and fail to do cleanup steps. --- .circleci/config.yml | 22 +++++-- .circleci/config/jobs/build.yml | 14 ++++- .circleci/config/jobs/test-sql.yml | 2 +- CONTRIBUTING.md | 97 +++++++++++++++++++++++++++++- Makefile | 12 +++- 5 files changed, 136 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 781135b83..049f97ccb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ version: 2 jobs: test-sql-13-alpine: machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202107-02 steps: - checkout - run: @@ -249,7 +249,7 @@ jobs: - .buildcache/packages/store test-sql-11-alpine: machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202107-02 steps: - checkout - run: @@ -887,7 +887,7 @@ jobs: - .buildcache/packages/store test-sql-12-alpine: machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202107-02 steps: - checkout - run: @@ -969,14 +969,26 @@ jobs: - .buildcache/packages/store build: machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202107-02 + resource_class: large steps: - checkout + - run: + command: | + which pg_isready || sudo apt-get update && sudo apt-get install -y postgresql-client + make DOCKER_ARGS='-d' -C testing/dbtest/docker database-up + until pg_isready -h 127.0.0.1; do docker container inspect boundary-sql-tests &> /dev/null || exit -1; sleep 1; done + name: Initialize Test Database - run: command: | make test-ci name: Run Acceptance Tests no_output_timeout: 15m + - run: + command: | + make -C testing/dbtest/docker clean + name: Cleanup + when: always - run: command: | echo 'export SLACK_BUILD_STATUS="fail"' >> $BASH_ENV @@ -1467,7 +1479,7 @@ jobs: - .buildcache/packages/store test-sql-latest: machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202107-02 steps: - checkout - run: diff --git a/.circleci/config/jobs/build.yml b/.circleci/config/jobs/build.yml index 2289ec8c1..87a76947a 100644 --- a/.circleci/config/jobs/build.yml +++ b/.circleci/config/jobs/build.yml @@ -1,13 +1,25 @@ +resource_class: large machine: - image: 'ubuntu-1604:201903-01' + image: 'ubuntu-2004:202107-02' working_directory: ~/boundary steps: - checkout +- run: + name: "Initialize Test Database" + command: | + which pg_isready || sudo apt-get update && sudo apt-get install -y postgresql-client + make DOCKER_ARGS='-d' -C testing/dbtest/docker database-up + until pg_isready -h 127.0.0.1; do docker container inspect boundary-sql-tests &> /dev/null || exit -1; sleep 1; done - run: name: "Run Acceptance Tests" no_output_timeout: 15m command: | make test-ci +- run: + name: "Cleanup" + when: always + command: | + make -C testing/dbtest/docker clean - slack/status: fail_only: true only_for_branches: master diff --git a/.circleci/config/jobs/test-sql.yml b/.circleci/config/jobs/test-sql.yml index c651d2d5e..f82933aa1 100644 --- a/.circleci/config/jobs/test-sql.yml +++ b/.circleci/config/jobs/test-sql.yml @@ -1,5 +1,5 @@ machine: - image: 'ubuntu-1604:201903-01' + image: 'ubuntu-2004:202107-02' working_directory: ~/boundary parameters: postgres-version: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b06d3bfb8..6cd3dfcd6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,40 @@ Any bug fixes fall into this section. ## Testing -To run the entire test suite run this command in the root of the project: +Most tests require a postgres database instance to successfully run. +This is provided via docker +by running a customized postgres image that is optimized for boundary tests. + +Before running the test suite, this docker container must be started: + +``` +$ make test-database-up +``` + +This can take a few seconds to initialize as it will +create a template database with the boundary migrations. +The progress can be checked b running: + +``` +$ docker logs -f boundary-sql-tests +``` + +Once a log line like the following is seen, the container is ready for running +tests: + +``` +PostgreSQL init process complete; ready for start up. +``` + +Alternatively if the `pg_isready` command is installed, it can be used to +determine if the container is ready, i.e.: + +``` +$ until pg_isready -h 127.0.0.1; do sleep 1; done +``` + +To run the entire test suite run this command in the root of the project +once the test database is ready: ``` $ make test @@ -88,3 +121,65 @@ run: ``` $ go test -run TestAuthTokenAuthenticator -v ./internal/auth ``` + +### Stopping the test database container + +The test database container can be shutdown using: + +``` +$ make test-database-down +``` + +Note that the container does *not* need to be shutdown between each run of +`make test` or `go test`. + +### Test database container options + +By default the container uses the host port of 5432. +This can changed using an environment variable: + +``` +$ export TEST_DB_PORT=5433 +$ make test-database-up +$ make test +``` + +The default docker image is built using the `postgres:11` base image. +The image can be changed using a make option to test against other versions: + +``` +$ make IMAGE_TAG=docker.io/hashicorpboundary/postgres:12-alpine test-database-up +$ make IMAGE_TAG=docker.io/hashicorpboundary/postgres:13-alpine test-database-up +$ make IMAGE_TAG=docker.io/hashicorpboundary/postgres:alpine test-database-up +``` + +Additional options can be passed to postgres to customize and override the +configuration in the config file of the docker image. +See the troubleshooting section below for more details. + +### Troubleshooting test database container + +The postgres configuration file included in the image +is optimized to support running the full test suite in parallel in CI. +As such, there may be issues starting the container locally, +especially in cases where the container has less then 4GB of memory. + +This is likely the case if the output of `docker logs boundary-sql-tests` shows: + +``` +pg_ctl: could not start server +``` + +In this case adjust the +[max_connections](https://www.postgresql.org/docs/11/runtime-config-connection.html#GUC-MAX-CONNECTIONS) +and/or +[shared_buffers](https://www.postgresql.org/docs/11/runtime-config-resource.html#GUC-SHARED-BUFFERS): + +``` +make PG_OPTS="-c max_connections=1000" test-database-up +``` + +Note that if `max_connections` is set too low, it may result in sporadic test +failures if a connection cannot be established. In this case, reduce the number +of concurrent tests via `GOMAXPROCS` or selectively run tests. + diff --git a/Makefile b/Makefile index b0bc24aef..9a5e4c2e4 100644 --- a/Makefile +++ b/Makefile @@ -157,14 +157,20 @@ website-install: website-start: @npm start --prefix website/ +test-database-up: + make -C testing/dbtest/docker database-up + +test-database-down: + make -C testing/dbtest/docker clean + test-ci: install-go ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m test-sql: $(MAKE) -C internal/db/sqltest/ test -test: - ~/.go/bin/go test ./... -timeout 30m +test: + go test ./... -timeout 30m install-go: ./ci/goinstall.sh @@ -207,7 +213,7 @@ docker-publish: docker push $(IMAGE_TAG) docker push hashicorp/boundary:latest -.PHONY: api cli tools gen migrations proto website ci-config ci-verify set-ui-version docker docker-build docker-build-dev docker-publish +.PHONY: api cli tools gen migrations proto website ci-config ci-verify set-ui-version docker docker-build docker-build-dev docker-publish test-database-up test-database-down .NOTPARALLEL: From 2ee1d4e6e4403856e8fe11ab0cbecf1529012084 Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Tue, 7 Sep 2021 10:39:03 -0400 Subject: [PATCH 5/5] refactor: Make docker pacakge an internal package of cmd/base The docker package and its StartDbInDocker function should only be used by the `boundary dev` command now. So moving it into an internal package of `base` will prevent it from being use for tests accidentally, while still allowing it to be used for `boundary dev`. --- internal/cmd/base/dev.go | 2 +- internal/{ => cmd/base/internal}/docker/docker.go | 0 internal/{ => cmd/base/internal}/docker/option.go | 0 internal/{ => cmd/base/internal}/docker/supported.go | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename internal/{ => cmd/base/internal}/docker/docker.go (100%) rename internal/{ => cmd/base/internal}/docker/option.go (100%) rename internal/{ => cmd/base/internal}/docker/supported.go (100%) diff --git a/internal/cmd/base/dev.go b/internal/cmd/base/dev.go index 4ab7a9267..90db26906 100644 --- a/internal/cmd/base/dev.go +++ b/internal/cmd/base/dev.go @@ -10,9 +10,9 @@ import ( "strings" "github.com/hashicorp/boundary/internal/auth/oidc" + "github.com/hashicorp/boundary/internal/cmd/base/internal/docker" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/schema" - "github.com/hashicorp/boundary/internal/docker" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/observability/event" diff --git a/internal/docker/docker.go b/internal/cmd/base/internal/docker/docker.go similarity index 100% rename from internal/docker/docker.go rename to internal/cmd/base/internal/docker/docker.go diff --git a/internal/docker/option.go b/internal/cmd/base/internal/docker/option.go similarity index 100% rename from internal/docker/option.go rename to internal/cmd/base/internal/docker/option.go diff --git a/internal/docker/supported.go b/internal/cmd/base/internal/docker/supported.go similarity index 100% rename from internal/docker/supported.go rename to internal/cmd/base/internal/docker/supported.go