|
8 | 8 | "fmt" |
9 | 9 | "os" |
10 | 10 | "path/filepath" |
| 11 | + "strings" |
11 | 12 | "testing" |
12 | 13 | "time" |
13 | 14 |
|
@@ -36,27 +37,75 @@ func SetupTestDB(t *testing.T) *TestDB { |
36 | 37 | dsn = "postgres://postgres:testpassword@localhost:5432/ackify_test?sslmode=disable" |
37 | 38 | } |
38 | 39 |
|
39 | | - db, err := sql.Open("postgres", dsn) |
| 40 | + // Create unique test database name to enable parallel test execution |
| 41 | + // Format: testdb_{nanosecond}_{pid}_{testname} |
| 42 | + // PostgreSQL converts unquoted identifiers to lowercase, so we normalize to lowercase |
| 43 | + testName := strings.ReplaceAll(t.Name(), "/", "_") |
| 44 | + testName = strings.ReplaceAll(testName, " ", "_") |
| 45 | + testName = strings.ToLower(testName) |
| 46 | + // Limit testName to avoid exceeding PostgreSQL's 63-character limit |
| 47 | + if len(testName) > 30 { |
| 48 | + testName = testName[:30] |
| 49 | + } |
| 50 | + dbName := fmt.Sprintf("testdb_%d_%d_%s", time.Now().UnixNano(), os.Getpid(), testName) |
| 51 | + |
| 52 | + // Truncate database name to PostgreSQL's 63-character limit |
| 53 | + if len(dbName) > 63 { |
| 54 | + dbName = dbName[:63] |
| 55 | + } |
| 56 | + |
| 57 | + // Connect to default postgres database to create test database |
| 58 | + mainDSN := strings.Replace(dsn, "/ackify_test?", "/postgres?", 1) |
| 59 | + mainDB, err := sql.Open("postgres", mainDSN) |
| 60 | + if err != nil { |
| 61 | + t.Fatalf("Failed to connect to postgres database: %v", err) |
| 62 | + } |
| 63 | + defer mainDB.Close() |
| 64 | + |
| 65 | + // Create unique test database |
| 66 | + _, err = mainDB.Exec(fmt.Sprintf("CREATE DATABASE %s", dbName)) |
| 67 | + if err != nil { |
| 68 | + t.Fatalf("Failed to create test database %s: %v", dbName, err) |
| 69 | + } |
| 70 | + |
| 71 | + // Connect to the new test database |
| 72 | + testDSN := strings.Replace(dsn, "/ackify_test?", fmt.Sprintf("/%s?", dbName), 1) |
| 73 | + db, err := sql.Open("postgres", testDSN) |
40 | 74 | if err != nil { |
41 | | - t.Fatalf("Failed to connect to test database: %v", err) |
| 75 | + t.Fatalf("Failed to connect to test database %s: %v", dbName, err) |
42 | 76 | } |
43 | 77 |
|
44 | 78 | if err := db.Ping(); err != nil { |
45 | | - t.Fatalf("Failed to ping test database: %v", err) |
| 79 | + t.Fatalf("Failed to ping test database %s: %v", dbName, err) |
46 | 80 | } |
47 | 81 |
|
48 | 82 | testDB := &TestDB{ |
49 | 83 | DB: db, |
50 | | - DSN: dsn, |
51 | | - dbName: fmt.Sprintf("test_%d_%d", time.Now().UnixNano(), os.Getpid()), |
| 84 | + DSN: testDSN, |
| 85 | + dbName: dbName, |
52 | 86 | } |
53 | 87 |
|
54 | 88 | if err := testDB.createSchema(); err != nil { |
55 | | - t.Fatalf("Failed to create test schema: %v", err) |
| 89 | + t.Fatalf("Failed to create test schema in %s: %v", dbName, err) |
56 | 90 | } |
57 | 91 |
|
58 | 92 | t.Cleanup(func() { |
59 | 93 | testDB.Cleanup() |
| 94 | + |
| 95 | + // Drop the test database after cleanup |
| 96 | + mainDB, err := sql.Open("postgres", mainDSN) |
| 97 | + if err == nil { |
| 98 | + defer mainDB.Close() |
| 99 | + // Force close any remaining connections |
| 100 | + _, _ = mainDB.Exec(fmt.Sprintf(` |
| 101 | + SELECT pg_terminate_backend(pg_stat_activity.pid) |
| 102 | + FROM pg_stat_activity |
| 103 | + WHERE pg_stat_activity.datname = '%s' |
| 104 | + AND pid <> pg_backend_pid() |
| 105 | + `, dbName)) |
| 106 | + // Drop the database |
| 107 | + _, _ = mainDB.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbName)) |
| 108 | + } |
60 | 109 | }) |
61 | 110 |
|
62 | 111 | return testDB |
|
0 commit comments