1
0
mirror of https://github.com/xorcare/testing-go-code-with-postgres.git synced 2025-06-30 23:23:40 +02:00

Make human-readable database name

Sometimes tests terminate fatally and databases are not deleted and you
want to understand which test left garbage behind. It is also in the
future it will be possible to add the ability not to delete databases
for tests that ended with an error, and it will be necessary to
understand from which test the database.

Therefore, the generation of the database name from the name of the
test with the addition of a unique identifier so that the names do not
overlap.

    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

    PostgreSQL allows you to create any number of databases at a given
    site. Database names must have an alphabetic first character and
    are limited to 63 bytes in length. A convenient choice is to create
    a database with the same name as your current user name. Many tools
    assume that database name as the default, so it can save you some
    typing. To create that database, simply type:

See https://www.postgresql.org/docs/15/tutorial-createdb.html
This commit is contained in:
Vasiliy Vasilyuk
2023-07-16 11:33:52 +03:00
parent cdd90f86db
commit b98187650e
2 changed files with 75 additions and 7 deletions

View File

@ -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) 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). 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 ## How to use
Run `make test-env-up test` and then everything will happen by itself. Run `make test-env-up test` and then everything will happen by itself.

View File

@ -6,13 +6,16 @@ package testingpg
import ( import (
"context" "context"
"crypto/rand"
"encoding/base64"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
"unicode"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -20,8 +23,11 @@ import (
type TestingT interface { type TestingT interface {
require.TestingT require.TestingT
Logf(format string, args ...any)
Cleanup(f func()) Cleanup(f func())
Log(args ...any)
Logf(format string, args ...any)
Name() string
Failed() bool
} }
func New(t TestingT) *Postgres { func New(t TestingT) *Postgres {
@ -72,11 +78,13 @@ func (p *Postgres) PgxPool() *pgxpool.Pool {
} }
func (p *Postgres) cloneFromReference() *Postgres { 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( sql := fmt.Sprintf(
`CREATE DATABASE %q WITH TEMPLATE %q;`, `CREATE DATABASE %q WITH TEMPLATE %q;`,
newDatabaseName, newDBName,
p.ref, p.ref,
) )
@ -85,7 +93,7 @@ func (p *Postgres) cloneFromReference() *Postgres {
// Automatically drop database copy after the test is completed. // Automatically drop database copy after the test is completed.
p.t.Cleanup(func() { 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) ctx, done := context.WithTimeout(context.Background(), time.Minute)
defer done() defer done()
@ -97,11 +105,55 @@ func (p *Postgres) cloneFromReference() *Postgres {
return &Postgres{ return &Postgres{
t: p.t, t: p.t,
url: replaceDBName(p.t, p.URL(), newDatabaseName), url: replaceDBName(p.t, p.URL(), newDBName),
ref: newDatabaseName, 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 { func replaceDBName(t TestingT, dataSourceURL, dbname string) string {
r, err := url.Parse(dataSourceURL) r, err := url.Parse(dataSourceURL)
require.NoError(t, err) require.NoError(t, err)