diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f59ef1ced..d9ba71a936 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,6 +114,24 @@ TF_ACC=1 go test ./internal/initwd Because the acceptance tests depend on services outside of the OpenTofu codebase, and because the acceptance tests are usually used only when making changes to the systems they cover, it is common and expected that drift in those external systems will cause test failures. Because of this, prior to working on a system covered by acceptance tests it's important to run the existing tests for that system in an *unchanged* work tree first and respond to any test failures that preexist, to avoid misinterpreting such failures as bugs in your new changes. +### Integration Tests: Testing interactions with external backends + +OpenTofu supports various [backends](https://opentofu.org/docs/language/settings/backends/configuration). We run integration test against them to ensure no side effects when using OpenTofu. + +Execute to list all available commands to run tests: + +```commandline +make list-integration-tests +``` + +From the list of output commands, you can execute those which involve backends you intend to test against. + +For example, execute the command to run integration tests with s3 backend: + +```commandline +make test-s3 +``` + ## Generated Code Some files in the OpenTofu CLI codebase are generated. In most cases, we update these using `go generate`, which is the standard way to encapsulate code generation steps in a Go codebase. diff --git a/Makefile b/Makefile index e01090cd79..cbd48c2636 100644 --- a/Makefile +++ b/Makefile @@ -62,3 +62,62 @@ bin/licensei-${LICENSEI_VERSION}: # commands during the build process create temporary files that collide # under parallel conditions. .NOTPARALLEL: + +# Integration tests +# + +.PHONY: list-integration-tests +list-integration-tests: ## Lists tests. + @ grep -h -E '^(test|integration)-.+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[1m%-30s\033[0m %s\n", $$1, $$2}' + +# integration test with s3 as backend +.PHONY: test-s3 + +define infoTestS3 +Test requires: +* AWS Credentials to be configured + - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html + - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html +* IAM Permissions in us-west-2 + - S3 CRUD operations on buckets which will follow the pattern tofu-test-* + - DynamoDB CRUD operations on a Table named dynamoTable + +endef + +test-s3: ## Runs tests with s3 bucket as the backend. + @ $(info $(infoTestS3)) + @ TF_S3_TEST=1 go test ./internal/backend/remote-state/s3/... + +# integration test with postgres as backend +.PHONY: test-pg test-pg-clean + +PG_PORT := 5432 + +define infoTestPg +Test requires: +* Docker: https://docs.docker.com/engine/install/ +* Port: $(PG_PORT) + +endef + +test-pg: ## Runs tests with local Postgres instance as the backend. + @ $(info $(infoTestPg)) + @ echo "Starting database" + @ make test-pg-clean + @ docker run --rm -d --name tofu-pg \ + -p $(PG_PORT):5432 \ + -e POSTGRES_PASSWORD=tofu \ + -e POSTGRES_USER=tofu \ + postgres:16-alpine3.17 1> /dev/null + @ docker exec tofu-pg /bin/bash -c 'until psql -U tofu -c "\q" 2> /dev/null; do echo "Database is getting ready, waiting"; sleep 1; done' + @ DATABASE_URL="postgres://tofu:tofu@localhost:$(PG_PORT)/tofu?sslmode=disable" \ + TF_PG_TEST=1 go test ./internal/backend/remote-state/pg/... + +test-pg-clean: ## Cleans environment after `test-pg`. + @ docker rm -f tofu-pg 2> /dev/null + +.PHONY: +integration-tests: test-s3 test-pg integration-tests-clean ## Runs all integration tests test. + +.PHONY: +integration-tests-clean: test-pg-clean ## Cleans environment after all integration tests. diff --git a/internal/backend/remote-state/pg/backend_test.go b/internal/backend/remote-state/pg/backend_test.go index 15996b8056..a383538310 100644 --- a/internal/backend/remote-state/pg/backend_test.go +++ b/internal/backend/remote-state/pg/backend_test.go @@ -26,22 +26,22 @@ import ( // // A running Postgres server identified by env variable // DATABASE_URL is required for acceptance tests. -func testACC(t *testing.T) string { - skip := os.Getenv("TF_ACC") == "" +func testACC(t *testing.T) (connectionURI *url.URL) { + skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_PG_TEST") == "" if skip { - t.Log("pg backend tests require setting TF_ACC") + t.Log("pg backend tests requires setting TF_ACC or TF_PG_TEST") t.Skip() } databaseUrl, found := os.LookupEnv("DATABASE_URL") if !found { - databaseUrl = "postgres://localhost/terraform_backend_pg_test?sslmode=disable" - os.Setenv("DATABASE_URL", databaseUrl) + t.Fatal("pg backend tests require setting DATABASE_URL") } + u, err := url.Parse(databaseUrl) if err != nil { t.Fatal(err) } - return u.Path[1:] + return u } func TestBackend_impl(t *testing.T) { @@ -49,8 +49,15 @@ func TestBackend_impl(t *testing.T) { } func TestBackendConfig(t *testing.T) { - databaseName := testACC(t) - connStr := getDatabaseUrl() + connectionURI := testACC(t) + connStr := os.Getenv("DATABASE_URL") + + user := connectionURI.User.Username() + password, _ := connectionURI.User.Password() + databaseName := connectionURI.Path[1:] + + connectionURIObfuscated := connectionURI + connectionURIObfuscated.User = nil testCases := []struct { Name string @@ -71,6 +78,8 @@ func TestBackendConfig(t *testing.T) { EnvVars: map[string]string{ "PGSSLMODE": "disable", "PGDATABASE": databaseName, + "PGUSER": user, + "PGPASSWORD": password, }, Config: map[string]interface{}{ "schema_name": fmt.Sprintf("terraform_%s", t.Name()), @@ -92,10 +101,10 @@ func TestBackendConfig(t *testing.T) { "PGPASSWORD": "badpassword", }, Config: map[string]interface{}{ - "conn_str": connStr, + "conn_str": connectionURIObfuscated.String(), "schema_name": fmt.Sprintf("terraform_%s", t.Name()), }, - ExpectConnectionError: `role "baduser" does not exist`, + ExpectConnectionError: `authentication failed for user "baduser"`, }, { Name: "host-in-env-vars", @@ -117,6 +126,7 @@ func TestBackendConfig(t *testing.T) { "PGDATABASE": databaseName, }, Config: map[string]interface{}{ + "conn_str": connStr, "schema_name": fmt.Sprintf("terraform_%s", t.Name()), }, }, diff --git a/scripts/test/state_backends/s3.sh b/scripts/test/state_backends/s3.sh deleted file mode 100755 index dbe1a5735a..0000000000 --- a/scripts/test/state_backends/s3.sh +++ /dev/null @@ -1,13 +0,0 @@ -cat <