Skip to content

Commit a9bf36b

Browse files
committed
feat: Add database fixtures for testing migrations
1 parent b1c400a commit a9bf36b

File tree

7 files changed

+6526
-0
lines changed

7 files changed

+6526
-0
lines changed

coderd/database/migrations/migrate.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"database/sql"
66
"embed"
77
"errors"
8+
"io/fs"
89
"os"
910

1011
"github.com/golang-migrate/migrate/v4"
@@ -160,3 +161,52 @@ func CheckLatestVersion(sourceDriver source.Driver, currentVersion uint) error {
160161
}
161162
return nil
162163
}
164+
165+
// Stepper returns a function that runs SQL migrations one step at a time.
166+
//
167+
// Stepper cannot be closed pre-emptively, it must be run to completion
168+
// (or until an error is encountered).
169+
func Stepper(db *sql.DB) (next func() (version uint, more bool, err error), err error) {
170+
_, m, err := setup(db)
171+
if err != nil {
172+
return nil, xerrors.Errorf("migrate setup: %w", err)
173+
}
174+
175+
return func() (version uint, more bool, err error) {
176+
defer func() {
177+
if !more {
178+
srcErr, dbErr := m.Close()
179+
if err != nil {
180+
return
181+
}
182+
if dbErr != nil {
183+
err = dbErr
184+
return
185+
}
186+
err = srcErr
187+
}
188+
}()
189+
190+
err = m.Steps(1)
191+
if err != nil {
192+
switch {
193+
case errors.Is(err, migrate.ErrNoChange):
194+
// It's OK if no changes happened!
195+
return 0, false, nil
196+
case errors.Is(err, fs.ErrNotExist):
197+
// This error is encountered at the of Steps when
198+
// reading from embed.FS.
199+
return 0, false, nil
200+
}
201+
202+
return 0, false, xerrors.Errorf("Step: %w", err)
203+
}
204+
205+
v, _, err := m.Version()
206+
if err != nil {
207+
return 0, false, err
208+
}
209+
210+
return v, true, nil
211+
}, nil
212+
}

coderd/database/migrations/migrate_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
package migrations_test
44

55
import (
6+
"context"
67
"database/sql"
78
"fmt"
9+
"os"
10+
"path/filepath"
811
"testing"
912

13+
"github.com/golang-migrate/migrate/v4"
14+
migratepostgres "github.com/golang-migrate/migrate/v4/database/postgres"
1015
"github.com/golang-migrate/migrate/v4/source"
16+
"github.com/golang-migrate/migrate/v4/source/iofs"
1117
"github.com/golang-migrate/migrate/v4/source/stub"
1218
"github.com/stretchr/testify/require"
1319
"go.uber.org/goleak"
@@ -129,3 +135,111 @@ func TestCheckLatestVersion(t *testing.T) {
129135
})
130136
}
131137
}
138+
139+
func setupMigrate(t *testing.T, db *sql.DB, name, path string) (source.Driver, *migrate.Migrate) {
140+
t.Helper()
141+
142+
ctx := context.Background()
143+
144+
conn, err := db.Conn(ctx)
145+
require.NoError(t, err)
146+
147+
dbDriver, err := migratepostgres.WithConnection(ctx, conn, &migratepostgres.Config{
148+
MigrationsTable: "test_migrate_" + name,
149+
})
150+
require.NoError(t, err)
151+
152+
dirFS := os.DirFS(path)
153+
d, err := iofs.New(dirFS, ".")
154+
require.NoError(t, err)
155+
t.Cleanup(func() {
156+
d.Close()
157+
})
158+
159+
m, err := migrate.NewWithInstance(name, d, "", dbDriver)
160+
require.NoError(t, err)
161+
t.Cleanup(func() {
162+
m.Close()
163+
})
164+
165+
return d, m
166+
}
167+
168+
func TestMigrateUpWithFixtures(t *testing.T) {
169+
t.Parallel()
170+
171+
if testing.Short() {
172+
t.Skip()
173+
return
174+
}
175+
176+
type testCase struct {
177+
name string
178+
path string
179+
}
180+
tests := []testCase{
181+
{
182+
name: "fixtures",
183+
path: filepath.Join("testdata", "fixtures"),
184+
},
185+
// More test cases added via glob below.
186+
}
187+
188+
// Folders in testdata/full_dumps represent fixtures for a full
189+
// deployment of Coder.
190+
matches, err := filepath.Glob(filepath.Join("testdata", "full_dumps", "*"))
191+
require.NoError(t, err)
192+
for _, match := range matches {
193+
tests = append(tests, testCase{
194+
name: filepath.Base(match),
195+
path: match,
196+
})
197+
}
198+
199+
for _, tt := range tests {
200+
tt := tt
201+
202+
t.Run(tt.name, func(t *testing.T) {
203+
t.Parallel()
204+
205+
db := testSQLDB(t)
206+
207+
// Prepare database for stepping up.
208+
err := migrations.Down(db)
209+
require.NoError(t, err)
210+
211+
// Initialize migrations for fixtures.
212+
fDriver, fMigrate := setupMigrate(t, db, tt.name, tt.path)
213+
214+
nextStep, err := migrations.Stepper(db)
215+
require.NoError(t, err)
216+
217+
var fixtureVer uint
218+
nextFixtureVer, err := fDriver.First()
219+
require.NoError(t, err)
220+
221+
for {
222+
version, more, err := nextStep()
223+
require.NoError(t, err)
224+
225+
if !more {
226+
// We reached the end of the migrations.
227+
break
228+
}
229+
230+
if nextFixtureVer == version {
231+
err = fMigrate.Steps(1)
232+
require.NoError(t, err)
233+
fixtureVer = version
234+
235+
nv, _ := fDriver.Next(nextFixtureVer)
236+
if nv > 0 {
237+
nextFixtureVer = nv
238+
}
239+
}
240+
241+
t.Logf("migrated to version %d, fixture version %d", version, fixtureVer)
242+
}
243+
})
244+
}
245+
}

0 commit comments

Comments
 (0)