From 9118b51bfb66195e9bb5b0bf5637afde0001324a Mon Sep 17 00:00:00 2001 From: Louis Garman <75728+leg100@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:37:44 +0100 Subject: [PATCH] refactor: use docker compose for integration tests (#483) --- .github/workflows/build.yml | 2 - Makefile | 5 - docker-compose.yml | 29 ++++- docs/testing.md | 103 ++---------------- hack/go-tfe-tests.bash | 2 +- internal/integration/agent_token_ui_test.go | 2 +- internal/integration/auto_apply_ui_test.go | 2 +- internal/integration/broker_test.go | 2 +- internal/integration/cloud_block_ui_test.go | 2 +- internal/integration/cluster_test.go | 2 +- .../integration/configuration_version_test.go | 2 +- internal/integration/connect_repo_e2e_test.go | 2 +- internal/integration/db_test.go | 33 ++++++ internal/integration/events_test.go | 2 +- internal/integration/github_login_test.go | 2 +- internal/integration/github_pr_test.go | 2 +- internal/integration/helpers_test.go | 11 ++ internal/integration/lock_file_test.go | 2 +- internal/integration/logs_test.go | 4 +- internal/integration/main_test.go | 50 +++++++-- internal/integration/module_e2e_test.go | 2 +- internal/integration/module_test.go | 2 +- .../notification_configuration_test.go | 2 +- .../notification_gcppubsub_test.go | 2 +- .../integration/notification_slack_test.go | 2 +- internal/integration/oidc_test.go | 2 +- internal/integration/organization_cli_test.go | 2 +- .../organization_min_permissions_test.go | 2 +- internal/integration/organization_test.go | 2 +- .../integration/plan_permission_e2e_test.go | 2 +- internal/integration/repo_db_test.go | 16 +++ internal/integration/repo_test.go | 2 +- internal/integration/retry_run_ui_test.go | 2 +- internal/integration/run_api_test.go | 2 +- internal/integration/run_complete_test.go | 2 +- internal/integration/run_list_ui_test.go | 6 +- internal/integration/run_test.go | 2 +- internal/integration/run_token_test.go | 2 +- internal/integration/sandbox_test.go | 2 +- internal/integration/session_test.go | 2 +- internal/integration/site_admin_ui_test.go | 2 +- internal/integration/start_run_ui_test.go | 2 +- internal/integration/state_cli_test.go | 2 +- internal/integration/state_service_test.go | 2 +- internal/integration/tag_e2e_test.go | 2 +- internal/integration/tag_service_test.go | 2 +- internal/integration/team_cli_test.go | 2 +- internal/integration/team_service_test.go | 2 +- internal/integration/team_ui_test.go | 2 +- .../integration/terraform_cli_cancel_test.go | 2 +- .../integration/terraform_cli_discard_test.go | 2 +- internal/integration/terraform_login_test.go | 2 +- internal/integration/user_cli_test.go | 2 +- internal/integration/user_test.go | 2 +- internal/integration/user_token_test.go | 2 +- internal/integration/user_token_ui_test.go | 2 +- internal/integration/variable_e2e_test.go | 2 +- internal/integration/variable_test.go | 2 +- internal/integration/vcsprovider_test.go | 2 +- internal/integration/web_test.go | 2 +- internal/integration/webhook_test.go | 2 +- .../integration/working_directory_ui_test.go | 2 +- internal/integration/workspace_api_test.go | 2 +- internal/integration/workspace_cli_test.go | 2 +- .../workspace_permissions_service_test.go | 2 +- internal/integration/workspace_test.go | 2 +- internal/integration/workspace_ui_test.go | 2 +- .../integration/write_permission_e2e_test.go | 2 +- internal/repo/{db_test.go => db_test_.go} | 3 + internal/sql/db_test.go | 32 ------ internal/testcompose/testcompose.go | 63 +++++++++++ 71 files changed, 263 insertions(+), 210 deletions(-) create mode 100644 internal/integration/db_test.go create mode 100644 internal/integration/repo_db_test.go rename internal/repo/{db_test.go => db_test_.go} (88%) create mode 100644 internal/testcompose/testcompose.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1309fb6b..5a47af3c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,8 +43,6 @@ jobs: name: Tests env: GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} - OTF_TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5433/postgres?sslmode=disable - PUBSUB_EMULATOR_HOST: "localhost:8085" run: make test - name: Archive browser screenshots diff --git a/Makefile b/Makefile index a8e2f6d88..5c8eda234 100644 --- a/Makefile +++ b/Makefile @@ -75,11 +75,6 @@ compose-rm: postgres: docker compose up -d postgres -# Run squid via docker compose -.PHONY: squid -squid: - docker compose up -d squid - # Run staticcheck metalinter recursively against code .PHONY: lint lint: diff --git a/docker-compose.yml b/docker-compose.yml index 5149029ed..1753b364f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,19 +2,36 @@ services: postgres: image: postgres:14-alpine ports: - - "5433:5432" + - 5432 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 3 - command: -c fsync=off + # * setting high max_connections number avoids the error when running large number of + # integration tests in parallel: + # + # FATAL: sorry, too many clients already + # + # * disabling fsync dramatically improves integration test speed + # + # NOTE: neither setting is appropriate for a production deployment + command: -c fsync=off -c max_connections=999 environment: - POSTGRES_PASSWORD=postgres + # Terraform-based tests spawn `terraform`. These tests retrieve providers from + # the internet which can consume quite a lot of bandwidth and slow down the tests + # significantly. To cache these providers we use the squid caching + # proxy (http://www.squid-cache.org/). + # + # It is configured to use + # [SSL-bumping](https://wiki.squid-cache.org/Features/SslBump), which permits + # caching content transported via SSL (`terraform` retrieves providers only via + # SSL). squid: image: leg100/squid ports: - - "3128:3128" + - 3128 healthcheck: test: ["CMD-SHELL", "nc -zw1 localhost 3128"] interval: 5s @@ -27,7 +44,7 @@ services: pubsub: image: google/cloud-sdk:emulators ports: - - "8085:8085" + - 8085 stop_signal: SIGINT command: gcloud beta emulators pubsub start --project=abc123 --host-port=0.0.0.0:8085 otfd: @@ -38,14 +55,14 @@ services: squid: condition: service_healthy ports: - - "8833:8080" + - "8080:8080" healthcheck: test: ["CMD", "curl", "http://localhost:8080/healthz"] interval: 5s timeout: 5s retries: 3 environment: - - OTF_DATABASE=postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable + - OTF_DATABASE=postgres://postgres:postgres@postgres/postgres?sslmode=disable - OTF_SECRET=6b07b57377755b07cf61709780ee7484 - OTF_SITE_TOKEN=site-token - OTF_SSL=true diff --git a/docs/testing.md b/docs/testing.md index 8083ae05f..0ac7d6ab6 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -5,83 +5,28 @@ Change into the repo directory and run unit tests: ``` -go test ./... +go test -short ./... ``` ### Integration tests Integration tests require: -* PostgreSQL -* Terraform >= 1.2.0 +* docker compose +* terraform >= 1.2.0 * Chrome -Set the environment variable `OTF_TEST_DATABASE_URL` to a valid connection string. For example, if you have installed postgres on your local machine with the default database `postgres`: +The following runs integration tests: ``` -export OTF_TEST_DATABASE_URL=postgres:///postgres +go test ./internal/integration... ``` -Then run the both unit and integration tests: +The tests first stands up external services using docker compose: -``` -go test ./... -``` - -!!! note - Tests check for the presence of `OTF_TEST_DATABASE_URL`. If it absent then only unit tests are run; otherwise both unit and integration tests are run. - -#### Postgres too many connections error - -When running integration tests you may run into this error: - -``` -FATAL: sorry, too many clients already -``` - -The tests make potentially hundreds of connections to postgres at a time and you'll need to increase the maximum number of connections in postgres. Increase the `max_connections` parameter on your installation: - -``` -psql postgres:///otf -alter system set max_connections = 999; -``` - -Then reload/restart postgres. On Ubuntu, for example: - -``` -sudo systemctl reload postgres -``` - -!!! note - This is only recommended for a postgres installation dedicated to development and testing purposes. Too many connections in production most likely indicates a problem with the client application. - - -#### Postgres performance optimisation - -To further improve the speed of your integration tests turn off `fsync`: - -``` -psql postgres:///otf -alter system set fsync = off; -``` - -Then reload/restart postgres. On Ubuntu, for example: - -``` -sudo systemctl reload postgres -``` - -!!! note - This is only recommended for a postgres installation dedicated to development and testing purposes. Disabling `fsync` can lead to data loss. - -#### Database cleanup - -A dedicated logical database is created in postgres for each individual -integration test; as a result the above command creates 100s of databases. Upon -test completion the databases are removed. In certain situtations upon a test -failure the test may fail to remove a database, in which case you will have to -manually remove the database (although this is often not a concern other than -consuming a small amount of disk space). +* postgres (integration tests require a real database) +* squid cache (speeds up tests by caching terraform providers) +* GCP pub/sub emulator (necessary for the GCP pub/sub integration test) #### Disable headless mode @@ -98,32 +43,6 @@ export OTF_E2E_HEADLESS=false
Integration tests with headless mode disabled
-#### Cache provider requests - -Terraform-based tests spawn `terraform`. These tests retrieve providers from -the internet which can consume quite a lot of bandwidth and slow down the tests -significantly. To cache these providers it is recommended to use a caching -proxy. The following make task runs [squid](http://www.squid-cache.org/) in a -docker container: - -``` -make squid -``` - -It is configured to use -[SSL-bumping](https://wiki.squid-cache.org/Features/SslBump), which permits -caching content transported via SSL (`terraform` retrieves providers only via -SSL). - -You then need to instruct the tests to use the proxy: - -``` -export HTTPS_PROXY=localhost:3128 -``` - -You should now find the tests consume a lot less bandwidth and run several times -faster. - ### API tests Tests from the [go-tfe](https://github.com/hashicorp/go-tfe) project are routinely run to ensure OTF correctly implements the documented Terraform Cloud API. However, OTF only implements a subset of the API endpoints, and there are some differences (e.g. an OTF organization has no email address where as a TFC organization does). Therefore a [fork](https://github.com/leg100/go-tfe) of the go-tfe repo is maintained. @@ -136,9 +55,9 @@ make go-tfe-tests performs the following steps: -* Starts a docker compose stack of `otfd`, postgres, and [squid](#cache-provider-requests) +* Starts a docker compose stack of `otfd`, postgres, and squid * Runs a subset of `go-tfe` tests using the **forked** repo * Runs a subset of `go-tfe` tests using the **upstream** repo !!! note - You can instead manually invoke API tests using the scripts in `./hack`. The tests first require `otfd` to be running at `https://localhost:8833`, with a [site token](../config/flags/#-site-token) set to `site-token`. These settings can be overridden with the environment variables `TFE_ADDRESS` and `TFE_TOKEN`. + You can instead manually invoke API tests using the scripts in `./hack`. The tests first require `otfd` to be running at `https://localhost:8080`, with a [site token](../config/flags/#-site-token) set to `site-token`. These settings can be overridden with the environment variables `TFE_ADDRESS` and `TFE_TOKEN`. diff --git a/hack/go-tfe-tests.bash b/hack/go-tfe-tests.bash index a46258dfc..8771854d7 100755 --- a/hack/go-tfe-tests.bash +++ b/hack/go-tfe-tests.bash @@ -12,7 +12,7 @@ set -e GO_TFE_REPO="${GO_TFE_REPO:-github.com/leg100/go-tfe@otf}" TESTS="${@:-Test(Variables|Workspaces(Create|List|Update|Delete|Lock|Unlock|ForceUnlock|Read\$|ReadByID)|Organizations(Create|List|Read|Update)|StateVersion|Runs|Plans|Applies(Read|Logs)|ConfigurationVersions)}" -export TFE_ADDRESS="${TFE_ADDRESS:-https://localhost:8833}" +export TFE_ADDRESS="${TFE_ADDRESS:-https://localhost:8080}" # go-tfe tests perform privileged operations (e.g. creating organizations), so # we use a site admin token. # diff --git a/internal/integration/agent_token_ui_test.go b/internal/integration/agent_token_ui_test.go index 11f638795..c05ee0125 100644 --- a/internal/integration/agent_token_ui_test.go +++ b/internal/integration/agent_token_ui_test.go @@ -11,7 +11,7 @@ import ( // TestAgentTokenUI demonstrates managing agent tokens via the UI. func TestAgentTokenUI(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/auto_apply_ui_test.go b/internal/integration/auto_apply_ui_test.go index 059281b03..cb40ecf3e 100644 --- a/internal/integration/auto_apply_ui_test.go +++ b/internal/integration/auto_apply_ui_test.go @@ -10,7 +10,7 @@ import ( // TestAutoApply tests auto-apply functionality, using the UI to enable // auto-apply on a workspace first before invoking 'terraform apply'. func TestAutoApply(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/broker_test.go b/internal/integration/broker_test.go index 3601452de..5c6d8fa9b 100644 --- a/internal/integration/broker_test.go +++ b/internal/integration/broker_test.go @@ -11,7 +11,7 @@ import ( // TestBroker demonstrates publishing and subscribing of events via postgres. func TestBroker(t *testing.T) { - t.Parallel() + integrationTest(t) // simulate a cluster of two otfd nodes sharing a database cfg := config{ diff --git a/internal/integration/cloud_block_ui_test.go b/internal/integration/cloud_block_ui_test.go index 65ab7d522..f23d73a37 100644 --- a/internal/integration/cloud_block_ui_test.go +++ b/internal/integration/cloud_block_ui_test.go @@ -13,7 +13,7 @@ import ( // // https://developer.hashicorp.com/terraform/cli/cloud/settings#the-cloud-block func TestCloudBlock(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/cluster_test.go b/internal/integration/cluster_test.go index 84c14c5d5..2d92cbe05 100644 --- a/internal/integration/cluster_test.go +++ b/internal/integration/cluster_test.go @@ -21,7 +21,7 @@ import ( // processes successfully, e.g. relaying of logs from the agent through to the // TF CLI func TestCluster(t *testing.T) { - t.Parallel() + integrationTest(t) // start two daemons, one for user, one for agent, both sharing a db connstr := sql.NewTestDB(t) diff --git a/internal/integration/configuration_version_test.go b/internal/integration/configuration_version_test.go index 7df479744..2e1420b67 100644 --- a/internal/integration/configuration_version_test.go +++ b/internal/integration/configuration_version_test.go @@ -11,7 +11,7 @@ import ( ) func TestConfigurationVersion(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, _, ctx := setup(t, nil) diff --git a/internal/integration/connect_repo_e2e_test.go b/internal/integration/connect_repo_e2e_test.go index e31e8496b..1ccd48f4f 100644 --- a/internal/integration/connect_repo_e2e_test.go +++ b/internal/integration/connect_repo_e2e_test.go @@ -14,7 +14,7 @@ import ( // TestConnectRepoE2E demonstrates connecting a workspace to a VCS repository, pushing a // git commit which triggers a run on the workspace. func TestConnectRepoE2E(t *testing.T) { - t.Parallel() + integrationTest(t) // create an otf daemon with a fake github backend, serve up a repo and its // contents via tarball. And register a callback to test receipt of commit diff --git a/internal/integration/db_test.go b/internal/integration/db_test.go new file mode 100644 index 000000000..f72737a6f --- /dev/null +++ b/internal/integration/db_test.go @@ -0,0 +1,33 @@ +package integration + +import ( + "context" + "testing" + + "github.com/leg100/otf/internal/logr" + "github.com/leg100/otf/internal/sql" + "github.com/stretchr/testify/require" +) + +// TestWaitAndLock tests acquiring a connection from a pool, obtaining a session +// lock and then releasing lock and the connection, and it does this several +// times, to demonstrate that it is returning resources and not running into +// limits. +func TestWaitAndLock(t *testing.T) { + integrationTest(t) + + ctx := context.Background() + db, err := sql.New(ctx, sql.Options{ + Logger: logr.Discard(), + ConnString: sql.NewTestDB(t), + }) + require.NoError(t, err) + t.Cleanup(db.Close) + + for i := 0; i < 100; i++ { + func() { + err := db.WaitAndLock(ctx, 123, func() error { return nil }) + require.NoError(t, err) + }() + } +} diff --git a/internal/integration/events_test.go b/internal/integration/events_test.go index e1e689705..156ef0859 100644 --- a/internal/integration/events_test.go +++ b/internal/integration/events_test.go @@ -11,7 +11,7 @@ import ( // TestIntegration_Events demonstrates events are triggered and successfully // received by a subscriber. func TestIntegration_Events(t *testing.T) { - t.Parallel() + integrationTest(t) // disable the scheduler so that the run below doesn't get scheduled and // change state before we test for equality with the received event. diff --git a/internal/integration/github_login_test.go b/internal/integration/github_login_test.go index f4c2e51e1..05191c0ff 100644 --- a/internal/integration/github_login_test.go +++ b/internal/integration/github_login_test.go @@ -13,7 +13,7 @@ import ( // TestGithubLogin demonstrates logging into the UI via Github OAuth. func TestGithubLogin(t *testing.T) { - t.Parallel() + integrationTest(t) // Start daemon with a stub github server populated with a user. cfg := config{ diff --git a/internal/integration/github_pr_test.go b/internal/integration/github_pr_test.go index 72955ccba..44765724a 100644 --- a/internal/integration/github_pr_test.go +++ b/internal/integration/github_pr_test.go @@ -14,7 +14,7 @@ import ( // TestIntegration_GithubPR demonstrates the spawning of runs in response to // opening and updating a pull-request on github. func TestIntegration_GithubPR(t *testing.T) { - t.Parallel() + integrationTest(t) // create an otf daemon with a fake github backend, serve up a repo and its // contents via tarball. diff --git a/internal/integration/helpers_test.go b/internal/integration/helpers_test.go index e9f4b8726..857e810af 100644 --- a/internal/integration/helpers_test.go +++ b/internal/integration/helpers_test.go @@ -11,6 +11,17 @@ import ( "github.com/stretchr/testify/require" ) +func integrationTest(t *testing.T) { + // An integration test can take a while to run so it be run in parallel to + // other integration tests + t.Parallel() + + // Skip long-running integration tests if user has passed -short flag + if testing.Short() { + t.Skip() + } +} + func runURL(hostname, runID string) string { return "https://" + hostname + "/app/runs/" + runID } diff --git a/internal/integration/lock_file_test.go b/internal/integration/lock_file_test.go index 6a8415683..eeee921b9 100644 --- a/internal/integration/lock_file_test.go +++ b/internal/integration/lock_file_test.go @@ -15,7 +15,7 @@ import ( // has to then generates hashes for linux, etc. It is notorious for causing // difficulties for users and it's no different for OTF. func TestLockFile(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/logs_test.go b/internal/integration/logs_test.go index 4f180e636..1cbd313d2 100644 --- a/internal/integration/logs_test.go +++ b/internal/integration/logs_test.go @@ -12,7 +12,7 @@ import ( ) func TestLogs(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("upload chunk", func(t *testing.T) { svc, _, ctx := setup(t, nil) @@ -123,7 +123,7 @@ func TestLogs(t *testing.T) { // TestClusterLogs tests the relaying of logs across a cluster of otfd nodes. func TestClusterLogs(t *testing.T) { - t.Parallel() + integrationTest(t) // simulate a cluster of two otfd nodes connstr := sql.NewTestDB(t) diff --git a/internal/integration/main_test.go b/internal/integration/main_test.go index 0b7de9182..d0d105fc3 100644 --- a/internal/integration/main_test.go +++ b/internal/integration/main_test.go @@ -13,6 +13,7 @@ import ( "github.com/leg100/otf/internal" "github.com/leg100/otf/internal/auth" "github.com/leg100/otf/internal/testbrowser" + "github.com/leg100/otf/internal/testcompose" ) var ( @@ -45,6 +46,45 @@ func doMain(m *testing.M) (int, error) { } } + // Start external services + if err := testcompose.Up(); err != nil { + return 0, fmt.Errorf("starting external services: %w", err) + } + + // get postgres host and set environment variable + host, err := testcompose.GetHost(testcompose.Postgres) + if err != nil { + return 0, fmt.Errorf("getting postgres host: %w", err) + } + postgresURL := fmt.Sprintf("postgres://postgres:postgres@%s/postgres", host) + unset, err := setenv("OTF_TEST_DATABASE_URL", postgresURL) + if err != nil { + return 0, err + } + defer unset() + + // get squid host and set environment variable + squid, err := testcompose.GetHost(testcompose.Squid) + if err != nil { + return 0, fmt.Errorf("getting squid host: %w", err) + } + unset, err = setenv("HTTPS_PROXY", squid) + if err != nil { + return 0, err + } + defer unset() + + // get pubsub emulator host and set environment variable + pubsub, err := testcompose.GetHost(testcompose.PubSub) + if err != nil { + return 0, fmt.Errorf("getting squid host: %w", err) + } + unset, err = setenv("PUBSUB_EMULATOR_HOST", pubsub) + if err != nil { + return 0, err + } + defer unset() + // The otfd daemon spawned in an integration test uses a self-signed cert. // The following environment variable instructs any Go program spawned in a // test, e.g. the terraform CLI, the otf agent, etc, to trust the @@ -55,7 +95,7 @@ func doMain(m *testing.M) (int, error) { if err != nil { return 0, fmt.Errorf("retrieving working directory: %w", err) } - unset, err := setenv("SSL_CERT_FILE", filepath.Join(wd, "./fixtures/cert.pem")) + unset, err = setenv("SSL_CERT_FILE", filepath.Join(wd, "./fixtures/cert.pem")) if err != nil { return 0, err } @@ -76,14 +116,6 @@ func doMain(m *testing.M) (int, error) { } defer unset() - // If HTTPS_PROXY has been defined then add it to the authoritative list of - // environment variables so that processes, particularly terraform, spawed - // in tests use the proxy. This can be very useful for caching repeated - // downloads of terraform providers during tests. - if proxy, ok := os.LookupEnv("HTTPS_PROXY"); ok { - envs = append(envs, "HTTPS_PROXY="+proxy) - } - // Instruct terraform CLI to skip checks for new versions. unset, err = setenv("CHECKPOINT_DISABLE", "true") if err != nil { diff --git a/internal/integration/module_e2e_test.go b/internal/integration/module_e2e_test.go index a35f242ed..9fec3dcab 100644 --- a/internal/integration/module_e2e_test.go +++ b/internal/integration/module_e2e_test.go @@ -16,7 +16,7 @@ import ( // TestModuleE2E tests publishing a module, first via the UI and then via a webhook // event, and then invokes a terraform run that sources the module. func TestModuleE2E(t *testing.T) { - t.Parallel() + integrationTest(t) // create an otf daemon with a fake github backend, ready to serve up a repo // and its contents via tarball. diff --git a/internal/integration/module_test.go b/internal/integration/module_test.go index 3c7653ecd..ac219e45b 100644 --- a/internal/integration/module_test.go +++ b/internal/integration/module_test.go @@ -11,7 +11,7 @@ import ( ) func TestModule(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, org, ctx := setup(t, nil) diff --git a/internal/integration/notification_configuration_test.go b/internal/integration/notification_configuration_test.go index 181869fd3..77f0105cf 100644 --- a/internal/integration/notification_configuration_test.go +++ b/internal/integration/notification_configuration_test.go @@ -13,7 +13,7 @@ import ( ) func TestIntegration_NotificationConfigurationService(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { daemon, org, ctx := setup(t, nil) diff --git a/internal/integration/notification_gcppubsub_test.go b/internal/integration/notification_gcppubsub_test.go index fc69f06ac..9de33ea4f 100644 --- a/internal/integration/notification_gcppubsub_test.go +++ b/internal/integration/notification_gcppubsub_test.go @@ -20,7 +20,7 @@ import ( func TestIntegration_NotificationGCPPubSub(t *testing.T) { testutils.SkipIfEnvUnspecified(t, "PUBSUB_EMULATOR_HOST") - t.Parallel() + integrationTest(t) ctx := context.Background() client, err := pubsub.NewClient(ctx, "abc123") diff --git a/internal/integration/notification_slack_test.go b/internal/integration/notification_slack_test.go index eadd05965..39b330dd0 100644 --- a/internal/integration/notification_slack_test.go +++ b/internal/integration/notification_slack_test.go @@ -15,7 +15,7 @@ import ( // TestIntegration_NotificationSlack demonstrates run events triggering the // sending of notifications to a slack channel. func TestIntegration_NotificationSlack(t *testing.T) { - t.Parallel() + integrationTest(t) url, got := newSlackServer(t) daemon, _, ctx := setup(t, nil) diff --git a/internal/integration/oidc_test.go b/internal/integration/oidc_test.go index 245cabe1a..6f538d413 100644 --- a/internal/integration/oidc_test.go +++ b/internal/integration/oidc_test.go @@ -11,7 +11,7 @@ import ( // TestIntegration_OIDC demonstrates logging in using OIDC func TestIntegration_OIDC(t *testing.T) { - t.Parallel() + integrationTest(t) // Start daemon with a stub github server populated with a user. cfg := config{ diff --git a/internal/integration/organization_cli_test.go b/internal/integration/organization_cli_test.go index defd01496..049ed3c6a 100644 --- a/internal/integration/organization_cli_test.go +++ b/internal/integration/organization_cli_test.go @@ -8,7 +8,7 @@ import ( // TestOrganizationCLI tests managing organizations via the CLI func TestOrganizationCLI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, ctx := setup(t, nil) diff --git a/internal/integration/organization_min_permissions_test.go b/internal/integration/organization_min_permissions_test.go index 1bab4a204..6f259ff9b 100644 --- a/internal/integration/organization_min_permissions_test.go +++ b/internal/integration/organization_min_permissions_test.go @@ -13,7 +13,7 @@ import ( // a role on a workspace in the organization - that they receive a minimum set // of permissions across the organization. func TestIntegration_MinimumPermissions(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) ws := svc.createWorkspace(t, ctx, org) diff --git a/internal/integration/organization_test.go b/internal/integration/organization_test.go index 91464c7c0..7f9c1c608 100644 --- a/internal/integration/organization_test.go +++ b/internal/integration/organization_test.go @@ -13,7 +13,7 @@ import ( ) func TestOrganization(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, defaultOrg, ctx := setup(t, nil) diff --git a/internal/integration/plan_permission_e2e_test.go b/internal/integration/plan_permission_e2e_test.go index 03643c6ae..6179871d8 100644 --- a/internal/integration/plan_permission_e2e_test.go +++ b/internal/integration/plan_permission_e2e_test.go @@ -13,7 +13,7 @@ import ( // 'plan' role to a team and what they can and cannot do with that role via the // CLI and via the UI. func TestIntegration_PlanPermission(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/repo_db_test.go b/internal/integration/repo_db_test.go new file mode 100644 index 000000000..53a009b63 --- /dev/null +++ b/internal/integration/repo_db_test.go @@ -0,0 +1,16 @@ +package integration + +import ( + "testing" + + "github.com/leg100/otf/internal/repo" +) + +// TestRepoDB tests repo package's interaction with a (real) database. +func TestRepoDB(t *testing.T) { + integrationTest(t) + + // Call out to integration test situated within 'repo' package, which then + // calls private methods + repo.TestDB(t) +} diff --git a/internal/integration/repo_test.go b/internal/integration/repo_test.go index b465d002b..9f48b0c8c 100644 --- a/internal/integration/repo_test.go +++ b/internal/integration/repo_test.go @@ -9,7 +9,7 @@ import ( ) func TestRepo(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create multiple connections", func(t *testing.T) { svc, org, ctx := setup(t, nil, github.WithRepo("test/dummy")) diff --git a/internal/integration/retry_run_ui_test.go b/internal/integration/retry_run_ui_test.go index af9402dae..5fe918848 100644 --- a/internal/integration/retry_run_ui_test.go +++ b/internal/integration/retry_run_ui_test.go @@ -11,7 +11,7 @@ import ( // TestIntegration_RetryRunUI demonstrates retrying a run via the UI func TestIntegration_RetryRunUI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, ctx := setup(t, nil) ws := daemon.createWorkspace(t, ctx, nil) diff --git a/internal/integration/run_api_test.go b/internal/integration/run_api_test.go index c7ba95633..0b2db93c4 100644 --- a/internal/integration/run_api_test.go +++ b/internal/integration/run_api_test.go @@ -18,7 +18,7 @@ import ( // by the `go-tfe` integration tests, i.e. behaviours that are specific to // OTF. func TestIntegration_RunAPI(t *testing.T) { - t.Parallel() + integrationTest(t) // setup daemon along with fake github repo repo := cloud.NewTestRepo() diff --git a/internal/integration/run_complete_test.go b/internal/integration/run_complete_test.go index ea007aa1f..79d03e9ff 100644 --- a/internal/integration/run_complete_test.go +++ b/internal/integration/run_complete_test.go @@ -11,7 +11,7 @@ import ( // TestCompleteRun tests a terraform run from start to finish. func TestCompleteRun(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, ctx := setup(t, nil) diff --git a/internal/integration/run_list_ui_test.go b/internal/integration/run_list_ui_test.go index 5908ec3bf..73277ef57 100644 --- a/internal/integration/run_list_ui_test.go +++ b/internal/integration/run_list_ui_test.go @@ -11,7 +11,7 @@ import ( // TestIntegration_RunListUI demonstrates listing runs via the UI. func TestIntegration_RunListUI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, ctx := setup(t, nil) ws := daemon.createWorkspace(t, ctx, nil) @@ -21,10 +21,8 @@ func TestIntegration_RunListUI(t *testing.T) { browser.Run(t, ctx, chromedp.Tasks{ // navigate to workspace page chromedp.Navigate(workspaceURL(daemon.Hostname(), ws.Organization, ws.Name)), - chromedp.WaitReady(`body`), // navigate to runs page chromedp.Click(`//a[text()='runs']`, chromedp.NodeVisible), - chromedp.WaitReady(`body`), // should be no runs listed matchText(t, `//div[@id='content-list']`, `No items currently exist.`), chromedp.ActionFunc(func(context.Context) error { @@ -34,7 +32,7 @@ func TestIntegration_RunListUI(t *testing.T) { return nil }), // should be one run listed - chromedp.Nodes(`//div[@id='content-list']//*[@class='item']`, &runListingAfter), + chromedp.Nodes(`//div[@id='content-list']//*[@class='item']`, &runListingAfter, chromedp.NodeVisible), // and its status should be 'planned and finished' chromedp.WaitVisible(`//*[@class='item']//*[@class='status status-planned_and_finished']`), }) diff --git a/internal/integration/run_test.go b/internal/integration/run_test.go index 5fbbc0426..bf5452b34 100644 --- a/internal/integration/run_test.go +++ b/internal/integration/run_test.go @@ -16,7 +16,7 @@ import ( ) func TestRun(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, _, ctx := setup(t, &config{Config: daemon.Config{DisableScheduler: true}}) diff --git a/internal/integration/run_token_test.go b/internal/integration/run_token_test.go index 86f77a9a2..5b674756f 100644 --- a/internal/integration/run_token_test.go +++ b/internal/integration/run_token_test.go @@ -9,7 +9,7 @@ import ( ) func TestRunToken(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, org, ctx := setup(t, nil) diff --git a/internal/integration/sandbox_test.go b/internal/integration/sandbox_test.go index 71f6efdf5..7a228f210 100644 --- a/internal/integration/sandbox_test.go +++ b/internal/integration/sandbox_test.go @@ -14,7 +14,7 @@ import ( // TestSandbox demonstrates the sandbox feature, whereby terraform apply is run // within an isolated environment. func TestSandbox(t *testing.T) { - t.Parallel() + integrationTest(t) _, err := exec.LookPath("bwrap") if errors.Is(err, exec.ErrNotFound) { diff --git a/internal/integration/session_test.go b/internal/integration/session_test.go index 636d39504..718a0a0e3 100644 --- a/internal/integration/session_test.go +++ b/internal/integration/session_test.go @@ -11,7 +11,7 @@ import ( ) func TestSession(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("start", func(t *testing.T) { svc, _, ctx := setup(t, nil) diff --git a/internal/integration/site_admin_ui_test.go b/internal/integration/site_admin_ui_test.go index 63f45f6c4..b59a77ca1 100644 --- a/internal/integration/site_admin_ui_test.go +++ b/internal/integration/site_admin_ui_test.go @@ -12,7 +12,7 @@ import ( // TestSiteAdminUI demonstrates signing into the web app as a site admin, using // their super powers to create and delete an organization. func TestSiteAdminUI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, _ := setup(t, &config{Config: daemon.Config{ SiteToken: "abc123", diff --git a/internal/integration/start_run_ui_test.go b/internal/integration/start_run_ui_test.go index 7e3c1cf04..ae3d653cc 100644 --- a/internal/integration/start_run_ui_test.go +++ b/internal/integration/start_run_ui_test.go @@ -10,7 +10,7 @@ import ( // TestStartRunUI tests starting a run via the Web UI before confirming and // applying the run. func TestStartRunUI(t *testing.T) { - t.Parallel() + integrationTest(t) svc, _, ctx := setup(t, nil) diff --git a/internal/integration/state_cli_test.go b/internal/integration/state_cli_test.go index a6215e27d..821e5e76d 100644 --- a/internal/integration/state_cli_test.go +++ b/internal/integration/state_cli_test.go @@ -12,7 +12,7 @@ import ( // TestIntegration_StateCLI demonstrates managing state via the CLI func TestIntegration_StateCLI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, ctx := setup(t, nil) diff --git a/internal/integration/state_service_test.go b/internal/integration/state_service_test.go index dd74e2e5f..bc1c0a34b 100644 --- a/internal/integration/state_service_test.go +++ b/internal/integration/state_service_test.go @@ -11,7 +11,7 @@ import ( ) func TestIntegration_StateService(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, _, ctx := setup(t, nil) diff --git a/internal/integration/tag_e2e_test.go b/internal/integration/tag_e2e_test.go index 7212842b7..b159015eb 100644 --- a/internal/integration/tag_e2e_test.go +++ b/internal/integration/tag_e2e_test.go @@ -16,7 +16,7 @@ import ( // TestIntegration_TagsE2E demonstrates end-to-end usage of workspace tags. func TestIntegration_TagsE2E(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, org, ctx := setup(t, nil) diff --git a/internal/integration/tag_service_test.go b/internal/integration/tag_service_test.go index b1978ccf5..d25349d86 100644 --- a/internal/integration/tag_service_test.go +++ b/internal/integration/tag_service_test.go @@ -9,7 +9,7 @@ import ( ) func TestIntegration_TagService(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("add tags to workspace", func(t *testing.T) { svc, org, ctx := setup(t, nil) diff --git a/internal/integration/team_cli_test.go b/internal/integration/team_cli_test.go index e0ca9009f..bc9acce08 100644 --- a/internal/integration/team_cli_test.go +++ b/internal/integration/team_cli_test.go @@ -8,7 +8,7 @@ import ( // TestTeamCLI tests managing teams via the CLI func TestTeamCLI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, ctx := setup(t, nil) diff --git a/internal/integration/team_service_test.go b/internal/integration/team_service_test.go index 85a98ba1c..a48f641f3 100644 --- a/internal/integration/team_service_test.go +++ b/internal/integration/team_service_test.go @@ -11,7 +11,7 @@ import ( ) func TestIntegation_TeamService(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, org, ctx := setup(t, nil) diff --git a/internal/integration/team_ui_test.go b/internal/integration/team_ui_test.go index cb7151e13..37cb4363d 100644 --- a/internal/integration/team_ui_test.go +++ b/internal/integration/team_ui_test.go @@ -10,7 +10,7 @@ import ( // TestIntegration_TeamUI demonstrates managing teams and team members via the // UI. func TestIntegration_TeamUI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, org, ctx := setup(t, nil) newbie := daemon.createUser(t) diff --git a/internal/integration/terraform_cli_cancel_test.go b/internal/integration/terraform_cli_cancel_test.go index c96272ed7..926a6afac 100644 --- a/internal/integration/terraform_cli_cancel_test.go +++ b/internal/integration/terraform_cli_cancel_test.go @@ -17,7 +17,7 @@ import ( // TestIntegration_TerraformCLICancel demonstrates a user canceling a run via // the terraform CLI. func TestIntegration_TerraformCLICancel(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/terraform_cli_discard_test.go b/internal/integration/terraform_cli_discard_test.go index be178dbd1..26619546b 100644 --- a/internal/integration/terraform_cli_discard_test.go +++ b/internal/integration/terraform_cli_discard_test.go @@ -16,7 +16,7 @@ import ( // TestIntegration_TerraformCLIDiscard demonstrates a user discarding a run via // the terraform CLI. func TestIntegration_TerraformCLIDiscard(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/terraform_login_test.go b/internal/integration/terraform_login_test.go index bdd006269..2fe3701f5 100644 --- a/internal/integration/terraform_login_test.go +++ b/internal/integration/terraform_login_test.go @@ -19,7 +19,7 @@ import ( // TestTerraformLogin demonstrates using `terraform login` to retrieve // credentials. func TestTerraformLogin(t *testing.T) { - t.Parallel() + integrationTest(t) svc, _, ctx := setup(t, nil) diff --git a/internal/integration/user_cli_test.go b/internal/integration/user_cli_test.go index ed7af84f5..60d7459cf 100644 --- a/internal/integration/user_cli_test.go +++ b/internal/integration/user_cli_test.go @@ -8,7 +8,7 @@ import ( // TestUserCLI tests managing user accounts via the CLI func TestUserCLI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, _ := setup(t, nil) diff --git a/internal/integration/user_test.go b/internal/integration/user_test.go index 6b9b536d5..ed531adc6 100644 --- a/internal/integration/user_test.go +++ b/internal/integration/user_test.go @@ -12,7 +12,7 @@ import ( ) func TestUser(t *testing.T) { - t.Parallel() + integrationTest(t) // Tests the --site-admins functionality, promoting a number of users to the // role of site admin when starting the daemon. diff --git a/internal/integration/user_token_test.go b/internal/integration/user_token_test.go index 29dc95b35..a5149b2f6 100644 --- a/internal/integration/user_token_test.go +++ b/internal/integration/user_token_test.go @@ -12,7 +12,7 @@ import ( ) func TestUserToken(t *testing.T) { - t.Parallel() + integrationTest(t) // perform all actions as superuser ctx := internal.AddSubjectToContext(context.Background(), &auth.SiteAdmin) diff --git a/internal/integration/user_token_ui_test.go b/internal/integration/user_token_ui_test.go index d7cee5477..f49d44c27 100644 --- a/internal/integration/user_token_ui_test.go +++ b/internal/integration/user_token_ui_test.go @@ -9,7 +9,7 @@ import ( // TestIntegration_UserTokenUI demonstrates managing user tokens via the UI. func TestIntegration_UserTokenUI(t *testing.T) { - t.Parallel() + integrationTest(t) svc, _, ctx := setup(t, nil) browser.Run(t, ctx, chromedp.Tasks{ diff --git a/internal/integration/variable_e2e_test.go b/internal/integration/variable_e2e_test.go index 8b9da236d..fa35d924a 100644 --- a/internal/integration/variable_e2e_test.go +++ b/internal/integration/variable_e2e_test.go @@ -13,7 +13,7 @@ import ( // TestVariableE2E tests adding, updating and deleting workspace variables via the // UI, and tests that variables are made available to runs. func TestVariableE2E(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/variable_test.go b/internal/integration/variable_test.go index 43a5df41e..f48b26b3f 100644 --- a/internal/integration/variable_test.go +++ b/internal/integration/variable_test.go @@ -10,7 +10,7 @@ import ( ) func TestVariable(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, _, ctx := setup(t, nil) diff --git a/internal/integration/vcsprovider_test.go b/internal/integration/vcsprovider_test.go index 48ac8284a..5eca42d20 100644 --- a/internal/integration/vcsprovider_test.go +++ b/internal/integration/vcsprovider_test.go @@ -10,7 +10,7 @@ import ( ) func TestVCSProvider(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { svc, org, ctx := setup(t, nil) diff --git a/internal/integration/web_test.go b/internal/integration/web_test.go index c6731d346..998bfe999 100644 --- a/internal/integration/web_test.go +++ b/internal/integration/web_test.go @@ -11,7 +11,7 @@ import ( // TestWeb is a random walkthrough of the Web UI func TestWeb(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, org, ctx := setup(t, nil) user := userFromContext(t, ctx) diff --git a/internal/integration/webhook_test.go b/internal/integration/webhook_test.go index 25d9d5dc1..8c59f2e23 100644 --- a/internal/integration/webhook_test.go +++ b/internal/integration/webhook_test.go @@ -19,7 +19,7 @@ import ( // triggering workspace runs and publishing module versions in tested in other // E2E tests. func TestWebhook(t *testing.T) { - t.Parallel() + integrationTest(t) repo := cloud.NewTestRepo() diff --git a/internal/integration/working_directory_ui_test.go b/internal/integration/working_directory_ui_test.go index d5ac62cea..654a44a5b 100644 --- a/internal/integration/working_directory_ui_test.go +++ b/internal/integration/working_directory_ui_test.go @@ -14,7 +14,7 @@ import ( // WorkingDirectory tests setting a working directory on a workspace and checks // that terraform runs use configuration from that directory. func TestWorkingDirectory(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, org, ctx := setup(t, nil) diff --git a/internal/integration/workspace_api_test.go b/internal/integration/workspace_api_test.go index 3e2a7fbd9..b8ac7cc28 100644 --- a/internal/integration/workspace_api_test.go +++ b/internal/integration/workspace_api_test.go @@ -16,7 +16,7 @@ import ( // TestIntegration_WorkspaceAPI tests the option to retrieve latest state // outputs alongside a workspace from the API. func TestIntegration_WorkspaceAPI_IncludeOutputs(t *testing.T) { - t.Parallel() + integrationTest(t) svc, _, ctx := setup(t, nil) sv := svc.createStateVersion(t, ctx, nil) diff --git a/internal/integration/workspace_cli_test.go b/internal/integration/workspace_cli_test.go index 0fe8557b4..5e123784a 100644 --- a/internal/integration/workspace_cli_test.go +++ b/internal/integration/workspace_cli_test.go @@ -11,7 +11,7 @@ import ( // TestIntegration_WorkspaceCLI tests managing workspaces via the otf CLI func TestIntegration_WorkspaceCLI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, _, ctx := setup(t, nil) diff --git a/internal/integration/workspace_permissions_service_test.go b/internal/integration/workspace_permissions_service_test.go index 44347bbc6..cb6ea8202 100644 --- a/internal/integration/workspace_permissions_service_test.go +++ b/internal/integration/workspace_permissions_service_test.go @@ -11,7 +11,7 @@ import ( ) func TestIntegration_WorkspacePermissionsService(t *testing.T) { - t.Parallel() + integrationTest(t) svc, org, ctx := setup(t, nil) diff --git a/internal/integration/workspace_test.go b/internal/integration/workspace_test.go index 75235b58e..bacd39672 100644 --- a/internal/integration/workspace_test.go +++ b/internal/integration/workspace_test.go @@ -16,7 +16,7 @@ import ( ) func TestWorkspace(t *testing.T) { - t.Parallel() + integrationTest(t) t.Run("create", func(t *testing.T) { daemon, org, ctx := setup(t, nil) diff --git a/internal/integration/workspace_ui_test.go b/internal/integration/workspace_ui_test.go index 87bcd2ffc..222bcb068 100644 --- a/internal/integration/workspace_ui_test.go +++ b/internal/integration/workspace_ui_test.go @@ -14,7 +14,7 @@ import ( // TestIntegration_WorkspaceUI demonstrates management of workspaces via the UI. func TestIntegration_WorkspaceUI(t *testing.T) { - t.Parallel() + integrationTest(t) daemon, org, ctx := setup(t, nil) diff --git a/internal/integration/write_permission_e2e_test.go b/internal/integration/write_permission_e2e_test.go index fa8574495..9295e0722 100644 --- a/internal/integration/write_permission_e2e_test.go +++ b/internal/integration/write_permission_e2e_test.go @@ -11,7 +11,7 @@ import ( // TestWritePermissionE2E demonstrates a user with write permissions on a workspace interacting // with the workspace via the terraform CLI. func TestWritePermissionE2E(t *testing.T) { - t.Parallel() + integrationTest(t) // Create user and org, and user becomes owner of the org svc, org, ctx := setup(t, nil) diff --git a/internal/repo/db_test.go b/internal/repo/db_test_.go similarity index 88% rename from internal/repo/db_test.go rename to internal/repo/db_test_.go index b0f76ed12..a9f051c94 100644 --- a/internal/repo/db_test.go +++ b/internal/repo/db_test_.go @@ -9,6 +9,9 @@ import ( "github.com/stretchr/testify/require" ) +// TestDB should not be called directly but via the 'integration' package (Hence +// why this file is suffixed with '_' to prevent Go from detecting it as a test +// file). func TestDB(t *testing.T) { ctx := context.Background() db := newTestDB(t) diff --git a/internal/sql/db_test.go b/internal/sql/db_test.go index 2e3600b6c..337c4538a 100644 --- a/internal/sql/db_test.go +++ b/internal/sql/db_test.go @@ -1,11 +1,8 @@ package sql import ( - "context" - "net/url" "testing" - "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -29,32 +26,3 @@ func TestSetDefaultMaxConnections(t *testing.T) { }) } } - -// TestWaitAndLock tests acquiring a connection from a pool, obtaining a session -// lock and then releasing lock and the connection, and it does this several -// times, to demonstrate that it is returning resources and not running into -// limits. -func TestWaitAndLock(t *testing.T) { - ctx := context.Background() - db, err := New(ctx, Options{ - Logger: logr.Discard(), - ConnString: NewTestDB(t), - }) - require.NoError(t, err) - t.Cleanup(db.Close) - - for i := 0; i < 100; i++ { - func() { - err := db.WaitAndLock(ctx, 123, func() error { return nil }) - require.NoError(t, err) - }() - } -} - -func TestConnStringParse(t *testing.T) { - got := "postgres:///" - u, err := url.Parse(got) - u.Path = "/mydb" - require.NoError(t, err) - assert.Equal(t, "postgres:///mydb", u.String()) -} diff --git a/internal/testcompose/testcompose.go b/internal/testcompose/testcompose.go new file mode 100644 index 000000000..868682614 --- /dev/null +++ b/internal/testcompose/testcompose.go @@ -0,0 +1,63 @@ +// Package testcompose provides interaction with a docker compose stack of +// services for testing purposes. +package testcompose + +import ( + "bytes" + "fmt" + "os/exec" + "strconv" + "strings" +) + +const ( + Postgres Service = "postgres" + Squid Service = "squid" + PubSub Service = "pubsub" +) + +type Service string + +var ports = map[Service]int{ + Postgres: 5432, + Squid: 3128, + PubSub: 8085, +} + +func Up() error { + // --wait implies -d, which detaches the containers + args := []string{"compose", "-p", "otf", "up", "--wait", "--wait-timeout", "60"} + args = append(args, string(Postgres), string(Squid), string(PubSub)) + cmd := exec.Command("docker", args...) + + var buferr bytes.Buffer + cmd.Stderr = &buferr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("%v failed: %s: %w", args, buferr.String(), err) + } + return nil +} + +func GetHost(svc Service) (string, error) { + port, ok := ports[svc] + if !ok { + return "", fmt.Errorf("service not found: %s", svc) + } + args := []string{"docker", "compose", "port", string(svc), strconv.Itoa(port)} + cmd := exec.Command(args[0], args[1:]...) + + var bufout, buferr bytes.Buffer + cmd.Stdout = &bufout + cmd.Stderr = &buferr + + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("getting port info for %s: %s: %w", svc, buferr.String(), err) + } + + parts := strings.Split(strings.TrimSpace(bufout.String()), ":") + if len(parts) != 2 { + return "", fmt.Errorf("unexpected output from %v: %v", args, parts) + } + return fmt.Sprintf("localhost:%s", parts[1]), nil +}