build: switch default to ESM
[poolifier.git] / tests / pools / selection-strategies / worker-choice-strategy-context.test.mjs
1 import { expect } from 'expect'
2 import { createStubInstance, restore, stub } from 'sinon'
3 import {
4 DynamicThreadPool,
5 FixedThreadPool,
6 WorkerChoiceStrategies
7 } from '../../../lib/index.cjs'
8 import { WorkerChoiceStrategyContext } from '../../../lib/pools/selection-strategies/worker-choice-strategy-context.cjs'
9 import { RoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/round-robin-worker-choice-strategy.cjs'
10 import { LeastUsedWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-used-worker-choice-strategy.cjs'
11 import { LeastBusyWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-busy-worker-choice-strategy.cjs'
12 import { LeastEluWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-elu-worker-choice-strategy.cjs'
13 import { FairShareWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/fair-share-worker-choice-strategy.cjs'
14 import { WeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.cjs'
15 import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.cjs'
16
17 describe('Worker choice strategy context test suite', () => {
18 const min = 1
19 const max = 3
20 let fixedPool, dynamicPool
21
22 before(() => {
23 fixedPool = new FixedThreadPool(
24 max,
25 './tests/worker-files/thread/testWorker.mjs'
26 )
27 dynamicPool = new DynamicThreadPool(
28 min,
29 max,
30 './tests/worker-files/thread/testWorker.mjs'
31 )
32 })
33
34 afterEach(() => {
35 restore()
36 })
37
38 after(async () => {
39 await fixedPool.destroy()
40 await dynamicPool.destroy()
41 })
42
43 it('Verify that constructor() initializes the context with all the available worker choice strategies', () => {
44 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
45 fixedPool
46 )
47 expect(workerChoiceStrategyContext.workerChoiceStrategies.size).toBe(
48 Object.keys(WorkerChoiceStrategies).length
49 )
50 })
51
52 it('Verify that execute() return the worker node key chosen by the strategy with fixed pool', () => {
53 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
54 fixedPool
55 )
56 const workerChoiceStrategyStub = createStubInstance(
57 RoundRobinWorkerChoiceStrategy,
58 {
59 hasPoolWorkerNodesReady: stub().returns(true),
60 choose: stub().returns(0)
61 }
62 )
63 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
64 WorkerChoiceStrategies.ROUND_ROBIN
65 )
66 workerChoiceStrategyContext.workerChoiceStrategies.set(
67 workerChoiceStrategyContext.workerChoiceStrategy,
68 workerChoiceStrategyStub
69 )
70 const chosenWorkerKey = workerChoiceStrategyContext.execute()
71 expect(
72 workerChoiceStrategyContext.workerChoiceStrategies.get(
73 workerChoiceStrategyContext.workerChoiceStrategy
74 ).choose.calledOnce
75 ).toBe(true)
76 expect(chosenWorkerKey).toBe(0)
77 })
78
79 it('Verify that execute() throws error if null or undefined is returned after retries', () => {
80 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
81 fixedPool
82 )
83 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
84 WorkerChoiceStrategies.ROUND_ROBIN
85 )
86 const workerChoiceStrategyUndefinedStub = createStubInstance(
87 RoundRobinWorkerChoiceStrategy,
88 {
89 hasPoolWorkerNodesReady: stub().returns(true),
90 choose: stub().returns(undefined)
91 }
92 )
93 workerChoiceStrategyContext.workerChoiceStrategies.set(
94 workerChoiceStrategyContext.workerChoiceStrategy,
95 workerChoiceStrategyUndefinedStub
96 )
97 expect(() => workerChoiceStrategyContext.execute()).toThrow(
98 new Error(
99 `Worker node key chosen is null or undefined after ${
100 fixedPool.info.maxSize +
101 Object.keys(workerChoiceStrategyContext.opts.weights).length
102 } retries`
103 )
104 )
105 const workerChoiceStrategyNullStub = createStubInstance(
106 RoundRobinWorkerChoiceStrategy,
107 {
108 hasPoolWorkerNodesReady: stub().returns(true),
109 choose: stub().returns(null)
110 }
111 )
112 workerChoiceStrategyContext.workerChoiceStrategies.set(
113 workerChoiceStrategyContext.workerChoiceStrategy,
114 workerChoiceStrategyNullStub
115 )
116 expect(() => workerChoiceStrategyContext.execute()).toThrow(
117 new Error(
118 `Worker node key chosen is null or undefined after ${
119 fixedPool.info.maxSize +
120 Object.keys(workerChoiceStrategyContext.opts.weights).length
121 } retries`
122 )
123 )
124 })
125
126 it('Verify that execute() retry until a worker node is ready and chosen', () => {
127 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
128 fixedPool
129 )
130 const workerChoiceStrategyStub = createStubInstance(
131 RoundRobinWorkerChoiceStrategy,
132 {
133 hasPoolWorkerNodesReady: stub()
134 .onCall(0)
135 .returns(false)
136 .onCall(1)
137 .returns(false)
138 .onCall(2)
139 .returns(false)
140 .onCall(3)
141 .returns(false)
142 .onCall(4)
143 .returns(false)
144 .returns(true),
145 choose: stub().returns(1)
146 }
147 )
148 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
149 WorkerChoiceStrategies.ROUND_ROBIN
150 )
151 workerChoiceStrategyContext.workerChoiceStrategies.set(
152 workerChoiceStrategyContext.workerChoiceStrategy,
153 workerChoiceStrategyStub
154 )
155 const chosenWorkerKey = workerChoiceStrategyContext.execute()
156 expect(
157 workerChoiceStrategyContext.workerChoiceStrategies.get(
158 workerChoiceStrategyContext.workerChoiceStrategy
159 ).hasPoolWorkerNodesReady.callCount
160 ).toBe(6)
161 expect(
162 workerChoiceStrategyContext.workerChoiceStrategies.get(
163 workerChoiceStrategyContext.workerChoiceStrategy
164 ).choose.callCount
165 ).toBe(1)
166 expect(chosenWorkerKey).toBe(1)
167 })
168
169 it('Verify that execute() throws error if worker choice strategy recursion reach the maximum depth', () => {
170 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
171 fixedPool
172 )
173 const workerChoiceStrategyStub = createStubInstance(
174 RoundRobinWorkerChoiceStrategy,
175 {
176 hasPoolWorkerNodesReady: stub().returns(false),
177 choose: stub().returns(0)
178 }
179 )
180 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
181 WorkerChoiceStrategies.ROUND_ROBIN
182 )
183 workerChoiceStrategyContext.workerChoiceStrategies.set(
184 workerChoiceStrategyContext.workerChoiceStrategy,
185 workerChoiceStrategyStub
186 )
187 expect(() => workerChoiceStrategyContext.execute()).toThrow(
188 new RangeError('Maximum call stack size exceeded')
189 )
190 })
191
192 it('Verify that execute() return the worker node key chosen by the strategy with dynamic pool', () => {
193 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
194 dynamicPool
195 )
196 const workerChoiceStrategyStub = createStubInstance(
197 RoundRobinWorkerChoiceStrategy,
198 {
199 hasPoolWorkerNodesReady: stub().returns(true),
200 choose: stub().returns(0)
201 }
202 )
203 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
204 WorkerChoiceStrategies.ROUND_ROBIN
205 )
206 workerChoiceStrategyContext.workerChoiceStrategies.set(
207 workerChoiceStrategyContext.workerChoiceStrategy,
208 workerChoiceStrategyStub
209 )
210 const chosenWorkerKey = workerChoiceStrategyContext.execute()
211 expect(
212 workerChoiceStrategyContext.workerChoiceStrategies.get(
213 workerChoiceStrategyContext.workerChoiceStrategy
214 ).choose.calledOnce
215 ).toBe(true)
216 expect(chosenWorkerKey).toBe(0)
217 })
218
219 it('Verify that setWorkerChoiceStrategy() works with ROUND_ROBIN and fixed pool', () => {
220 const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
221 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
222 fixedPool
223 )
224 expect(
225 workerChoiceStrategyContext.workerChoiceStrategies.get(
226 workerChoiceStrategy
227 )
228 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
229 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
230 workerChoiceStrategy
231 )
232 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
233 expect(
234 workerChoiceStrategyContext.workerChoiceStrategies.get(
235 workerChoiceStrategy
236 )
237 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
238 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
239 workerChoiceStrategy
240 )
241 })
242
243 it('Verify that setWorkerChoiceStrategy() works with ROUND_ROBIN and dynamic pool', () => {
244 const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
245 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
246 dynamicPool
247 )
248 expect(
249 workerChoiceStrategyContext.workerChoiceStrategies.get(
250 workerChoiceStrategy
251 )
252 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
253 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
254 workerChoiceStrategy
255 )
256 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
257 expect(
258 workerChoiceStrategyContext.workerChoiceStrategies.get(
259 workerChoiceStrategy
260 )
261 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
262 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
263 workerChoiceStrategy
264 )
265 })
266
267 it('Verify that setWorkerChoiceStrategy() works with LEAST_USED and fixed pool', () => {
268 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED
269 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
270 fixedPool
271 )
272 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
273 expect(
274 workerChoiceStrategyContext.workerChoiceStrategies.get(
275 workerChoiceStrategy
276 )
277 ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
278 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
279 workerChoiceStrategy
280 )
281 })
282
283 it('Verify that setWorkerChoiceStrategy() works with LEAST_USED and dynamic pool', () => {
284 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED
285 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
286 dynamicPool
287 )
288 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
289 expect(
290 workerChoiceStrategyContext.workerChoiceStrategies.get(
291 workerChoiceStrategy
292 )
293 ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
294 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
295 workerChoiceStrategy
296 )
297 })
298
299 it('Verify that setWorkerChoiceStrategy() works with LEAST_BUSY and fixed pool', () => {
300 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY
301 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
302 fixedPool
303 )
304 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
305 expect(
306 workerChoiceStrategyContext.workerChoiceStrategies.get(
307 workerChoiceStrategy
308 )
309 ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
310 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
311 workerChoiceStrategy
312 )
313 })
314
315 it('Verify that setWorkerChoiceStrategy() works with LEAST_BUSY and dynamic pool', () => {
316 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY
317 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
318 dynamicPool
319 )
320 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
321 expect(
322 workerChoiceStrategyContext.workerChoiceStrategies.get(
323 workerChoiceStrategy
324 )
325 ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
326 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
327 workerChoiceStrategy
328 )
329 })
330
331 it('Verify that setWorkerChoiceStrategy() works with LEAST_ELU and fixed pool', () => {
332 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU
333 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
334 fixedPool
335 )
336 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
337 expect(
338 workerChoiceStrategyContext.workerChoiceStrategies.get(
339 workerChoiceStrategy
340 )
341 ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
342 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
343 workerChoiceStrategy
344 )
345 })
346
347 it('Verify that setWorkerChoiceStrategy() works with LEAST_ELU and dynamic pool', () => {
348 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU
349 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
350 dynamicPool
351 )
352 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
353 expect(
354 workerChoiceStrategyContext.workerChoiceStrategies.get(
355 workerChoiceStrategy
356 )
357 ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
358 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
359 workerChoiceStrategy
360 )
361 })
362
363 it('Verify that setWorkerChoiceStrategy() works with FAIR_SHARE and fixed pool', () => {
364 const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
365 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
366 fixedPool
367 )
368 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
369 expect(
370 workerChoiceStrategyContext.workerChoiceStrategies.get(
371 workerChoiceStrategy
372 )
373 ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
374 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
375 workerChoiceStrategy
376 )
377 })
378
379 it('Verify that setWorkerChoiceStrategy() works with FAIR_SHARE and dynamic pool', () => {
380 const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
381 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
382 dynamicPool
383 )
384 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
385 expect(
386 workerChoiceStrategyContext.workerChoiceStrategies.get(
387 workerChoiceStrategy
388 )
389 ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
390 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
391 workerChoiceStrategy
392 )
393 })
394
395 it('Verify that setWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and fixed pool', () => {
396 const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
397 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
398 fixedPool
399 )
400 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
401 expect(
402 workerChoiceStrategyContext.workerChoiceStrategies.get(
403 workerChoiceStrategy
404 )
405 ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
406 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
407 workerChoiceStrategy
408 )
409 })
410
411 it('Verify that setWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
412 const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
413 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
414 dynamicPool
415 )
416 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
417 expect(
418 workerChoiceStrategyContext.workerChoiceStrategies.get(
419 workerChoiceStrategy
420 )
421 ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
422 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
423 workerChoiceStrategy
424 )
425 })
426
427 it('Verify that setWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and fixed pool', () => {
428 const workerChoiceStrategy =
429 WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
430 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
431 fixedPool
432 )
433 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
434 expect(
435 workerChoiceStrategyContext.workerChoiceStrategies.get(
436 workerChoiceStrategy
437 )
438 ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
439 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
440 workerChoiceStrategy
441 )
442 })
443
444 it('Verify that setWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
445 const workerChoiceStrategy =
446 WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
447 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
448 dynamicPool
449 )
450 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
451 expect(
452 workerChoiceStrategyContext.workerChoiceStrategies.get(
453 workerChoiceStrategy
454 )
455 ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
456 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
457 workerChoiceStrategy
458 )
459 })
460
461 it('Verify that worker choice strategy options enable median runtime pool statistics', () => {
462 const wwrWorkerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
463 let workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
464 fixedPool,
465 wwrWorkerChoiceStrategy,
466 {
467 runTime: { median: true }
468 }
469 )
470 expect(
471 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
472 .average
473 ).toBe(false)
474 expect(
475 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
476 ).toBe(true)
477 workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
478 dynamicPool,
479 wwrWorkerChoiceStrategy,
480 {
481 runTime: { median: true }
482 }
483 )
484 expect(
485 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
486 .average
487 ).toBe(false)
488 expect(
489 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
490 ).toBe(true)
491 const fsWorkerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
492 workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
493 fixedPool,
494 fsWorkerChoiceStrategy,
495 {
496 runTime: { median: true }
497 }
498 )
499 expect(
500 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
501 .average
502 ).toBe(false)
503 expect(
504 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
505 ).toBe(true)
506 workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
507 dynamicPool,
508 fsWorkerChoiceStrategy,
509 {
510 runTime: { median: true }
511 }
512 )
513 expect(
514 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
515 .average
516 ).toBe(false)
517 expect(
518 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
519 ).toBe(true)
520 })
521 })