diff --git a/README.md b/README.md index 6eb85c6..759a035 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,22 @@ to organize integration testing of Go code with Postgres. of [GitHub Actions](https://github.com/xorcare/testing-go-code-with-postgres/blob/main/.github/workflows/go.yml) and [Gitlab CI](https://github.com/xorcare/testing-go-code-with-postgres/blob/main/.gitlab-ci.yml). +Generating human-readable database names from `t.Name()` to simplifying problem investigation. +The last 8 characters are a short unique identifier needed to prevent name collision, its necessary +because the maximum length of the name is 63 bytes, and the name must be unique. + +```txt +TestNewPostgres-Changes-are-not-visible-in-different-inWirPQD7J +TestNewPostgres-Changes-are-not-visible-in-different-ineYp0ljjI +TestNewPostgres-Successfully-connect-by-URL-and-get-verzGq4pGza +TestNewPostgres-Successfully-obtained-a-version-using-a20YgZaMf +TestNewPostgres-URL-is-different-at-different-instancesIMDkJgoP +TestNewPostgres-URL-is-different-at-different-instancesjtSsjPR5 +TestUserRepository-CreateUser-Cannot-create-a-user-withmgmHFdZe +TestUserRepository-CreateUser-Successfully-created-a-UspTBGNltW +TestUserRepository-ReadUser-Get-an-error-if-the-user-doRqS1GvYh +``` + ## How to use Run `make test-env-up test` and then everything will happen by itself. diff --git a/testingpg/testingpg.go b/testingpg/testingpg.go index aba23e5..7484f27 100644 --- a/testingpg/testingpg.go +++ b/testingpg/testingpg.go @@ -6,13 +6,16 @@ package testingpg import ( "context" + "crypto/rand" + "encoding/base64" "fmt" "net/url" "os" + "strings" "sync" "time" + "unicode" - "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/require" ) @@ -20,8 +23,11 @@ import ( type TestingT interface { require.TestingT - Logf(format string, args ...any) Cleanup(f func()) + Log(args ...any) + Logf(format string, args ...any) + Name() string + Failed() bool } func New(t TestingT) *Postgres { @@ -72,11 +78,13 @@ func (p *Postgres) PgxPool() *pgxpool.Pool { } func (p *Postgres) cloneFromReference() *Postgres { - newDatabaseName := uuid.New().String() + newDBName := newUniqueHumanReadableDatabaseName(p.t) + + p.t.Log("database name for this test:", newDBName) sql := fmt.Sprintf( `CREATE DATABASE %q WITH TEMPLATE %q;`, - newDatabaseName, + newDBName, p.ref, ) @@ -85,7 +93,7 @@ func (p *Postgres) cloneFromReference() *Postgres { // Automatically drop database copy after the test is completed. p.t.Cleanup(func() { - sql := fmt.Sprintf(`DROP DATABASE %q WITH (FORCE);`, newDatabaseName) + sql := fmt.Sprintf(`DROP DATABASE %q WITH (FORCE);`, newDBName) ctx, done := context.WithTimeout(context.Background(), time.Minute) defer done() @@ -97,11 +105,55 @@ func (p *Postgres) cloneFromReference() *Postgres { return &Postgres{ t: p.t, - url: replaceDBName(p.t, p.URL(), newDatabaseName), - ref: newDatabaseName, + url: replaceDBName(p.t, p.URL(), newDBName), + ref: newDBName, } } +func newUniqueHumanReadableDatabaseName(t TestingT) string { + output := strings.Builder{} + + // Reports the maximum identifier length. It is determined as one less + // than the value of NAMEDATALEN when building the server. The default + // value of NAMEDATALEN is 64; therefore the default max_identifier_length + // is 63 bytes, which can be less than 63 characters when using multibyte + // encodings. + // See https://www.postgresql.org/docs/15/runtime-config-preset.html + const maxIdentifierLengthBytes = 63 + + uid := genUnique8BytesID(t) + maxHumanReadableLenBytes := maxIdentifierLengthBytes - len(uid) + + lastSymbolIsDash := false + for _, r := range t.Name() { + if unicode.IsLetter(r) || unicode.IsNumber(r) { + output.WriteRune(r) + lastSymbolIsDash = false + } else { + if !lastSymbolIsDash { + output.WriteRune('-') + } + lastSymbolIsDash = true + } + if output.Len() >= maxHumanReadableLenBytes { + break + } + } + + output.WriteString(uid) + + return output.String() +} + +func genUnique8BytesID(t TestingT) string { + bs := make([]byte, 6) + + _, err := rand.Read(bs) + require.NoError(t, err) + + return base64.RawURLEncoding.EncodeToString(bs) +} + func replaceDBName(t TestingT, dataSourceURL, dbname string) string { r, err := url.Parse(dataSourceURL) require.NoError(t, err)