96281a9563bf853d207156984fdf9f2938579213
[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.js'
8 import { WorkerChoiceStrategyContext } from '../../../lib/pools/selection-strategies/worker-choice-strategy-context.js'
9 import { RoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/round-robin-worker-choice-strategy.js'
10 import { LeastUsedWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-used-worker-choice-strategy.js'
11 import { LeastBusyWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-busy-worker-choice-strategy.js'
12 import { LeastEluWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/least-elu-worker-choice-strategy.js'
13 import { FairShareWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/fair-share-worker-choice-strategy.js'
14 import { WeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.js'
15 import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.js'
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('Worker node key chosen is null or undefined after 6 retries')
99 )
100 const workerChoiceStrategyNullStub = createStubInstance(
101 RoundRobinWorkerChoiceStrategy,
102 {
103 hasPoolWorkerNodesReady: stub().returns(true),
104 choose: stub().returns(null)
105 }
106 )
107 workerChoiceStrategyContext.workerChoiceStrategies.set(
108 workerChoiceStrategyContext.workerChoiceStrategy,
109 workerChoiceStrategyNullStub
110 )
111 expect(() => workerChoiceStrategyContext.execute()).toThrow(
112 new Error('Worker node key chosen is null or undefined after 6 retries')
113 )
114 })
115
116 it('Verify that execute() retry until a worker node is ready and chosen', () => {
117 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
118 fixedPool
119 )
120 const workerChoiceStrategyStub = createStubInstance(
121 RoundRobinWorkerChoiceStrategy,
122 {
123 hasPoolWorkerNodesReady: stub()
124 .onCall(0)
125 .returns(false)
126 .onCall(1)
127 .returns(false)
128 .onCall(2)
129 .returns(false)
130 .onCall(3)
131 .returns(false)
132 .onCall(4)
133 .returns(false)
134 .returns(true),
135 choose: stub().returns(1)
136 }
137 )
138 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
139 WorkerChoiceStrategies.ROUND_ROBIN
140 )
141 workerChoiceStrategyContext.workerChoiceStrategies.set(
142 workerChoiceStrategyContext.workerChoiceStrategy,
143 workerChoiceStrategyStub
144 )
145 const chosenWorkerKey = workerChoiceStrategyContext.execute()
146 expect(
147 workerChoiceStrategyContext.workerChoiceStrategies.get(
148 workerChoiceStrategyContext.workerChoiceStrategy
149 ).hasPoolWorkerNodesReady.callCount
150 ).toBe(6)
151 expect(
152 workerChoiceStrategyContext.workerChoiceStrategies.get(
153 workerChoiceStrategyContext.workerChoiceStrategy
154 ).choose.callCount
155 ).toBe(1)
156 expect(chosenWorkerKey).toBe(1)
157 })
158
159 it('Verify that execute() throws error if worker choice strategy recursion reach the maximum depth', () => {
160 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
161 fixedPool
162 )
163 const workerChoiceStrategyStub = createStubInstance(
164 RoundRobinWorkerChoiceStrategy,
165 {
166 hasPoolWorkerNodesReady: stub().returns(false),
167 choose: stub().returns(0)
168 }
169 )
170 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
171 WorkerChoiceStrategies.ROUND_ROBIN
172 )
173 workerChoiceStrategyContext.workerChoiceStrategies.set(
174 workerChoiceStrategyContext.workerChoiceStrategy,
175 workerChoiceStrategyStub
176 )
177 expect(() => workerChoiceStrategyContext.execute()).toThrow(
178 new RangeError('Maximum call stack size exceeded')
179 )
180 })
181
182 it('Verify that execute() return the worker node key chosen by the strategy with dynamic pool', () => {
183 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
184 dynamicPool
185 )
186 const workerChoiceStrategyStub = createStubInstance(
187 RoundRobinWorkerChoiceStrategy,
188 {
189 hasPoolWorkerNodesReady: stub().returns(true),
190 choose: stub().returns(0)
191 }
192 )
193 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
194 WorkerChoiceStrategies.ROUND_ROBIN
195 )
196 workerChoiceStrategyContext.workerChoiceStrategies.set(
197 workerChoiceStrategyContext.workerChoiceStrategy,
198 workerChoiceStrategyStub
199 )
200 const chosenWorkerKey = workerChoiceStrategyContext.execute()
201 expect(
202 workerChoiceStrategyContext.workerChoiceStrategies.get(
203 workerChoiceStrategyContext.workerChoiceStrategy
204 ).choose.calledOnce
205 ).toBe(true)
206 expect(chosenWorkerKey).toBe(0)
207 })
208
209 it('Verify that setWorkerChoiceStrategy() works with ROUND_ROBIN and fixed pool', () => {
210 const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
211 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
212 fixedPool
213 )
214 expect(
215 workerChoiceStrategyContext.workerChoiceStrategies.get(
216 workerChoiceStrategy
217 )
218 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
219 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
220 workerChoiceStrategy
221 )
222 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
223 expect(
224 workerChoiceStrategyContext.workerChoiceStrategies.get(
225 workerChoiceStrategy
226 )
227 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
228 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
229 workerChoiceStrategy
230 )
231 })
232
233 it('Verify that setWorkerChoiceStrategy() works with ROUND_ROBIN and dynamic pool', () => {
234 const workerChoiceStrategy = WorkerChoiceStrategies.ROUND_ROBIN
235 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
236 dynamicPool
237 )
238 expect(
239 workerChoiceStrategyContext.workerChoiceStrategies.get(
240 workerChoiceStrategy
241 )
242 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
243 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
244 workerChoiceStrategy
245 )
246 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
247 expect(
248 workerChoiceStrategyContext.workerChoiceStrategies.get(
249 workerChoiceStrategy
250 )
251 ).toBeInstanceOf(RoundRobinWorkerChoiceStrategy)
252 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
253 workerChoiceStrategy
254 )
255 })
256
257 it('Verify that setWorkerChoiceStrategy() works with LEAST_USED and fixed pool', () => {
258 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED
259 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
260 fixedPool
261 )
262 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
263 expect(
264 workerChoiceStrategyContext.workerChoiceStrategies.get(
265 workerChoiceStrategy
266 )
267 ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
268 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
269 workerChoiceStrategy
270 )
271 })
272
273 it('Verify that setWorkerChoiceStrategy() works with LEAST_USED and dynamic pool', () => {
274 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_USED
275 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
276 dynamicPool
277 )
278 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
279 expect(
280 workerChoiceStrategyContext.workerChoiceStrategies.get(
281 workerChoiceStrategy
282 )
283 ).toBeInstanceOf(LeastUsedWorkerChoiceStrategy)
284 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
285 workerChoiceStrategy
286 )
287 })
288
289 it('Verify that setWorkerChoiceStrategy() works with LEAST_BUSY and fixed pool', () => {
290 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY
291 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
292 fixedPool
293 )
294 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
295 expect(
296 workerChoiceStrategyContext.workerChoiceStrategies.get(
297 workerChoiceStrategy
298 )
299 ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
300 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
301 workerChoiceStrategy
302 )
303 })
304
305 it('Verify that setWorkerChoiceStrategy() works with LEAST_BUSY and dynamic pool', () => {
306 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_BUSY
307 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
308 dynamicPool
309 )
310 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
311 expect(
312 workerChoiceStrategyContext.workerChoiceStrategies.get(
313 workerChoiceStrategy
314 )
315 ).toBeInstanceOf(LeastBusyWorkerChoiceStrategy)
316 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
317 workerChoiceStrategy
318 )
319 })
320
321 it('Verify that setWorkerChoiceStrategy() works with LEAST_ELU and fixed pool', () => {
322 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU
323 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
324 fixedPool
325 )
326 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
327 expect(
328 workerChoiceStrategyContext.workerChoiceStrategies.get(
329 workerChoiceStrategy
330 )
331 ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
332 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
333 workerChoiceStrategy
334 )
335 })
336
337 it('Verify that setWorkerChoiceStrategy() works with LEAST_ELU and dynamic pool', () => {
338 const workerChoiceStrategy = WorkerChoiceStrategies.LEAST_ELU
339 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
340 dynamicPool
341 )
342 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
343 expect(
344 workerChoiceStrategyContext.workerChoiceStrategies.get(
345 workerChoiceStrategy
346 )
347 ).toBeInstanceOf(LeastEluWorkerChoiceStrategy)
348 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
349 workerChoiceStrategy
350 )
351 })
352
353 it('Verify that setWorkerChoiceStrategy() works with FAIR_SHARE and fixed pool', () => {
354 const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
355 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
356 fixedPool
357 )
358 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
359 expect(
360 workerChoiceStrategyContext.workerChoiceStrategies.get(
361 workerChoiceStrategy
362 )
363 ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
364 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
365 workerChoiceStrategy
366 )
367 })
368
369 it('Verify that setWorkerChoiceStrategy() works with FAIR_SHARE and dynamic pool', () => {
370 const workerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
371 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
372 dynamicPool
373 )
374 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
375 expect(
376 workerChoiceStrategyContext.workerChoiceStrategies.get(
377 workerChoiceStrategy
378 )
379 ).toBeInstanceOf(FairShareWorkerChoiceStrategy)
380 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
381 workerChoiceStrategy
382 )
383 })
384
385 it('Verify that setWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and fixed pool', () => {
386 const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
387 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
388 fixedPool
389 )
390 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
391 expect(
392 workerChoiceStrategyContext.workerChoiceStrategies.get(
393 workerChoiceStrategy
394 )
395 ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
396 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
397 workerChoiceStrategy
398 )
399 })
400
401 it('Verify that setWorkerChoiceStrategy() works with WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
402 const workerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
403 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
404 dynamicPool
405 )
406 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
407 expect(
408 workerChoiceStrategyContext.workerChoiceStrategies.get(
409 workerChoiceStrategy
410 )
411 ).toBeInstanceOf(WeightedRoundRobinWorkerChoiceStrategy)
412 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
413 workerChoiceStrategy
414 )
415 })
416
417 it('Verify that setWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and fixed pool', () => {
418 const workerChoiceStrategy =
419 WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
420 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
421 fixedPool
422 )
423 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
424 expect(
425 workerChoiceStrategyContext.workerChoiceStrategies.get(
426 workerChoiceStrategy
427 )
428 ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
429 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
430 workerChoiceStrategy
431 )
432 })
433
434 it('Verify that setWorkerChoiceStrategy() works with INTERLEAVED_WEIGHTED_ROUND_ROBIN and dynamic pool', () => {
435 const workerChoiceStrategy =
436 WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN
437 const workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
438 dynamicPool
439 )
440 workerChoiceStrategyContext.setWorkerChoiceStrategy(workerChoiceStrategy)
441 expect(
442 workerChoiceStrategyContext.workerChoiceStrategies.get(
443 workerChoiceStrategy
444 )
445 ).toBeInstanceOf(InterleavedWeightedRoundRobinWorkerChoiceStrategy)
446 expect(workerChoiceStrategyContext.workerChoiceStrategy).toBe(
447 workerChoiceStrategy
448 )
449 })
450
451 it('Verify that worker choice strategy options enable median runtime pool statistics', () => {
452 const wwrWorkerChoiceStrategy = WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN
453 let workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
454 fixedPool,
455 wwrWorkerChoiceStrategy,
456 {
457 runTime: { median: true }
458 }
459 )
460 expect(
461 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
462 .average
463 ).toBe(false)
464 expect(
465 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
466 ).toBe(true)
467 workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
468 dynamicPool,
469 wwrWorkerChoiceStrategy,
470 {
471 runTime: { median: true }
472 }
473 )
474 expect(
475 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
476 .average
477 ).toBe(false)
478 expect(
479 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
480 ).toBe(true)
481 const fsWorkerChoiceStrategy = WorkerChoiceStrategies.FAIR_SHARE
482 workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
483 fixedPool,
484 fsWorkerChoiceStrategy,
485 {
486 runTime: { median: true }
487 }
488 )
489 expect(
490 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
491 .average
492 ).toBe(false)
493 expect(
494 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
495 ).toBe(true)
496 workerChoiceStrategyContext = new WorkerChoiceStrategyContext(
497 dynamicPool,
498 fsWorkerChoiceStrategy,
499 {
500 runTime: { median: true }
501 }
502 )
503 expect(
504 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime
505 .average
506 ).toBe(false)
507 expect(
508 workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median
509 ).toBe(true)
510 })
511 })