Skip to content

Commit 82676d3

Browse files
committed
Move db setup out of TestSupport, make non-static
1 parent 25b3175 commit 82676d3

File tree

2 files changed

+113
-132
lines changed

2 files changed

+113
-132
lines changed

Tests/AppTests/Helpers/DatabasePool.swift

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ import ShellOut
2121
actor DatabasePool {
2222
typealias DatabaseID = UUID
2323

24+
#warning("rename to Database")
2425
struct DatabaseInfo: Hashable {
2526
var id: DatabaseID
2627
var port: Int
2728
}
2829

29-
static let shared = DatabasePool(maxCount: 4)
30+
static let shared = DatabasePool(maxCount: 8)
3031

3132
var maxCount: Int
3233

@@ -63,7 +64,7 @@ actor DatabasePool {
6364
func withDatabase(_ operation: @Sendable (DatabaseInfo) async throws -> Void) async throws {
6465
let dbID = try await retainDatabase()
6566
do {
66-
print("⚠️ available", availableDatabases.map(\.port).sorted())
67+
// print("⚠️ available", availableDatabases.map(\.port).sorted())
6768
try await operation(dbID)
6869
try await releaseDatabase(dbInfo: dbID)
6970
} catch {
@@ -100,8 +101,114 @@ actor DatabasePool {
100101

101102
private func removeDB(dbInfo: DatabaseInfo, maxAttempts: Int = 3) async throws {
102103
try await run(maxAttempts: 3) { attempt in
103-
print("⚠️ Removing DB \(dbInfo.id) on port \(dbInfo.port) (attempt: \(attempt))")
104+
// print("⚠️ Removing DB \(dbInfo.id) on port \(dbInfo.port) (attempt: \(attempt))")
104105
try await ShellOut.shellOut(to: .removeDB(id: dbInfo.id))
105106
}
106107
}
107108
}
109+
110+
111+
import PostgresNIO
112+
import Vapor
113+
114+
extension DatabasePool.DatabaseInfo {
115+
116+
func setupDb(_ environment: Environment) async throws {
117+
await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton))
118+
119+
// Ensure DATABASE_HOST is from a restricted set db hostnames and nothing else.
120+
// This is safeguard against accidental inheritance of setup in QueryPerformanceTests
121+
// and to ensure the database resetting cannot impact any other network hosts.
122+
let host = Environment.get("DATABASE_HOST")!
123+
precondition(["localhost", "postgres", "host.docker.internal"].contains(host),
124+
"DATABASE_HOST must be a local db, was: \(host)")
125+
126+
let testDbName = Environment.get("DATABASE_NAME")!
127+
let snapshotName = testDbName + "_snapshot"
128+
129+
// Create initial db snapshot
130+
try await createSchema(environment, databaseName: testDbName)
131+
try await createSnapshot(original: testDbName, snapshot: snapshotName, environment: environment)
132+
133+
try await restoreSnapshot(original: testDbName, snapshot: snapshotName, environment: environment)
134+
}
135+
136+
func createSchema(_ environment: Environment, databaseName: String) async throws {
137+
do {
138+
try await _withDatabase("postgres", port: port, environment) { // Connect to `postgres` db in order to reset the test db
139+
try await $0.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(databaseName) WITH (FORCE)"))
140+
try await $0.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(databaseName)"))
141+
}
142+
143+
do { // Use autoMigrate to spin up the schema
144+
let app = try await Application.make(environment)
145+
app.logger = .init(label: "noop") { _ in SwiftLogNoOpLogHandler() }
146+
try await configure(app, databasePort: port)
147+
try await app.autoMigrate()
148+
try await app.asyncShutdown()
149+
}
150+
} catch {
151+
print("Create schema failed with error: ", String(reflecting: error))
152+
throw error
153+
}
154+
}
155+
156+
func createSnapshot(original: String, snapshot: String, environment: Environment) async throws {
157+
do {
158+
try await _withDatabase("postgres", port: port, environment) { client in
159+
try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(snapshot) WITH (FORCE)"))
160+
try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(snapshot) TEMPLATE \(original)"))
161+
}
162+
} catch {
163+
print("Create snapshot failed with error: ", String(reflecting: error))
164+
throw error
165+
}
166+
}
167+
168+
func restoreSnapshot(original: String,
169+
snapshot: String,
170+
environment: Environment) async throws {
171+
// delete db and re-create from snapshot
172+
do {
173+
try await _withDatabase("postgres", port: port, environment) { client in
174+
try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(original) WITH (FORCE)"))
175+
try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(original) TEMPLATE \(snapshot)"))
176+
}
177+
} catch {
178+
print("Restore snapshot failed with error: ", String(reflecting: error))
179+
throw error
180+
}
181+
}
182+
183+
}
184+
185+
186+
private func connect(to databaseName: String,
187+
port: Int,
188+
_ environment: Environment) async throws -> PostgresClient {
189+
#warning("don't load dot file, just pass in host, port, username, password tuple - or make this a method and the other values properties")
190+
await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton))
191+
let host = Environment.get("DATABASE_HOST")!
192+
let username = Environment.get("DATABASE_USERNAME")!
193+
let password = Environment.get("DATABASE_PASSWORD")!
194+
195+
let config = PostgresClient.Configuration(host: host, port: port, username: username, password: password, database: databaseName, tls: .disable)
196+
197+
return .init(configuration: config)
198+
}
199+
200+
201+
private func _withDatabase(_ databaseName: String,
202+
port: Int,
203+
_ environment: Environment,
204+
_ query: @escaping (PostgresClient) async throws -> Void) async throws {
205+
let client = try await connect(to: databaseName, port: port, environment)
206+
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
207+
taskGroup.addTask { await client.run() }
208+
209+
try await query(client)
210+
211+
taskGroup.cancelAll()
212+
}
213+
}
214+

Tests/AppTests/Helpers/TestSupport.swift

Lines changed: 3 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ func withApp(
3030
}
3131

3232
try await DatabasePool.shared.withDatabase { dbInfo in
33-
try await TestSupport.setupDb(environment, databasePort: dbInfo.port)
34-
let app = try await TestSupport.setupApp(environment, databasePort: dbInfo.port)
33+
try await dbInfo.setupDb(environment)
34+
let app = try await Application.make(environment)
35+
try await configure(app, databasePort: dbInfo.port)
3536

3637
return try await run {
3738
try await setup(app)
@@ -48,130 +49,3 @@ func withApp(
4849
func isRunningInCI() -> Bool {
4950
ProcessInfo.processInfo.environment.keys.contains("GITHUB_WORKFLOW")
5051
}
51-
52-
53-
enum TestSupport {
54-
55-
static func setupApp(_ environment: Environment, databasePort: Int? = nil) async throws -> Application {
56-
let app = try await Application.make(environment)
57-
try await configure(app, databasePort: databasePort)
58-
return app
59-
}
60-
61-
62-
static func setupDb(_ environment: Environment, databasePort: Int? = nil) async throws {
63-
await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton))
64-
65-
// Ensure DATABASE_HOST is from a restricted set db hostnames and nothing else.
66-
// This is safeguard against accidental inheritance of setup in QueryPerformanceTests
67-
// and to ensure the database resetting cannot impact any other network hosts.
68-
let host = Environment.get("DATABASE_HOST")
69-
let databasePort = databasePort ?? Environment.get("DATABASE_PORT").flatMap(Int.init)!
70-
precondition(["localhost", "postgres", "host.docker.internal"].contains(host),
71-
"DATABASE_HOST must be a local db, was: \(host)")
72-
73-
let testDbName = Environment.get("DATABASE_NAME")!
74-
let snapshotName = testDbName + "_snapshot"
75-
76-
// Create initial db snapshot on first run
77-
try await snapshotCreated.withValue { snapshotCreated in
78-
if !snapshotCreated {
79-
try await createSchema(environment, databaseName: testDbName, databasePort: databasePort)
80-
try await createSnapshot(original: testDbName, snapshot: snapshotName, databasePort: databasePort, environment: environment)
81-
snapshotCreated = true
82-
}
83-
}
84-
85-
try await restoreSnapshot(original: testDbName, snapshot: snapshotName, databasePort: databasePort, environment: environment)
86-
}
87-
88-
89-
static func createSchema(_ environment: Environment,
90-
databaseName: String,
91-
databasePort: Int) async throws {
92-
do {
93-
try await withDatabase("postgres", port: databasePort, environment) { // Connect to `postgres` db in order to reset the test db
94-
try await $0.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(databaseName) WITH (FORCE)"))
95-
try await $0.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(databaseName)"))
96-
}
97-
98-
do { // Use autoMigrate to spin up the schema
99-
let app = try await Application.make(environment)
100-
app.logger = .init(label: "noop") { _ in SwiftLogNoOpLogHandler() }
101-
try await configure(app, databasePort: databasePort)
102-
try await app.autoMigrate()
103-
try await app.asyncShutdown()
104-
}
105-
} catch {
106-
print("Create schema failed with error: ", String(reflecting: error))
107-
throw error
108-
}
109-
}
110-
111-
112-
static func createSnapshot(original: String,
113-
snapshot: String,
114-
databasePort: Int,
115-
environment: Environment) async throws {
116-
do {
117-
try await withDatabase("postgres", port: databasePort, environment) { client in
118-
try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(snapshot) WITH (FORCE)"))
119-
try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(snapshot) TEMPLATE \(original)"))
120-
}
121-
} catch {
122-
print("Create snapshot failed with error: ", String(reflecting: error))
123-
throw error
124-
}
125-
}
126-
127-
128-
static func restoreSnapshot(original: String,
129-
snapshot: String,
130-
databasePort: Int,
131-
environment: Environment) async throws {
132-
// delete db and re-create from snapshot
133-
do {
134-
try await withDatabase("postgres", port: databasePort, environment) { client in
135-
try await client.query(PostgresQuery(unsafeSQL: "DROP DATABASE IF EXISTS \(original) WITH (FORCE)"))
136-
try await client.query(PostgresQuery(unsafeSQL: "CREATE DATABASE \(original) TEMPLATE \(snapshot)"))
137-
}
138-
} catch {
139-
print("Restore snapshot failed with error: ", String(reflecting: error))
140-
throw error
141-
}
142-
}
143-
144-
145-
static let snapshotCreated = ActorIsolated(false)
146-
147-
}
148-
149-
150-
private func connect(to databaseName: String,
151-
port: Int,
152-
_ environment: Environment) async throws -> PostgresClient {
153-
await DotEnvFile.load(for: environment, fileio: .init(threadPool: .singleton))
154-
let host = Environment.get("DATABASE_HOST")!
155-
let username = Environment.get("DATABASE_USERNAME")!
156-
let password = Environment.get("DATABASE_PASSWORD")!
157-
158-
let config = PostgresClient.Configuration(host: host, port: port, username: username, password: password, database: databaseName, tls: .disable)
159-
160-
return .init(configuration: config)
161-
}
162-
163-
164-
private func withDatabase(_ databaseName: String,
165-
port: Int,
166-
_ environment: Environment,
167-
_ query: @escaping (PostgresClient) async throws -> Void) async throws {
168-
let client = try await connect(to: databaseName, port: port, environment)
169-
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
170-
taskGroup.addTask { await client.run() }
171-
172-
try await query(client)
173-
174-
taskGroup.cancelAll()
175-
}
176-
}
177-

0 commit comments

Comments
 (0)