Skip to content

Commit bbd7716

Browse files
committed
test AsyncValueObservation with all DatabaseQueue & Pool
Also use .trackingConstantRegion since it triggers optimizations
1 parent 9a28c67 commit bbd7716

File tree

2 files changed

+163
-105
lines changed

2 files changed

+163
-105
lines changed

Tests/GRDBTests/ValueObservationRecorder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ extension XCTestCase {
394394
// Both missing and repeated values are allowed in the recorded values.
395395
// This is because of asynchronous DatabasePool observations.
396396
if recordedSuffix.isEmpty {
397-
XCTFail("missing expected value \(value) - \(message())", file: file, line: line)
397+
XCTFail("missing expected value \(value) - \(message()) in \(recordedValues)", file: file, line: line)
398398
}
399399
}
400400

Tests/GRDBTests/ValueObservationTests.swift

Lines changed: 162 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -520,144 +520,202 @@ class ValueObservationTests: GRDBTestCase {
520520
}
521521

522522
#if swift(>=5.5)
523+
// MARK: - Async Await
524+
523525
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
524526
func testAsyncAwait_values_prefix() async throws {
525-
let dbQueue = try makeDatabaseQueue()
526-
527-
// We need something to change
528-
try await dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
529-
530-
let cancellationExpectation = expectation(description: "cancelled")
531-
let observation = ValueObservation
532-
.tracking { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
533-
.handleEvents(didCancel: { cancellationExpectation.fulfill() })
534-
535-
let task = Task { () -> [Int] in
536-
var counts: [Int] = []
527+
func test(writer: DatabaseWriter) async throws {
528+
// We need something to change
529+
try await writer.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
530+
531+
let cancellationExpectation = expectation(description: "cancelled")
532+
let observation = ValueObservation
533+
.trackingConstantRegion { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
534+
.handleEvents(didCancel: { cancellationExpectation.fulfill() })
537535

538-
for try await count in observation.values(in: dbQueue).prefix(3) {
539-
counts.append(count)
540-
try await dbQueue.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
536+
let task = Task { () -> [Int] in
537+
var counts: [Int] = []
538+
539+
for try await count in try observation.values(in: writer).prefix(while: { $0 < 3 }) {
540+
counts.append(count)
541+
try await writer.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
542+
}
543+
return counts
541544
}
542-
return counts
545+
546+
let counts = try await task.value
547+
548+
// All values were published
549+
assertValueObservationRecordingMatch(recorded: counts, expected: [0, 1, 2])
550+
551+
// Observation was ended
552+
wait(for: [cancellationExpectation], timeout: 2)
543553
}
544554

545-
let counts = try await task.value
546-
547-
// All values were published
548-
XCTAssertEqual(counts, [0, 1, 2])
549-
550-
// Observation was ended
551-
wait(for: [cancellationExpectation], timeout: 2)
555+
try await AsyncTest(test)
556+
.run { DatabaseQueue() }
557+
.runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
558+
.runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
552559
}
553560

554561
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
555562
func testAsyncAwait_values_prefix_immediate_scheduling() async throws {
556-
let dbQueue = try makeDatabaseQueue()
557-
558-
// We need something to change
559-
try await dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
560-
561-
let cancellationExpectation = expectation(description: "cancelled")
562-
let observation = ValueObservation
563-
.tracking { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
564-
.handleEvents(didCancel: { cancellationExpectation.fulfill() })
565-
566-
let task = Task { @MainActor () -> [Int] in
567-
var counts: [Int] = []
563+
func test(writer: DatabaseWriter) async throws {
564+
// We need something to change
565+
try await writer.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
568566

569-
for try await count in observation.values(in: dbQueue, scheduling: .immediate).prefix(3) {
570-
counts.append(count)
571-
try await dbQueue.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
567+
let cancellationExpectation = expectation(description: "cancelled")
568+
let observation = ValueObservation
569+
.trackingConstantRegion { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
570+
.handleEvents(didCancel: { cancellationExpectation.fulfill() })
571+
572+
let task = Task { @MainActor () -> [Int] in
573+
var counts: [Int] = []
574+
575+
for try await count in try observation.values(in: writer, scheduling: .immediate).prefix(while: { $0 < 3 }) {
576+
counts.append(count)
577+
try await writer.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
578+
}
579+
return counts
572580
}
573-
return counts
581+
582+
let counts = try await task.value
583+
584+
// All values were published
585+
assertValueObservationRecordingMatch(recorded: counts, expected: [0, 1, 2])
586+
587+
// Observation was ended
588+
wait(for: [cancellationExpectation], timeout: 2)
574589
}
575590

576-
let counts = try await task.value
577-
578-
// All values were published
579-
XCTAssertEqual(counts, [0, 1, 2])
580-
581-
// Observation was ended
582-
wait(for: [cancellationExpectation], timeout: 2)
591+
try await AsyncTest(test)
592+
.run { DatabaseQueue() }
593+
.runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
594+
.runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
583595
}
584596

585597
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
586598
func testAsyncAwait_values_break() async throws {
587-
let dbQueue = try makeDatabaseQueue()
588-
589-
// We need something to change
590-
try await dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
591-
592-
let cancellationExpectation = expectation(description: "cancelled")
593-
let observation = ValueObservation
594-
.tracking { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
595-
.handleEvents(didCancel: { cancellationExpectation.fulfill() })
599+
func test(writer: DatabaseWriter) async throws {
600+
// We need something to change
601+
try await writer.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
602+
603+
let cancellationExpectation = expectation(description: "cancelled")
604+
let observation = ValueObservation
605+
.trackingConstantRegion { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
606+
.handleEvents(didCancel: { cancellationExpectation.fulfill() })
607+
608+
let task = Task { () -> [Int] in
609+
var counts: [Int] = []
610+
611+
for try await count in observation.values(in: writer) {
612+
counts.append(count)
613+
if count == 2 {
614+
break
615+
} else {
616+
try await writer.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
617+
}
618+
}
619+
return counts
620+
}
621+
622+
let counts = try await task.value
623+
624+
// All values were published
625+
assertValueObservationRecordingMatch(recorded: counts, expected: [0, 1, 2])
626+
627+
// Observation was ended
628+
wait(for: [cancellationExpectation], timeout: 2)
629+
}
596630

597-
let task = Task { () -> [Int] in
598-
var counts: [Int] = []
631+
try await AsyncTest(test)
632+
.run { DatabaseQueue() }
633+
.runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
634+
.runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
635+
}
636+
637+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
638+
func testAsyncAwait_values_immediate_break() async throws {
639+
func test(writer: DatabaseWriter) async throws {
640+
// We need something to change
641+
try await writer.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
642+
643+
let cancellationExpectation = expectation(description: "cancelled")
644+
let observation = ValueObservation
645+
.trackingConstantRegion { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
646+
.handleEvents(didCancel: { cancellationExpectation.fulfill() })
599647

600-
for try await count in observation.values(in: dbQueue) {
601-
counts.append(count)
602-
if count == 2 {
648+
let task = Task { @MainActor () -> [Int] in
649+
var counts: [Int] = []
650+
651+
for try await count in observation.values(in: writer, scheduling: .immediate) {
652+
counts.append(count)
603653
break
604-
} else {
605-
try await dbQueue.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
606654
}
655+
return counts
607656
}
608-
return counts
657+
658+
let counts = try await task.value
659+
660+
// A single value was published
661+
assertValueObservationRecordingMatch(recorded: counts, expected: [0])
662+
663+
// Observation was ended
664+
wait(for: [cancellationExpectation], timeout: 2)
609665
}
610666

611-
let counts = try await task.value
612-
613-
// All values were published
614-
XCTAssertEqual(counts, [0, 1, 2])
615-
616-
// Observation was ended
617-
wait(for: [cancellationExpectation], timeout: 2)
667+
try await AsyncTest(test)
668+
.run { DatabaseQueue() }
669+
.runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
670+
.runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
618671
}
619672

620673
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
621674
func testAsyncAwait_values_cancelled() async throws {
622-
let dbQueue = try makeDatabaseQueue()
623-
624-
// We need something to change
625-
try await dbQueue.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
626-
627-
let cancellationExpectation = expectation(description: "cancelled")
628-
let valueExpectation = expectation(description: "value")
629-
valueExpectation.assertForOverFulfill = false
630-
let observation = ValueObservation
631-
.tracking { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
632-
.handleEvents(
633-
didReceiveValue: { _ in valueExpectation.fulfill() },
634-
didCancel: { cancellationExpectation.fulfill() })
635-
636-
struct TestError: Error { }
637-
do {
638-
try await withThrowingTaskGroup(of: Void.self) { group in
639-
group.addTask {
640-
// Infinite loop
641-
for try await _ in observation.values(in: dbQueue) {
642-
try await dbQueue.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
675+
func test(writer: DatabaseWriter) async throws {
676+
// We need something to change
677+
try await writer.write { try $0.execute(sql: "CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT)") }
678+
679+
let cancellationExpectation = expectation(description: "cancelled")
680+
let valueExpectation = expectation(description: "value")
681+
valueExpectation.assertForOverFulfill = false
682+
let observation = ValueObservation
683+
.trackingConstantRegion { try Int.fetchOne($0, sql: "SELECT COUNT(*) FROM t")! }
684+
.handleEvents(
685+
didReceiveValue: { _ in valueExpectation.fulfill() },
686+
didCancel: { cancellationExpectation.fulfill() })
687+
688+
struct TestError: Error { }
689+
do {
690+
try await withThrowingTaskGroup(of: Void.self) { group in
691+
group.addTask {
692+
// Infinite loop
693+
for try await _ in observation.values(in: writer) {
694+
try await writer.write { try $0.execute(sql: "INSERT INTO t DEFAULT VALUES") }
695+
}
643696
}
697+
group.addTask {
698+
// Throw after a delay
699+
try await Task.sleep(nanoseconds: 1_000_000)
700+
throw TestError()
701+
}
702+
703+
for try await _ in group { }
644704
}
645-
group.addTask {
646-
// Throw after a delay
647-
try await Task.sleep(nanoseconds: 1_000_000)
648-
throw TestError()
649-
}
650-
651-
for try await _ in group { }
705+
XCTFail("Expected error")
706+
} catch is TestError {
707+
} catch {
708+
XCTFail("Unexpected error \(error)")
652709
}
653-
XCTFail("Expected error")
654-
} catch is TestError {
655-
} catch {
656-
XCTFail("Unexpected error \(error)")
710+
711+
// A value was observed, and observation was ended
712+
wait(for: [valueExpectation, cancellationExpectation], timeout: 2)
657713
}
658714

659-
// A value was observed, and observation was ended
660-
wait(for: [valueExpectation, cancellationExpectation], timeout: 2)
715+
try await AsyncTest(test)
716+
.run { DatabaseQueue() }
717+
.runAtTemporaryDatabasePath { try DatabaseQueue(path: $0) }
718+
.runAtTemporaryDatabasePath { try DatabasePool(path: $0) }
661719
}
662720
#endif
663721
}

0 commit comments

Comments
 (0)