@@ -16,80 +16,84 @@ import Testing
1616
1717@testable   import  DispatchAsync
1818
19- @available ( macOS 13 ,  iOS 16 ,  tvOS 16 ,  watchOS 9 ,  * )  
20- @Test ( . timeLimit( . minutes( 1 ) ) )  
21- func  asyncSemaphoreWaitSignal( )  async  throws  { 
22-     let  semaphore  =  AsyncSemaphore ( value:  1 ) 
23- 
24-     // First wait should succeed immediately and bring the count to 0
25-     await  semaphore. wait ( ) 
26- 
27-     // Launch a task that tries to wait – it should be suspended until we signal
28-     nonisolated ( unsafe)  var  didEnterCriticalSection  =  false 
29-     await  withCheckedContinuation  {  continuation in 
30-         Task  {  @Sendable   in 
31-             // Ensure the rest of this test doesn't
32-             // proceed until the Task block has started executing
33-             continuation. resume ( ) 
34- 
35-             await  semaphore. wait ( ) 
36-             didEnterCriticalSection =  true 
37-             await  semaphore. signal ( ) 
38-         } 
39-     } 
40- 
41-     // Allow the task a few cycles to reach the initial semaphore.wait()
42-     try ? await  Task . sleep ( nanoseconds:  1_000 ) 
43- 
44-     #expect( !didEnterCriticalSection)  // should still be waiting
45- 
46-     // Now release the semaphore – the waiter should proceed
47-     await  semaphore. signal ( ) 
19+ nonisolated ( unsafe)  private  var  sharedPoolCompletionCount  =  0 
20+ 
21+ @Suite ( " DispatchGroup Tests " )  
22+ class  AsyncSemaphoreTests  { 
23+     @available ( macOS 13 ,  iOS 16 ,  tvOS 16 ,  watchOS 9 ,  * )  
24+     @Test ( . timeLimit( . minutes( 1 ) ) )  
25+     func  asyncSemaphoreWaitSignal( )  async  throws  { 
26+         let  semaphore  =  AsyncSemaphore ( value:  1 ) 
27+ 
28+         // First wait should succeed immediately and bring the count to 0
29+         await  semaphore. wait ( ) 
30+ 
31+         // Launch a task that tries to wait – it should be suspended until we signal
32+         nonisolated ( unsafe)  var  didEnterCriticalSection  =  false 
33+         await  withCheckedContinuation  {  continuation in 
34+             Task  {  @Sendable   in 
35+                 // Ensure the rest of this test doesn't
36+                 // proceed until the Task block has started executing
37+                 continuation. resume ( ) 
4838
49-     // Wait for second signal to fire from inside the task above
50-     // There is a timeout on this test, so if there is a problem
51-     // we'll either hit the timeout and fail, or didEnterCriticalSection
52-     // will be false below
53-     await  semaphore. wait ( ) 
54- 
55-     #expect( didEnterCriticalSection)  // waiter must have run
56- } 
57- 
58- @Test   func  basicAsyncSemaphoreTest( )  async  throws  { 
59-     nonisolated ( unsafe)  var  sharedPoolCompletionCount  =  0 
60-     sharedPoolCompletionCount =  0  // Reset to 0 for each test run
61-     let  totalConcurrentPools  =  10 
39+                 await  semaphore. wait ( ) 
40+                 didEnterCriticalSection =  true 
41+                 await  semaphore. signal ( ) 
42+             } 
43+         } 
6244
63-     let  semaphore  =  AsyncSemaphore ( value:  1 ) 
45+         // Allow the task a few cycles to reach the initial semaphore.wait()
46+         try ? await  Task . sleep ( nanoseconds:  1_000 ) 
6447
65-     await  withTaskGroup ( of:  Void . self)  {  group in 
66-         for  _  in  0  ..<  totalConcurrentPools { 
67-             group. addTask  { 
68-                 // Wait for any other pools currently holding the semaphore
69-                 await  semaphore. wait ( ) 
48+         #expect( !didEnterCriticalSection)  // should still be waiting
7049
71-                 // Only one task should mutate counter at a time
72-                 //
73-                 // If there are issues with the semaphore, then
74-                 // we would expect to grab incorrect values here occasionally,
75-                 // which would result in an incorrect final completion count.
76-                 //
77-                 let  existingPoolCompletionCount  =  sharedPoolCompletionCount
50+         // Now release the semaphore – the waiter should proceed
51+         await  semaphore. signal ( ) 
7852
79-                 // Add artificial delay to amplify race conditions 
80-                 // Pools started shortly after this "semaphore-locked" 
81-                  // pool starts will run before this line, unless 
82-                  // this pool contains a valid lock. 
83-                  try ?   await  Task . sleep ( nanoseconds :   100 ) 
53+         // Wait for second signal to fire from inside the task above 
54+         // There is a timeout on this test, so if there is a problem 
55+         // we'll either hit the timeout and fail, or didEnterCriticalSection 
56+         // will be false below 
57+         await  semaphore . wait ( ) 
8458
85-                 sharedPoolCompletionCount =  existingPoolCompletionCount +  1 
59+         #expect( didEnterCriticalSection)  // waiter must have run
60+     } 
8661
87-                 // When we exit this flow, release our hold on the semaphore
88-                 await  semaphore. signal ( ) 
62+     @Test   func  basicAsyncSemaphoreTest( )  async  throws  { 
63+         sharedPoolCompletionCount =  0  // Reset to 0 for each test run
64+         let  totalConcurrentPools  =  10 
65+ 
66+         let  semaphore  =  AsyncSemaphore ( value:  1 ) 
67+ 
68+         await  withTaskGroup ( of:  Void . self)  {  group in 
69+             for  _  in  0  ..<  totalConcurrentPools { 
70+                 group. addTask  { 
71+                     // Wait for any other pools currently holding the semaphore
72+                     await  semaphore. wait ( ) 
73+ 
74+                     // Only one task should mutate counter at a time
75+                     //
76+                     // If there are issues with the semaphore, then
77+                     // we would expect to grab incorrect values here occasionally,
78+                     // which would result in an incorrect final completion count.
79+                     //
80+                     let  existingPoolCompletionCount  =  sharedPoolCompletionCount
81+ 
82+                     // Add artificial delay to amplify race conditions
83+                     // Pools started shortly after this "semaphore-locked"
84+                     // pool starts will run before this line, unless
85+                     // this pool contains a valid lock.
86+                     try ? await  Task . sleep ( nanoseconds:  100 ) 
87+ 
88+                     sharedPoolCompletionCount =  existingPoolCompletionCount +  1 
89+ 
90+                     // When we exit this flow, release our hold on the semaphore
91+                     await  semaphore. signal ( ) 
92+                 } 
8993            } 
9094        } 
91-     } 
9295
93-     // After all tasks are done, counter should be 10
94-     #expect( sharedPoolCompletionCount ==  totalConcurrentPools) 
96+         // After all tasks are done, counter should be 10
97+         #expect( sharedPoolCompletionCount ==  totalConcurrentPools) 
98+     } 
9599} 
0 commit comments