Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit26fd6f3

Browse files
mafredripull[bot]
authored andcommitted
feat: Add database fixtures for testing migrations (#4858)
1 parenta9527f9 commit26fd6f3

File tree

9 files changed

+6717
-0
lines changed

9 files changed

+6717
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env bash
2+
3+
# Naming the fixture is optional, if missing, the name of the latest
4+
# migration will be used.
5+
#
6+
# Usage:
7+
# ./create_fixture
8+
# ./create_fixture name of fixture
9+
# ./create_fixture "name of fixture"
10+
# ./create_fixture name_of_fixture
11+
12+
set -euo pipefail
13+
14+
SCRIPT_DIR=$(dirname"${BASH_SOURCE[0]}")
15+
(
16+
cd"$SCRIPT_DIR"
17+
18+
latest_migration=$(basename"$(find. -maxdepth 1 -name"*.up.sql"| sort -n| tail -n 1)")
19+
if [[-n"${*}" ]];then
20+
name=$*
21+
name=${name///_}
22+
num=${latest_migration%%_*}
23+
latest_migration="${num}_${name}.up.sql"
24+
fi
25+
26+
filename="$(pwd)/testdata/fixtures/$latest_migration"
27+
touch"$filename"
28+
echo"$filename"
29+
echo"Edit fixture and commit it."
30+
)

‎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
returnnil
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+
funcStepper(db*sql.DB) (nextfunc() (versionuint,morebool,errerror),errerror) {
170+
_,m,err:=setup(db)
171+
iferr!=nil {
172+
returnnil,xerrors.Errorf("migrate setup: %w",err)
173+
}
174+
175+
returnfunc() (versionuint,morebool,errerror) {
176+
deferfunc() {
177+
if!more {
178+
srcErr,dbErr:=m.Close()
179+
iferr!=nil {
180+
return
181+
}
182+
ifdbErr!=nil {
183+
err=dbErr
184+
return
185+
}
186+
err=srcErr
187+
}
188+
}()
189+
190+
err=m.Steps(1)
191+
iferr!=nil {
192+
switch {
193+
caseerrors.Is(err,migrate.ErrNoChange):
194+
// It's OK if no changes happened!
195+
return0,false,nil
196+
caseerrors.Is(err,fs.ErrNotExist):
197+
// This error is encountered at the of Steps when
198+
// reading from embed.FS.
199+
return0,false,nil
200+
}
201+
202+
return0,false,xerrors.Errorf("Step: %w",err)
203+
}
204+
205+
v,_,err:=m.Version()
206+
iferr!=nil {
207+
return0,false,err
208+
}
209+
210+
returnv,true,nil
211+
},nil
212+
}

‎coderd/database/migrations/migrate_test.go‎

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

55
import (
6+
"context"
67
"database/sql"
78
"fmt"
9+
"os"
10+
"path/filepath"
11+
"sync"
812
"testing"
913

14+
"github.com/golang-migrate/migrate/v4"
15+
migratepostgres"github.com/golang-migrate/migrate/v4/database/postgres"
1016
"github.com/golang-migrate/migrate/v4/source"
17+
"github.com/golang-migrate/migrate/v4/source/iofs"
1118
"github.com/golang-migrate/migrate/v4/source/stub"
19+
"github.com/lib/pq"
1220
"github.com/stretchr/testify/require"
1321
"go.uber.org/goleak"
22+
"golang.org/x/exp/slices"
1423

1524
"github.com/coder/coder/coderd/database/migrations"
1625
"github.com/coder/coder/coderd/database/postgres"
26+
"github.com/coder/coder/testutil"
1727
)
1828

1929
funcTestMain(m*testing.M) {
@@ -129,3 +139,196 @@ func TestCheckLatestVersion(t *testing.T) {
129139
})
130140
}
131141
}
142+
143+
funcsetupMigrate(t*testing.T,db*sql.DB,name,pathstring) (source.Driver,*migrate.Migrate) {
144+
t.Helper()
145+
146+
ctx:=context.Background()
147+
148+
conn,err:=db.Conn(ctx)
149+
require.NoError(t,err)
150+
151+
dbDriver,err:=migratepostgres.WithConnection(ctx,conn,&migratepostgres.Config{
152+
MigrationsTable:"test_migrate_"+name,
153+
})
154+
require.NoError(t,err)
155+
156+
dirFS:=os.DirFS(path)
157+
d,err:=iofs.New(dirFS,".")
158+
require.NoError(t,err)
159+
t.Cleanup(func() {
160+
d.Close()
161+
})
162+
163+
m,err:=migrate.NewWithInstance(name,d,"",dbDriver)
164+
require.NoError(t,err)
165+
t.Cleanup(func() {
166+
m.Close()
167+
})
168+
169+
returnd,m
170+
}
171+
172+
typetableStatsstruct {
173+
mu sync.Mutex
174+
smap[string]int
175+
}
176+
177+
func (s*tableStats)Add(tablestring,nint) {
178+
s.mu.Lock()
179+
defers.mu.Unlock()
180+
181+
s.s[table]=s.s[table]+n
182+
}
183+
184+
func (s*tableStats)Empty() []string {
185+
s.mu.Lock()
186+
defers.mu.Unlock()
187+
188+
varm []string
189+
fortable,n:=ranges.s {
190+
ifn==0 {
191+
m=append(m,table)
192+
}
193+
}
194+
returnm
195+
}
196+
197+
funcTestMigrateUpWithFixtures(t*testing.T) {
198+
t.Parallel()
199+
200+
iftesting.Short() {
201+
t.Skip()
202+
return
203+
}
204+
205+
typetestCasestruct {
206+
namestring
207+
pathstring
208+
209+
// For determining if test case table stats
210+
// are used to determine test coverage.
211+
useStatsbool
212+
}
213+
tests:= []testCase{
214+
{
215+
name:"fixtures",
216+
path:filepath.Join("testdata","fixtures"),
217+
useStats:true,
218+
},
219+
// More test cases added via glob below.
220+
}
221+
222+
// Folders in testdata/full_dumps represent fixtures for a full
223+
// deployment of Coder.
224+
matches,err:=filepath.Glob(filepath.Join("testdata","full_dumps","*"))
225+
require.NoError(t,err)
226+
for_,match:=rangematches {
227+
tests=append(tests,testCase{
228+
name:filepath.Base(match),
229+
path:match,
230+
useStats:true,
231+
})
232+
}
233+
234+
// These tables are allowed to have zero rows for now,
235+
// but we should eventually add fixtures for them.
236+
ignoredTablesForStats:= []string{
237+
"audit_logs",
238+
"git_auth_links",
239+
"group_members",
240+
"licenses",
241+
"replicas",
242+
}
243+
s:=&tableStats{s:make(map[string]int)}
244+
245+
// This will run after all subtests have run and fail the test if
246+
// new tables have been added without covering them with fixtures.
247+
t.Cleanup(func() {
248+
emptyTables:=s.Empty()
249+
slices.Sort(emptyTables)
250+
for_,table:=rangeignoredTablesForStats {
251+
i:=slices.Index(emptyTables,table)
252+
ifi>=0 {
253+
emptyTables=slices.Delete(emptyTables,i,i+1)
254+
}
255+
}
256+
iflen(emptyTables)>0 {
257+
t.Logf("The following tables have zero rows, consider adding fixtures for them or create a full database dump:")
258+
t.Errorf("tables have zero rows: %v",emptyTables)
259+
t.Logf("See https://github.com/coder/coder/blob/main/docs/CONTRIBUTING.md#database-fixtures-for-testing-migrations for more information")
260+
}
261+
})
262+
263+
for_,tt:=rangetests {
264+
tt:=tt
265+
266+
t.Run(tt.name,func(t*testing.T) {
267+
t.Parallel()
268+
269+
db:=testSQLDB(t)
270+
271+
ctx,_:=testutil.Context(t)
272+
273+
// Prepare database for stepping up.
274+
err:=migrations.Down(db)
275+
require.NoError(t,err)
276+
277+
// Initialize migrations for fixtures.
278+
fDriver,fMigrate:=setupMigrate(t,db,tt.name,tt.path)
279+
280+
nextStep,err:=migrations.Stepper(db)
281+
require.NoError(t,err)
282+
283+
varfixtureVeruint
284+
nextFixtureVer,err:=fDriver.First()
285+
require.NoError(t,err)
286+
287+
for {
288+
version,more,err:=nextStep()
289+
require.NoError(t,err)
290+
291+
if!more {
292+
// We reached the end of the migrations.
293+
break
294+
}
295+
296+
ifnextFixtureVer==version {
297+
err=fMigrate.Steps(1)
298+
require.NoError(t,err)
299+
fixtureVer=version
300+
301+
nv,_:=fDriver.Next(nextFixtureVer)
302+
ifnv>0 {
303+
nextFixtureVer=nv
304+
}
305+
}
306+
307+
t.Logf("migrated to version %d, fixture version %d",version,fixtureVer)
308+
}
309+
310+
// Gather number of rows for all existing tables
311+
// at the end of the migrations and fixtures.
312+
vartables pq.StringArray
313+
err=db.QueryRowContext(ctx,`
314+
SELECT array_agg(tablename)
315+
FROM pg_catalog.pg_tables
316+
WHERE
317+
schemaname != 'information_schema'
318+
AND schemaname != 'pg_catalog'
319+
AND tablename NOT LIKE 'test_migrate_%'
320+
`).Scan(&tables)
321+
require.NoError(t,err)
322+
323+
for_,table:=rangetables {
324+
varcountint
325+
err=db.QueryRowContext(ctx,"SELECT COUNT(*) FROM "+table).Scan(&count)
326+
require.NoError(t,err)
327+
328+
iftt.useStats {
329+
s.Add(table,count)
330+
}
331+
}
332+
})
333+
}
334+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp