Skip to content

Commit 5bbe0d8

Browse files
committed
Make Database index based
1 parent 900f71a commit 5bbe0d8

File tree

1 file changed

+64
-35
lines changed

1 file changed

+64
-35
lines changed

Tests/AppTests/Helpers/DatabasePool.swift

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ import Vapor
2222

2323
actor DatabasePool {
2424
struct Database: Hashable {
25-
var port: Int
25+
let index: Int
26+
let connectionDetails: ConnectionDetails
2627

27-
var connectionDetails: ConnectionDetails {
28-
.init(port: port)
28+
init(index: Int) {
29+
self.index = index
30+
self.connectionDetails = .init(index: index)
2931
}
32+
33+
var port: Int { connectionDetails.port }
3034
}
3135

3236
static let shared = DatabasePool(maxCount: Environment.databasePoolSize)
@@ -40,24 +44,53 @@ actor DatabasePool {
4044
var availableDatabases: Set<Database> = .init()
4145

4246
func setUp() async throws {
43-
let start = Date()
44-
print("ℹ️ \(#function) start")
45-
defer { print("ℹ️ \(#function) end", Date().timeIntervalSince(start)) }
4647
// Call DotEnvFile.load once to ensure env variables are set
4748
await DotEnvFile.load(for: .testing, fileio: .init(threadPool: .singleton))
4849

49-
// Re-use up to maxCount running dbs
5050
let runningDbs = try await runningDatabases()
51-
for db in runningDbs.prefix(maxCount) {
52-
availableDatabases.insert(db)
53-
}
5451

55-
print("ℹ️ availableDatabases", availableDatabases.count)
56-
for db in availableDatabases {
57-
print("ℹ️ setting up db \(db.port)")
58-
try await db.setup(for: .testing)
59-
print("ℹ️ DONE setting up db \(db.port)")
52+
if isRunningInCI() {
53+
// In CI, running dbs are new and need to be set up
54+
try await withThrowingTaskGroup(of: Database.self) { group in
55+
for db in runningDbs {
56+
group.addTask {
57+
try await db.setup(for: .testing)
58+
return db
59+
}
60+
}
61+
for try await db in group {
62+
availableDatabases.insert(db)
63+
}
64+
}
65+
} else {
66+
// Re-use up to maxCount running dbs
67+
for db in runningDbs.prefix(maxCount) {
68+
availableDatabases.insert(db)
69+
}
70+
71+
do { // Delete overprovisioned dbs
72+
let overprovisioned = runningDbs.dropFirst(maxCount)
73+
try await tearDown(databases: overprovisioned)
74+
}
75+
76+
do { // Create missing dbs
77+
let underprovisionedCount = max(maxCount - availableDatabases.count, 0)
78+
try await withThrowingTaskGroup(of: Database.self) { group in
79+
for _ in (0..<underprovisionedCount) {
80+
group.addTask {
81+
let db = try await self.launchDB()
82+
try await db.setup(for: .testing)
83+
return db
84+
}
85+
}
86+
for try await db in group {
87+
availableDatabases.insert(db)
88+
}
89+
}
90+
}
6091
}
92+
93+
print("ℹ️ availableDatabases:", availableDatabases.count)
6194
}
6295

6396
func tearDown() async throws {
@@ -81,9 +114,6 @@ actor DatabasePool {
81114
}
82115

83116
func withDatabase(_ operation: @Sendable (Database) async throws -> Void) async throws {
84-
let start = Date()
85-
print("ℹ️ \(#function) start")
86-
defer { print("ℹ️ \(#function) end", Date().timeIntervalSince(start)) }
87117
let db = try await retainDatabase()
88118
do {
89119
try await operation(db)
@@ -99,15 +129,15 @@ actor DatabasePool {
99129
// We don't have docker available in CI to probe for running dbs.
100130
// Instead, we have a hard-coded list of dbs we launch in the GH workflow
101131
// file and correspondingly, we hard-code their ports here.
102-
return (6000..<6008).map(Database.init)
132+
return (0..<8).map(Database.init(index:))
103133
} else {
104134
let stdout = try await ShellOut.shellOut(to: .getContainerNames).stdout
105135
return stdout
106136
.components(separatedBy: "\n")
107137
.filter { $0.starts(with: "spi_test_") }
108138
.map { String($0.dropFirst("spi_test_".count)) }
109139
.compactMap(Int.init)
110-
.map(Database.init(port:))
140+
.map(Database.init(index:))
111141
}
112142
}
113143

@@ -144,57 +174,56 @@ actor DatabasePool {
144174
print("⚠️ Launching DB on port \(port) (attempt: \(attempt))")
145175
try await ShellOut.shellOut(to: .launchDB(port: port))
146176
}
147-
return .init(port: port)
177+
return .init(index: port)
148178
}
149179

150180
private func removeDB(database: Database, maxAttempts: Int = 3) async throws {
151181
try await run(maxAttempts: 3) { attempt in
152182
// print("⚠️ Removing DB on port \(database.port) (attempt: \(attempt))")
153-
try await ShellOut.shellOut(to: .removeDB(port: database.port))
183+
try await ShellOut.shellOut(to: .removeDB(port: database.index))
154184
}
155185
}
156186
}
157187

158188

159189
extension DatabasePool.Database {
160190

161-
struct ConnectionDetails {
191+
struct ConnectionDetails: Hashable {
162192
var host: String
163193
var port: Int
164194
var username: String
165195
var password: String
166196

167-
init(port: Int) {
197+
init(index: Int) {
168198
// Ensure DATABASE_HOST is from a restricted set db hostnames and nothing else.
169199
// This is safeguard against accidental inheritance of setup in QueryPerformanceTests
170200
// and to ensure the database resetting cannot impact any other network hosts.
171201
if isRunningInCI() {
172-
self.host = "db-\(port)"
202+
self.host = "db-\(index)"
203+
self.port = 5432
173204
} else {
174205
self.host = Environment.get("DATABASE_HOST")!
175206
precondition(["localhost", "postgres", "host.docker.internal"].contains(host),
176207
"DATABASE_HOST must be a local db, was: \(host)")
208+
self.port = index
177209
}
178-
self.port = port
179210
self.username = Environment.get("DATABASE_USERNAME")!
180211
self.password = Environment.get("DATABASE_PASSWORD")!
181212
}
182213
}
183214

184215
func setup(for environment: Environment) async throws {
185-
let details = ConnectionDetails(port: port)
186-
187216
// Create initial db snapshot
188-
try await createSchema(environment, details: details)
189-
try await createSnapshot(details: details)
217+
try await createSchema(environment)
218+
try await createSnapshot()
190219
}
191220

192-
func createSchema(_ environment: Environment, details: ConnectionDetails) async throws {
221+
func createSchema(_ environment: Environment) async throws {
193222
let start = Date()
194223
print("ℹ️ \(#function) start")
195224
defer { print("ℹ️ \(#function) end", Date().timeIntervalSince(start)) }
196225
do {
197-
try await _withDatabase("postgres", details: details, timeout: .seconds(1)) { // Connect to `postgres` db in order to reset the test db
226+
try await _withDatabase("postgres", details: connectionDetails, timeout: .seconds(5)) { // Connect to `postgres` db in order to reset the test db
198227
let databaseName = Environment.get("DATABASE_NAME")!
199228
try await $0.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(databaseName) WITH (FORCE)"))
200229
try await $0.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(databaseName)"))
@@ -203,7 +232,7 @@ extension DatabasePool.Database {
203232
do { // Use autoMigrate to spin up the schema
204233
let app = try await Application.make(environment)
205234
app.logger = .init(label: "noop") { _ in SwiftLogNoOpLogHandler() }
206-
try await configure(app, databasePort: port)
235+
try await configure(app, databasePort: connectionDetails.port)
207236
try await app.autoMigrate()
208237
try await app.asyncShutdown()
209238
}
@@ -213,14 +242,14 @@ extension DatabasePool.Database {
213242
}
214243
}
215244

216-
func createSnapshot(details: ConnectionDetails) async throws {
245+
func createSnapshot() async throws {
217246
let start = Date()
218247
print("ℹ️ \(#function) start")
219248
defer { print("ℹ️ \(#function) end", Date().timeIntervalSince(start)) }
220249
let original = Environment.get("DATABASE_NAME")!
221250
let snapshot = original + "_snapshot"
222251
do {
223-
try await _withDatabase("postgres", details: details, timeout: .seconds(1)) { client in
252+
try await _withDatabase("postgres", details: connectionDetails, timeout: .seconds(5)) { client in
224253
try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(snapshot) WITH (FORCE)"))
225254
try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(snapshot) TEMPLATE \(original)"))
226255
}

0 commit comments

Comments
 (0)