|
8 | 8 | import FeatherDatabase |
9 | 9 | import Logging |
10 | 10 | import SQLiteNIO |
11 | | -import SQLiteNIOExtras |
12 | 11 | import Testing |
13 | 12 |
|
14 | 13 | @testable import FeatherSQLiteDatabase |
| 14 | +@testable import SQLiteNIOExtras |
| 15 | + |
| 16 | +#if ServiceLifecycleSupport |
| 17 | +import ServiceLifecycleTestKit |
| 18 | +#endif |
15 | 19 |
|
16 | 20 | @Suite |
17 | 21 | struct FeatherSQLiteDatabaseTestSuite { |
@@ -1180,5 +1184,179 @@ extension FeatherSQLiteDatabaseTestSuite { |
1180 | 1184 | await serviceGroup.triggerGracefulShutdown() |
1181 | 1185 | } |
1182 | 1186 | } |
| 1187 | + |
| 1188 | + @Test |
| 1189 | + func serviceLifecycleCancellationShutsDownClient() async throws { |
| 1190 | + var logger = Logger(label: "test") |
| 1191 | + logger.logLevel = .info |
| 1192 | + |
| 1193 | + let configuration = SQLiteClient.Configuration( |
| 1194 | + storage: .memory, |
| 1195 | + logger: logger |
| 1196 | + ) |
| 1197 | + let client = SQLiteClient(configuration: configuration) |
| 1198 | + let service = SQLiteDatabaseService(client) |
| 1199 | + |
| 1200 | + try await withThrowingTaskGroup(of: Void.self) { group in |
| 1201 | + group.addTask { |
| 1202 | + try await service.run() |
| 1203 | + } |
| 1204 | + |
| 1205 | + try await Task.sleep(for: .milliseconds(100)) |
| 1206 | + group.cancelAll() |
| 1207 | + |
| 1208 | + do { |
| 1209 | + while let _ = try await group.next() {} |
| 1210 | + } |
| 1211 | + catch { |
| 1212 | + // Cancellation is expected; the shutdown is asserted below. |
| 1213 | + } |
| 1214 | + } |
| 1215 | + |
| 1216 | + do { |
| 1217 | + try await client.withConnection { _ in } |
| 1218 | + Issue.record("Expected shutdown to reject new connections.") |
| 1219 | + } |
| 1220 | + catch { |
| 1221 | + #expect(error is SQLiteConnectionPoolError) |
| 1222 | + } |
| 1223 | + } |
| 1224 | + |
| 1225 | + @Test |
| 1226 | + func serviceLifecycleGracefulShutdownShutsDownClient() async throws { |
| 1227 | + var logger = Logger(label: "test") |
| 1228 | + logger.logLevel = .info |
| 1229 | + |
| 1230 | + let configuration = SQLiteClient.Configuration( |
| 1231 | + storage: .memory, |
| 1232 | + logger: logger |
| 1233 | + ) |
| 1234 | + let client = SQLiteClient(configuration: configuration) |
| 1235 | + let service = SQLiteDatabaseService(client) |
| 1236 | + let serviceGroup = ServiceGroup( |
| 1237 | + services: [service], |
| 1238 | + logger: logger |
| 1239 | + ) |
| 1240 | + |
| 1241 | + try await withThrowingTaskGroup(of: Void.self) { group in |
| 1242 | + group.addTask { |
| 1243 | + try await serviceGroup.run() |
| 1244 | + } |
| 1245 | + |
| 1246 | + try await Task.sleep(for: .milliseconds(100)) |
| 1247 | + await serviceGroup.triggerGracefulShutdown() |
| 1248 | + |
| 1249 | + do { |
| 1250 | + while let _ = try await group.next() {} |
| 1251 | + } |
| 1252 | + catch { |
| 1253 | + Issue.record(error) |
| 1254 | + } |
| 1255 | + } |
| 1256 | + |
| 1257 | + do { |
| 1258 | + try await client.withConnection { _ in } |
| 1259 | + Issue.record("Expected shutdown to reject new connections.") |
| 1260 | + } |
| 1261 | + catch { |
| 1262 | + #expect(error is SQLiteConnectionPoolError) |
| 1263 | + } |
| 1264 | + } |
| 1265 | + |
| 1266 | + @Test |
| 1267 | + func cancellationErrorTrigger() async throws { |
| 1268 | + var logger = Logger(label: "test") |
| 1269 | + logger.logLevel = .info |
| 1270 | + |
| 1271 | + let configuration = SQLiteClient.Configuration( |
| 1272 | + storage: .memory, |
| 1273 | + logger: logger |
| 1274 | + ) |
| 1275 | + let client = SQLiteClient(configuration: configuration) |
| 1276 | + let database = SQLiteDatabaseClient( |
| 1277 | + client: client, |
| 1278 | + logger: logger |
| 1279 | + ) |
| 1280 | + |
| 1281 | + enum MigrationError: Error { |
| 1282 | + case generic |
| 1283 | + } |
| 1284 | + |
| 1285 | + struct MigrationService: Service { |
| 1286 | + let database: any DatabaseClient |
| 1287 | + |
| 1288 | + func run() async throws { |
| 1289 | + let result = try await database.withConnection { connection in |
| 1290 | + try await connection.run( |
| 1291 | + query: #""" |
| 1292 | + SELECT sqlite_version() AS "version" |
| 1293 | + """# |
| 1294 | + ) { try await $0.collect().first } |
| 1295 | + } |
| 1296 | + let version = try result? |
| 1297 | + .decode( |
| 1298 | + column: "version", |
| 1299 | + as: String.self |
| 1300 | + ) |
| 1301 | + #expect(version?.split(separator: ".").count == 3) |
| 1302 | + |
| 1303 | + throw MigrationError.generic |
| 1304 | + } |
| 1305 | + } |
| 1306 | + |
| 1307 | + let serviceGroup = ServiceGroup( |
| 1308 | + configuration: .init( |
| 1309 | + services: [ |
| 1310 | + .init( |
| 1311 | + service: SQLiteDatabaseService(client) |
| 1312 | + ), |
| 1313 | + .init( |
| 1314 | + service: MigrationService(database: database), |
| 1315 | + successTerminationBehavior: .gracefullyShutdownGroup, |
| 1316 | + failureTerminationBehavior: .cancelGroup |
| 1317 | + ), |
| 1318 | + ], |
| 1319 | + logger: logger |
| 1320 | + ) |
| 1321 | + ) |
| 1322 | + |
| 1323 | + do { |
| 1324 | + try await serviceGroup.run() |
| 1325 | + Issue.record("Service group should fail.") |
| 1326 | + } |
| 1327 | + catch let error as MigrationError { |
| 1328 | + #expect(error == .generic) |
| 1329 | + } |
| 1330 | + catch { |
| 1331 | + Issue.record("Service group should throw a generic Migration error") |
| 1332 | + } |
| 1333 | + } |
| 1334 | + |
| 1335 | + @Test |
| 1336 | + func serviceGracefulShutdown() async throws { |
| 1337 | + var logger = Logger(label: "test") |
| 1338 | + logger.logLevel = .info |
| 1339 | + |
| 1340 | + let configuration = SQLiteClient.Configuration( |
| 1341 | + storage: .memory, |
| 1342 | + logger: logger |
| 1343 | + ) |
| 1344 | + let client = SQLiteClient(configuration: configuration) |
| 1345 | + let service = SQLiteDatabaseService(client) |
| 1346 | + |
| 1347 | + try await testGracefulShutdown { trigger in |
| 1348 | + try await withThrowingTaskGroup { group in |
| 1349 | + let serviceGroup = ServiceGroup( |
| 1350 | + services: [service], |
| 1351 | + logger: logger |
| 1352 | + ) |
| 1353 | + group.addTask { try await serviceGroup.run() } |
| 1354 | + |
| 1355 | + trigger.triggerGracefulShutdown() |
| 1356 | + |
| 1357 | + try await group.waitForAll() |
| 1358 | + } |
| 1359 | + } |
| 1360 | + } |
1183 | 1361 | } |
1184 | 1362 | #endif |
0 commit comments