@@ -518,4 +518,146 @@ class ValueObservationTests: GRDBTestCase {
518518 try test ( makeDatabaseQueue ( ) )
519519 try test ( makeDatabasePool ( ) )
520520 }
521+
522+ #if swift(>=5.5)
523+ @available ( macOS 12 , iOS 15 , tvOS 15 , watchOS 8 , * )
524+ 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 ] = [ ]
537+
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 " ) }
541+ }
542+ return counts
543+ }
544+
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 )
552+ }
553+
554+ @available ( macOS 12 , iOS 15 , tvOS 15 , watchOS 8 , * )
555+ 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 ] = [ ]
568+
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 " ) }
572+ }
573+ return counts
574+ }
575+
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 )
583+ }
584+
585+ @available ( macOS 12 , iOS 15 , tvOS 15 , watchOS 8 , * )
586+ 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 ( ) } )
596+
597+ let task = Task { ( ) -> [ Int ] in
598+ var counts : [ Int ] = [ ]
599+
600+ for try await count in observation. values ( in: dbQueue) {
601+ counts. append ( count)
602+ if count == 2 {
603+ break
604+ } else {
605+ try await dbQueue. write { try $0. execute ( sql: " INSERT INTO t DEFAULT VALUES " ) }
606+ }
607+ }
608+ return counts
609+ }
610+
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 )
618+ }
619+
620+ @available ( macOS 12 , iOS 15 , tvOS 15 , watchOS 8 , * )
621+ 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 " ) }
643+ }
644+ }
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 { }
652+ }
653+ XCTFail ( " Expected error " )
654+ } catch is TestError {
655+ } catch {
656+ XCTFail ( " Unexpected error \( error) " )
657+ }
658+
659+ // A value was observed, and observation was ended
660+ wait ( for: [ valueExpectation, cancellationExpectation] , timeout: 2 )
661+ }
662+ #endif
521663}
0 commit comments