@@ -38,13 +38,13 @@ test.group('JobPool', () => {
3838 cleanup,
3939 } ) => {
4040 const execution = Promise . withResolvers < void > ( )
41- const originalCatch = execution . promise . catch . bind ( execution . promise )
41+ const originalThen = execution . promise . then . bind ( execution . promise )
4242 let observed = false
4343
44- execution . promise . catch = ( ( ... args : Parameters < Promise < void > [ 'catch' ] > ) => {
45- observed = true
46- return originalCatch ( ... args )
47- } ) as Promise < void > [ 'catch ' ]
44+ execution . promise . then = ( ( onFulfilled , onRejected ) => {
45+ observed = onRejected !== undefined
46+ return originalThen ( onFulfilled , onRejected )
47+ } ) as Promise < void > [ 'then ' ]
4848
4949 const pool = new JobPool ( )
5050 cleanup ( async ( ) => {
@@ -57,6 +57,41 @@ test.group('JobPool', () => {
5757 assert . isTrue ( observed )
5858 } )
5959
60+ test ( 'attaches one settlement handler while a completion wait remains pending' , async ( {
61+ assert,
62+ cleanup,
63+ } ) => {
64+ const execution = Promise . withResolvers < void > ( )
65+ let attachments = 0
66+ const observablePromise = {
67+ then ( onFulfilled , onRejected ) {
68+ attachments ++
69+ return execution . promise . then ( onFulfilled , onRejected )
70+ } ,
71+ catch ( onRejected ) {
72+ return this . then ( undefined , onRejected )
73+ } ,
74+ } as Promise < void >
75+
76+ const pool = new JobPool ( )
77+ cleanup ( async ( ) => {
78+ execution . resolve ( )
79+ await pool . drain ( )
80+ } )
81+
82+ pool . add ( createJob ( 'observed-once' ) , 'default' , observablePromise )
83+ const completion = pool . waitForNextCompletion ( )
84+
85+ for ( let tick = 0 ; tick < 10 ; tick ++ ) {
86+ await setTimeout ( 0 )
87+ }
88+
89+ assert . equal ( attachments , 1 )
90+
91+ execution . resolve ( )
92+ await completion
93+ } )
94+
6095 test ( 'should check capacity correctly' , ( { assert } ) => {
6196 const pool = new JobPool ( )
6297
@@ -85,6 +120,81 @@ test.group('JobPool', () => {
85120 assert . equal ( pool . size , 1 )
86121 } )
87122
123+ test ( 'returns a completion queued before waiting begins' , async ( { assert } ) => {
124+ const pool = new JobPool ( )
125+ pool . add ( createJob ( 'already-completed' ) , 'default' , Promise . resolve ( ) )
126+
127+ await setTimeout ( 0 )
128+
129+ const completed = await pool . waitForNextCompletion ( )
130+ assert . equal ( completed . job . id , 'already-completed' )
131+ assert . isTrue ( pool . isEmpty ( ) )
132+ } )
133+
134+ test ( 'returns a newer job that settles after waiting begins' , async ( { assert } ) => {
135+ const pool = new JobPool ( )
136+ const longExecution = Promise . withResolvers < void > ( )
137+ pool . add ( createJob ( 'long-running' ) , 'default' , longExecution . promise )
138+
139+ const completion = pool . waitForNextCompletion ( )
140+ pool . add ( createJob ( 'newer-fast-job' ) , 'default' , Promise . resolve ( ) )
141+
142+ assert . equal ( ( await completion ) . job . id , 'newer-fast-job' )
143+ longExecution . resolve ( )
144+ await pool . drain ( )
145+ } )
146+
147+ test ( 'ignores a stale completion after an active job id is replaced' , async ( { assert } ) => {
148+ const pool = new JobPool ( )
149+ const staleExecution = Promise . withResolvers < void > ( )
150+ const currentExecution = Promise . withResolvers < void > ( )
151+
152+ pool . add ( createJob ( 'reused-id' ) , 'stale' , staleExecution . promise )
153+ staleExecution . resolve ( )
154+ await setTimeout ( 0 )
155+ pool . add ( createJob ( 'reused-id' ) , 'current' , currentExecution . promise )
156+
157+ let delivered = false
158+ const completion = pool . waitForNextCompletion ( ) . then ( ( entry ) => {
159+ delivered = true
160+ return entry
161+ } )
162+
163+ await setTimeout ( 0 )
164+ assert . isFalse ( delivered )
165+
166+ currentExecution . resolve ( )
167+ assert . equal ( ( await completion ) . queue , 'current' )
168+ assert . isTrue ( pool . isEmpty ( ) )
169+ } )
170+
171+ test ( 'returns every job once in settlement order' , async ( { assert } ) => {
172+ const pool = new JobPool ( )
173+ const first = Promise . withResolvers < void > ( )
174+ const second = Promise . withResolvers < void > ( )
175+ const third = Promise . withResolvers < void > ( )
176+
177+ pool . add ( createJob ( 'first' ) , 'default' , first . promise )
178+ pool . add ( createJob ( 'second' ) , 'default' , second . promise )
179+ pool . add ( createJob ( 'third' ) , 'default' , third . promise )
180+
181+ second . resolve ( )
182+ await setTimeout ( 0 )
183+ first . resolve ( )
184+ await setTimeout ( 0 )
185+ third . resolve ( )
186+
187+ assert . deepEqual (
188+ [
189+ ( await pool . waitForNextCompletion ( ) ) . job . id ,
190+ ( await pool . waitForNextCompletion ( ) ) . job . id ,
191+ ( await pool . waitForNextCompletion ( ) ) . job . id ,
192+ ] ,
193+ [ 'second' , 'first' , 'third' ]
194+ )
195+ assert . isTrue ( pool . isEmpty ( ) )
196+ } )
197+
88198 test ( 'should remove job from pool after completion' , async ( { assert } ) => {
89199 const pool = new JobPool ( )
90200
@@ -173,4 +283,22 @@ test.group('JobPool', () => {
173283
174284 assert . isTrue ( pool . isEmpty ( ) )
175285 } )
286+
287+ test ( 'drain clears completions that settled before they were consumed' , async ( { assert } ) => {
288+ const pool = new JobPool ( )
289+ const running = Promise . withResolvers < void > ( )
290+
291+ pool . add ( createJob ( 'settled' ) , 'default' , Promise . resolve ( ) )
292+ pool . add ( createJob ( 'running' ) , 'default' , running . promise )
293+ await setTimeout ( 0 )
294+
295+ const drain = pool . drain ( )
296+ running . resolve ( )
297+ await drain
298+
299+ assert . isTrue ( pool . isEmpty ( ) )
300+
301+ pool . add ( createJob ( 'after-drain' ) , 'default' , Promise . resolve ( ) )
302+ assert . equal ( ( await pool . waitForNextCompletion ( ) ) . job . id , 'after-drain' )
303+ } )
176304} )
0 commit comments