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