- Notifications
You must be signed in to change notification settings - Fork1k
test: refactor dbtestutil to record database creation#19843
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
+255 −133
Merged
Changes fromall commits
Commits
Show all changes
3 commits Select commitHold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
176 changes: 176 additions & 0 deletionscoderd/database/dbtestutil/broker.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package dbtestutil | ||
import ( | ||
"database/sql" | ||
_ "embed" | ||
"fmt" | ||
"os" | ||
"sync" | ||
"time" | ||
"github.com/google/uuid" | ||
"github.com/lib/pq" | ||
"golang.org/x/xerrors" | ||
"github.com/coder/coder/v2/cryptorand" | ||
) | ||
const CoderTestingDBName = "coder_testing" | ||
deansheather marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
//go:embed coder_testing.sql | ||
var coderTestingSQLInit string | ||
type Broker struct { | ||
sync.Mutex | ||
uuid uuid.UUID | ||
coderTestingDB *sql.DB | ||
refCount int | ||
} | ||
func (b *Broker) Create(t TBSubset, opts ...OpenOption) (ConnectionParams, error) { | ||
if err := b.init(t); err != nil { | ||
return ConnectionParams{}, err | ||
} | ||
openOptions := OpenOptions{} | ||
for _, opt := range opts { | ||
opt(&openOptions) | ||
} | ||
var ( | ||
username = defaultConnectionParams.Username | ||
password = defaultConnectionParams.Password | ||
host = defaultConnectionParams.Host | ||
port = defaultConnectionParams.Port | ||
) | ||
// Use a time-based prefix to make it easier to find the database | ||
// when debugging. | ||
now := time.Now().Format("test_2006_01_02_15_04_05") | ||
dbSuffix, err := cryptorand.StringCharset(cryptorand.Lower, 10) | ||
if err != nil { | ||
return ConnectionParams{}, xerrors.Errorf("generate db suffix: %w", err) | ||
} | ||
dbName := now + "_" + dbSuffix | ||
// TODO: add package and test name | ||
_, err = b.coderTestingDB.Exec( | ||
"INSERT INTO test_databases (name, process_uuid) VALUES ($1, $2)", dbName, b.uuid) | ||
if err != nil { | ||
return ConnectionParams{}, xerrors.Errorf("insert test_database row: %w", err) | ||
} | ||
// if empty createDatabaseFromTemplate will create a new template db | ||
templateDBName := os.Getenv("DB_FROM") | ||
if openOptions.DBFrom != nil { | ||
templateDBName = *openOptions.DBFrom | ||
} | ||
if err = createDatabaseFromTemplate(t, defaultConnectionParams, b.coderTestingDB, dbName, templateDBName); err != nil { | ||
return ConnectionParams{}, xerrors.Errorf("create database: %w", err) | ||
} | ||
testDBParams := ConnectionParams{ | ||
Username: username, | ||
Password: password, | ||
Host: host, | ||
Port: port, | ||
DBName: dbName, | ||
} | ||
// Optionally log the DSN to help connect to the test database. | ||
if openOptions.LogDSN { | ||
_, _ = fmt.Fprintf(os.Stderr, "Connect to the database for %s using: psql '%s'\n", t.Name(), testDBParams.DSN()) | ||
} | ||
t.Cleanup(b.clean(t, dbName)) | ||
return testDBParams, nil | ||
} | ||
func (b *Broker) clean(t TBSubset, dbName string) func() { | ||
return func() { | ||
_, err := b.coderTestingDB.Exec("DROP DATABASE " + dbName + ";") | ||
if err != nil { | ||
t.Logf("failed to clean up database %q: %s\n", dbName, err.Error()) | ||
return | ||
} | ||
_, err = b.coderTestingDB.Exec("UPDATE test_databases SET dropped_at = CURRENT_TIMESTAMP WHERE name = $1", dbName) | ||
if err != nil { | ||
t.Logf("failed to mark test database '%s' dropped: %s\n", dbName, err.Error()) | ||
} | ||
} | ||
} | ||
func (b *Broker) init(t TBSubset) error { | ||
b.Lock() | ||
defer b.Unlock() | ||
b.refCount++ | ||
t.Cleanup(b.decRef) | ||
if b.coderTestingDB != nil { | ||
// already initialized | ||
return nil | ||
} | ||
connectionParamsInitOnce.Do(func() { | ||
errDefaultConnectionParamsInit = initDefaultConnection(t) | ||
}) | ||
if errDefaultConnectionParamsInit != nil { | ||
return xerrors.Errorf("init default connection params: %w", errDefaultConnectionParamsInit) | ||
} | ||
coderTestingParams := defaultConnectionParams | ||
coderTestingParams.DBName = CoderTestingDBName | ||
coderTestingDB, err := sql.Open("postgres", coderTestingParams.DSN()) | ||
if err != nil { | ||
return xerrors.Errorf("open postgres connection: %w", err) | ||
} | ||
// creating the db can succeed even if the database doesn't exist. Ping it to find out. | ||
err = coderTestingDB.Ping() | ||
var pqErr *pq.Error | ||
if xerrors.As(err, &pqErr) && pqErr.Code == "3D000" { | ||
// database does not exist. | ||
if closeErr := coderTestingDB.Close(); closeErr != nil { | ||
return xerrors.Errorf("close postgres connection: %w", closeErr) | ||
} | ||
err = createCoderTestingDB(t) | ||
if err != nil { | ||
return xerrors.Errorf("create coder testing db: %w", err) | ||
} | ||
coderTestingDB, err = sql.Open("postgres", coderTestingParams.DSN()) | ||
if err != nil { | ||
return xerrors.Errorf("open postgres connection: %w", err) | ||
} | ||
} else if err != nil { | ||
_ = coderTestingDB.Close() | ||
return xerrors.Errorf("ping '%s' database: %w", CoderTestingDBName, err) | ||
} | ||
b.coderTestingDB = coderTestingDB | ||
b.uuid = uuid.New() | ||
return nil | ||
} | ||
func createCoderTestingDB(t TBSubset) error { | ||
db, err := sql.Open("postgres", defaultConnectionParams.DSN()) | ||
if err != nil { | ||
return xerrors.Errorf("open postgres connection: %w", err) | ||
} | ||
defer func() { | ||
_ = db.Close() | ||
}() | ||
err = createAndInitDatabase(t, defaultConnectionParams, db, CoderTestingDBName, func(testDB *sql.DB) error { | ||
_, err := testDB.Exec(coderTestingSQLInit) | ||
return err | ||
}) | ||
if err != nil { | ||
return xerrors.Errorf("create coder testing db: %w", err) | ||
} | ||
return nil | ||
} | ||
func (b *Broker) decRef() { | ||
b.Lock() | ||
defer b.Unlock() | ||
b.refCount-- | ||
if b.refCount == 0 { | ||
// ensures we don't leave go routines around for GoLeak to find. | ||
_ = b.coderTestingDB.Close() | ||
b.coderTestingDB = nil | ||
} | ||
deansheather marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} |
8 changes: 8 additions & 0 deletionscoderd/database/dbtestutil/coder_testing.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
CREATE TABLE IF NOT EXISTS test_databases ( | ||
name text PRIMARY KEY, | ||
created_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
dropped_at timestamp with time zone, -- null means it hasn't been dropped | ||
process_uuid uuid NOT NULL | ||
); | ||
CREATE INDEX IF NOT EXISTS test_databases_process_uuid ON test_databases (process_uuid, dropped_at); |
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.