Skip to content

Commit e21155a

Browse files
kyleconroylisitsky
authored andcommitted
test(endtoend): Re-use databases when possible (sqlc-dev#3315)
* read only * test: Run end-to-end tests against a databse * Fix some invalid SQL
1 parent dc04320 commit e21155a

File tree

7 files changed

+154
-49
lines changed

7 files changed

+154
-49
lines changed

internal/endtoend/endtoend_test.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,14 @@ func TestReplay(t *testing.T) {
120120
"managed-db": {
121121
Mutate: func(t *testing.T, path string) func(*config.Config) {
122122
return func(c *config.Config) {
123-
c.Cloud.Project = "01HAQMMECEYQYKFJN8MP16QC41" // TODO: Read from environment
124123
for i := range c.SQL {
125124
files := []string{}
126125
for _, s := range c.SQL[i].Schema {
127126
files = append(files, filepath.Join(path, s))
128127
}
129128
switch c.SQL[i].Engine {
130129
case config.EnginePostgreSQL:
131-
uri := local.PostgreSQL(t, files)
130+
uri := local.ReadOnlyPostgreSQL(t, files)
132131
c.SQL[i].Database = &config.Database{
133132
URI: uri,
134133
}
@@ -138,18 +137,12 @@ func TestReplay(t *testing.T) {
138137
// URI: uri,
139138
// }
140139
default:
141-
c.SQL[i].Database = &config.Database{
142-
Managed: true,
143-
}
140+
// pass
144141
}
145142
}
146143
}
147144
},
148145
Enabled: func() bool {
149-
// Return false if no auth token exists
150-
if len(os.Getenv("SQLC_AUTH_TOKEN")) == 0 {
151-
return false
152-
}
153146
if len(os.Getenv("POSTGRESQL_SERVER_URI")) == 0 {
154147
return false
155148
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CREATE TABLE foo(
1+
CREATE TABLE foo (
22
bar_id text,
33
site_url text
44
);
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
CREATE TABLE foo(
1+
CREATE TABLE foo (
22
bar text
33
);
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
CREATE SCHEMA foo;
22
CREATE TABLE foo.bar (id serial not null);
3-

internal/pgx/poolcache/poolcache.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package poolcache
2+
3+
import (
4+
"context"
5+
"sync"
6+
7+
"github.com/jackc/pgx/v5/pgxpool"
8+
)
9+
10+
var lock sync.RWMutex
11+
var pools = map[string]*pgxpool.Pool{}
12+
13+
func New(ctx context.Context, uri string) (*pgxpool.Pool, error) {
14+
lock.RLock()
15+
existing, found := pools[uri]
16+
lock.RUnlock()
17+
18+
if found {
19+
return existing, nil
20+
}
21+
22+
pool, err := pgxpool.New(ctx, uri)
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
lock.Lock()
28+
pools[uri] = pool
29+
lock.Unlock()
30+
31+
return pool, nil
32+
}

internal/sqltest/local/postgres.go

+63-37
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@ package local
33
import (
44
"context"
55
"fmt"
6+
"hash/fnv"
67
"net/url"
78
"os"
89
"strings"
9-
"sync"
1010
"testing"
1111

1212
"github.com/jackc/pgx/v5"
13-
"github.com/jackc/pgx/v5/pgxpool"
13+
"golang.org/x/sync/singleflight"
1414

1515
migrate "github.com/sqlc-dev/sqlc/internal/migrations"
16+
"github.com/sqlc-dev/sqlc/internal/pgx/poolcache"
1617
"github.com/sqlc-dev/sqlc/internal/sql/sqlpath"
1718
)
1819

19-
var postgresPool *pgxpool.Pool
20-
var postgresSync sync.Once
20+
var flight singleflight.Group
2121

2222
func PostgreSQL(t *testing.T, migrations []string) string {
23+
return postgreSQL(t, migrations, true)
24+
}
25+
26+
func ReadOnlyPostgreSQL(t *testing.T, migrations []string) string {
27+
return postgreSQL(t, migrations, false)
28+
}
29+
30+
func postgreSQL(t *testing.T, migrations []string, rw bool) string {
2331
ctx := context.Background()
2432
t.Helper()
2533

@@ -28,65 +36,83 @@ func PostgreSQL(t *testing.T, migrations []string) string {
2836
t.Skip("POSTGRESQL_SERVER_URI is empty")
2937
}
3038

31-
postgresSync.Do(func() {
32-
pool, err := pgxpool.New(ctx, dburi)
33-
if err != nil {
34-
t.Fatal(err)
35-
}
36-
postgresPool = pool
37-
})
38-
39-
if postgresPool == nil {
40-
t.Fatalf("PostgreSQL pool creation failed")
39+
postgresPool, err := poolcache.New(ctx, dburi)
40+
if err != nil {
41+
t.Fatalf("PostgreSQL pool creation failed: %s", err)
4142
}
4243

4344
var seed []string
4445
files, err := sqlpath.Glob(migrations)
4546
if err != nil {
4647
t.Fatal(err)
4748
}
49+
50+
h := fnv.New64()
4851
for _, f := range files {
4952
blob, err := os.ReadFile(f)
5053
if err != nil {
5154
t.Fatal(err)
5255
}
56+
h.Write(blob)
5357
seed = append(seed, migrate.RemoveRollbackStatements(string(blob)))
5458
}
5559

56-
uri, err := url.Parse(dburi)
57-
if err != nil {
58-
t.Fatal(err)
60+
var name string
61+
if rw {
62+
name = fmt.Sprintf("sqlc_test_%s", id())
63+
} else {
64+
name = fmt.Sprintf("sqlc_test_%x", h.Sum(nil))
5965
}
6066

61-
name := fmt.Sprintf("sqlc_test_%s", id())
62-
63-
if _, err := postgresPool.Exec(ctx, fmt.Sprintf(`CREATE DATABASE "%s"`, name)); err != nil {
67+
uri, err := url.Parse(dburi)
68+
if err != nil {
6469
t.Fatal(err)
6570
}
66-
6771
uri.Path = name
6872
dropQuery := fmt.Sprintf(`DROP DATABASE IF EXISTS "%s" WITH (FORCE)`, name)
6973

70-
t.Cleanup(func() {
71-
if _, err := postgresPool.Exec(ctx, dropQuery); err != nil {
72-
t.Fatal(err)
74+
key := uri.String()
75+
76+
_, err, _ = flight.Do(key, func() (interface{}, error) {
77+
row := postgresPool.QueryRow(ctx,
78+
fmt.Sprintf(`SELECT datname FROM pg_database WHERE datname = '%s'`, name))
79+
80+
var datname string
81+
if err := row.Scan(&datname); err == nil {
82+
t.Logf("database exists: %s", name)
83+
return nil, nil
7384
}
74-
})
7585

76-
conn, err := pgx.Connect(ctx, uri.String())
77-
if err != nil {
78-
t.Fatalf("connect %s: %s", name, err)
79-
}
80-
defer conn.Close(ctx)
86+
t.Logf("creating database: %s", name)
87+
if _, err := postgresPool.Exec(ctx, fmt.Sprintf(`CREATE DATABASE "%s"`, name)); err != nil {
88+
return nil, err
89+
}
8190

82-
for _, q := range seed {
83-
if len(strings.TrimSpace(q)) == 0 {
84-
continue
91+
conn, err := pgx.Connect(ctx, uri.String())
92+
if err != nil {
93+
return nil, fmt.Errorf("connect %s: %s", name, err)
8594
}
86-
if _, err := conn.Exec(ctx, q); err != nil {
87-
t.Fatalf("%s: %s", q, err)
95+
defer conn.Close(ctx)
96+
97+
for _, q := range seed {
98+
if len(strings.TrimSpace(q)) == 0 {
99+
continue
100+
}
101+
if _, err := conn.Exec(ctx, q); err != nil {
102+
return nil, fmt.Errorf("%s: %s", q, err)
103+
}
88104
}
105+
return nil, nil
106+
})
107+
if rw || err != nil {
108+
t.Cleanup(func() {
109+
if _, err := postgresPool.Exec(ctx, dropQuery); err != nil {
110+
t.Fatalf("failed cleaning up: %s", err)
111+
}
112+
})
89113
}
90-
91-
return uri.String()
114+
if err != nil {
115+
t.Fatalf("create db: %s", err)
116+
}
117+
return key
92118
}

scripts/cleanup-test-dbs/main.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
9+
"github.com/jackc/pgx/v5"
10+
"github.com/jackc/pgx/v5/pgxpool"
11+
)
12+
13+
func main() {
14+
if err := run(); err != nil {
15+
log.Fatal(err)
16+
}
17+
}
18+
19+
const query = `
20+
SELECT datname
21+
FROM pg_database
22+
WHERE datname LIKE 'sqlc_test_%'
23+
`
24+
25+
func run() error {
26+
ctx := context.Background()
27+
dburi := os.Getenv("POSTGRESQL_SERVER_URI")
28+
if dburi == "" {
29+
return fmt.Errorf("POSTGRESQL_SERVER_URI is empty")
30+
}
31+
pool, err := pgxpool.New(ctx, dburi)
32+
if err != nil {
33+
return err
34+
}
35+
36+
rows, err := pool.Query(ctx, query)
37+
if err != nil {
38+
return err
39+
}
40+
41+
names, err := pgx.CollectRows(rows, pgx.RowTo[string])
42+
if err != nil {
43+
return err
44+
}
45+
46+
for _, name := range names {
47+
drop := fmt.Sprintf(`DROP DATABASE IF EXISTS "%s" WITH (FORCE)`, name)
48+
if _, err := pool.Exec(ctx, drop); err != nil {
49+
return err
50+
}
51+
log.Println("dropping database", name)
52+
}
53+
54+
return nil
55+
}

0 commit comments

Comments
 (0)