Skip to content

Commit 7d1c3e8

Browse files
committed
Gather table row stats and fail test on missing fixtures
1 parent 511c213 commit 7d1c3e8

File tree

1 file changed

+94
-4
lines changed

1 file changed

+94
-4
lines changed

coderd/database/migrations/migrate_test.go

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@ import (
88
"fmt"
99
"os"
1010
"path/filepath"
11+
"sync"
1112
"testing"
1213

1314
"github.com/golang-migrate/migrate/v4"
1415
migratepostgres "github.com/golang-migrate/migrate/v4/database/postgres"
1516
"github.com/golang-migrate/migrate/v4/source"
1617
"github.com/golang-migrate/migrate/v4/source/iofs"
1718
"github.com/golang-migrate/migrate/v4/source/stub"
19+
"github.com/lib/pq"
1820
"github.com/stretchr/testify/require"
1921
"go.uber.org/goleak"
22+
"golang.org/x/exp/slices"
2023

2124
"github.com/coder/coder/coderd/database/migrations"
2225
"github.com/coder/coder/coderd/database/postgres"
26+
"github.com/coder/coder/testutil"
2327
)
2428

2529
func TestMain(m *testing.M) {
@@ -165,6 +169,31 @@ func setupMigrate(t *testing.T, db *sql.DB, name, path string) (source.Driver, *
165169
return d, m
166170
}
167171

172+
type tableStats struct {
173+
mu sync.Mutex
174+
s map[string]int
175+
}
176+
177+
func (s *tableStats) Add(table string, n int) {
178+
s.mu.Lock()
179+
defer s.mu.Unlock()
180+
181+
s.s[table] = s.s[table] + n
182+
}
183+
184+
func (s *tableStats) Empty() []string {
185+
s.mu.Lock()
186+
defer s.mu.Unlock()
187+
188+
var m []string
189+
for table, n := range s.s {
190+
if n == 0 {
191+
m = append(m, table)
192+
}
193+
}
194+
return m
195+
}
196+
168197
func TestMigrateUpWithFixtures(t *testing.T) {
169198
t.Parallel()
170199

@@ -176,11 +205,16 @@ func TestMigrateUpWithFixtures(t *testing.T) {
176205
type testCase struct {
177206
name string
178207
path string
208+
209+
// For determining if test case table stats
210+
// are used to determine test coverage.
211+
useStats bool
179212
}
180213
tests := []testCase{
181214
{
182-
name: "fixtures",
183-
path: filepath.Join("testdata", "fixtures"),
215+
name: "fixtures",
216+
path: filepath.Join("testdata", "fixtures"),
217+
useStats: true,
184218
},
185219
// More test cases added via glob below.
186220
}
@@ -191,11 +225,42 @@ func TestMigrateUpWithFixtures(t *testing.T) {
191225
require.NoError(t, err)
192226
for _, match := range matches {
193227
tests = append(tests, testCase{
194-
name: filepath.Base(match),
195-
path: match,
228+
name: filepath.Base(match),
229+
path: match,
230+
useStats: true,
196231
})
197232
}
198233

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 := range ignoredTablesForStats {
251+
i := slices.Index(emptyTables, table)
252+
if i >= 0 {
253+
emptyTables = slices.Delete(emptyTables, i, i+1)
254+
}
255+
}
256+
if len(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+
// TODO(mafredri): Placeholder URL.
260+
t.Logf("See https://github.com/coder/coder/blob/main/migrations/README.md#creating-fixtures for more information")
261+
}
262+
})
263+
199264
for _, tt := range tests {
200265
tt := tt
201266

@@ -204,6 +269,8 @@ func TestMigrateUpWithFixtures(t *testing.T) {
204269

205270
db := testSQLDB(t)
206271

272+
ctx, _ := testutil.Context(t)
273+
207274
// Prepare database for stepping up.
208275
err := migrations.Down(db)
209276
require.NoError(t, err)
@@ -240,6 +307,29 @@ func TestMigrateUpWithFixtures(t *testing.T) {
240307

241308
t.Logf("migrated to version %d, fixture version %d", version, fixtureVer)
242309
}
310+
311+
// Gather number of rows for all existing tables
312+
// at the end of the migrations and fixtures.
313+
var tables pq.StringArray
314+
err = db.QueryRowContext(ctx, `
315+
SELECT array_agg(tablename)
316+
FROM pg_catalog.pg_tables
317+
WHERE
318+
schemaname != 'information_schema'
319+
AND schemaname != 'pg_catalog'
320+
AND tablename NOT LIKE 'test_migrate_%'
321+
`).Scan(&tables)
322+
require.NoError(t, err)
323+
324+
for _, table := range tables {
325+
var count int
326+
err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM "+table).Scan(&count)
327+
require.NoError(t, err)
328+
329+
if tt.useStats {
330+
s.Add(table, count)
331+
}
332+
}
243333
})
244334
}
245335
}

0 commit comments

Comments
 (0)