build(ci): add autofix GH action
[poolifier.git] / tests / pools / cluster / dynamic.test.mjs
CommitLineData
a074ffee 1import { expect } from 'expect'
ded253e2 2
8e8d9101
JB
3import {
4 DynamicClusterPool,
5 PoolEvents,
3a502712 6 WorkerChoiceStrategies,
8e8d9101 7} from '../../../lib/index.cjs'
d35e5717 8import { TaskFunctions } from '../../test-types.cjs'
8e8d9101 9import { sleep, waitPoolEvents, waitWorkerEvents } from '../../test-utils.cjs'
506c2a14 10
a35560ba 11describe('Dynamic cluster pool test suite', () => {
e1ffb94f
JB
12 const min = 1
13 const max = 3
14 const pool = new DynamicClusterPool(
15 min,
16 max,
d35e5717 17 './tests/worker-files/cluster/testWorker.cjs',
e1ffb94f 18 {
3a502712 19 errorHandler: e => console.error(e),
e1ffb94f
JB
20 }
21 )
22
325f50bc 23 it('Verify that the function is executed in a worker cluster', async () => {
6db75ad9 24 let result = await pool.execute({
3a502712 25 function: TaskFunctions.fibonacci,
6db75ad9 26 })
66f0c14c 27 expect(result).toBe(354224848179262000000)
6db75ad9 28 result = await pool.execute({
3a502712 29 function: TaskFunctions.factorial,
6db75ad9 30 })
70a4f5ea 31 expect(result).toBe(9.33262154439441e157)
506c2a14 32 })
33
34 it('Verify that new workers are created when required, max size is not exceeded and that after a while new workers will die', async () => {
7c0ba920 35 let poolBusy = 0
aee46736 36 pool.emitter.on(PoolEvents.busy, () => ++poolBusy)
cf9aa6c3 37 for (let i = 0; i < max * 2; i++) {
8cbb82eb 38 pool.execute()
506c2a14 39 }
f06e48d8
JB
40 expect(pool.workerNodes.length).toBeLessThanOrEqual(max)
41 expect(pool.workerNodes.length).toBeGreaterThan(min)
94407def 42 expect(poolBusy).toBe(1)
bac873bd 43 const numberOfExitEvents = await waitWorkerEvents(pool, 'exit', max - min)
85a3f8a7 44 expect(numberOfExitEvents).toBe(max - min)
9b5c72ff 45 expect(pool.workerNodes.length).toBe(min)
506c2a14 46 })
47
325f50bc 48 it('Verify scale worker up and down is working', async () => {
e211bc18 49 for (let i = 0; i < max * 2; i++) {
6db75ad9 50 pool.execute()
bcf04003 51 }
f06e48d8 52 expect(pool.workerNodes.length).toBeGreaterThan(min)
bac873bd 53 await waitWorkerEvents(pool, 'exit', max - min)
f06e48d8 54 expect(pool.workerNodes.length).toBe(min)
e211bc18 55 for (let i = 0; i < max * 2; i++) {
6db75ad9 56 pool.execute()
bcf04003 57 }
f06e48d8 58 expect(pool.workerNodes.length).toBeGreaterThan(min)
bac873bd 59 await waitWorkerEvents(pool, 'exit', max - min)
f06e48d8 60 expect(pool.workerNodes.length).toBe(min)
bcf04003 61 })
506c2a14 62
85a3f8a7 63 it('Shutdown test', async () => {
bac873bd 64 const exitPromise = waitWorkerEvents(pool, 'exit', min)
c726f66c 65 expect(pool.emitter.eventNames()).toStrictEqual([PoolEvents.busy])
ef3891a3
JB
66 let poolDestroy = 0
67 pool.emitter.on(PoolEvents.destroy, () => ++poolDestroy)
c726f66c
JB
68 expect(pool.emitter.eventNames()).toStrictEqual([
69 PoolEvents.busy,
3a502712 70 PoolEvents.destroy,
c726f66c 71 ])
85a3f8a7 72 await pool.destroy()
bdacc2d2 73 const numberOfExitEvents = await exitPromise
bb9423b7 74 expect(pool.started).toBe(false)
a621172f
JB
75 expect(pool.emitter.eventNames()).toStrictEqual([
76 PoolEvents.busy,
3a502712 77 PoolEvents.destroy,
a621172f 78 ])
55082af9 79 expect(pool.readyEventEmitted).toBe(false)
bb9423b7 80 expect(pool.workerNodes.length).toBe(0)
bdacc2d2 81 expect(numberOfExitEvents).toBe(min)
ef3891a3 82 expect(poolDestroy).toBe(1)
506c2a14 83 })
84
8d3782fa 85 it('Validation of inputs test', () => {
948faff7 86 expect(() => new DynamicClusterPool(min)).toThrow(
c3719753 87 'The worker file path must be specified'
8d3782fa
JB
88 )
89 })
90
506c2a14 91 it('Should work even without opts in input', async () => {
0fe39c97 92 const pool = new DynamicClusterPool(
e1ffb94f
JB
93 min,
94 max,
d35e5717 95 './tests/worker-files/cluster/testWorker.cjs'
325f50bc 96 )
0fe39c97 97 const result = await pool.execute()
30b963d4 98 expect(result).toStrictEqual({ ok: 1 })
8bc77620 99 // We need to clean up the resources after our test
0fe39c97 100 await pool.destroy()
506c2a14 101 })
e826bd34 102
1c6fe997 103 it('Verify scale processes up and down is working when long executing task is used:hard', async () => {
c01733f1 104 const longRunningPool = new DynamicClusterPool(
105 min,
106 max,
d35e5717 107 './tests/worker-files/cluster/longRunningWorkerHardBehavior.cjs',
292ad316 108 {
041dc05b 109 errorHandler: e => console.error(e),
73bfd59d 110 onlineHandler: () => console.info('long executing worker is online'),
3a502712 111 exitHandler: () => console.info('long executing worker exited'),
292ad316 112 }
4c35177b 113 )
f06e48d8 114 expect(longRunningPool.workerNodes.length).toBe(min)
e211bc18 115 for (let i = 0; i < max * 2; i++) {
6db75ad9 116 longRunningPool.execute()
4c35177b 117 }
f06e48d8 118 expect(longRunningPool.workerNodes.length).toBe(max)
bac873bd 119 await waitWorkerEvents(longRunningPool, 'exit', max - min)
f06e48d8 120 expect(longRunningPool.workerNodes.length).toBe(min)
d710242d 121 expect(
bcfb06ce
JB
122 longRunningPool.workerChoiceStrategiesContext.workerChoiceStrategies.get(
123 longRunningPool.workerChoiceStrategiesContext
124 .defaultWorkerChoiceStrategy
9b106837 125 ).nextWorkerNodeKey
d710242d 126 ).toBeLessThan(longRunningPool.workerNodes.length)
8bc77620
APA
127 // We need to clean up the resources after our test
128 await longRunningPool.destroy()
4c35177b 129 })
130
1c6fe997 131 it('Verify scale processes up and down is working when long executing task is used:soft', async () => {
4c35177b 132 const longRunningPool = new DynamicClusterPool(
133 min,
134 max,
d35e5717 135 './tests/worker-files/cluster/longRunningWorkerSoftBehavior.cjs',
292ad316 136 {
041dc05b 137 errorHandler: e => console.error(e),
73bfd59d 138 onlineHandler: () => console.info('long executing worker is online'),
3a502712 139 exitHandler: () => console.info('long executing worker exited'),
292ad316 140 }
c01733f1 141 )
f06e48d8 142 expect(longRunningPool.workerNodes.length).toBe(min)
e211bc18 143 for (let i = 0; i < max * 2; i++) {
6db75ad9 144 longRunningPool.execute()
c01733f1 145 }
f06e48d8 146 expect(longRunningPool.workerNodes.length).toBe(max)
920278a2 147 await sleep(1000)
1c6fe997 148 // Here we expect the workerNodes to be at the max size since the task is still executing
f06e48d8 149 expect(longRunningPool.workerNodes.length).toBe(max)
8bc77620
APA
150 // We need to clean up the resources after our test
151 await longRunningPool.destroy()
c01733f1 152 })
8d3782fa
JB
153
154 it('Verify that a pool with zero worker can be instantiated', async () => {
155 const pool = new DynamicClusterPool(
156 0,
157 max,
d35e5717 158 './tests/worker-files/cluster/testWorker.cjs'
8d3782fa
JB
159 )
160 expect(pool).toBeInstanceOf(DynamicClusterPool)
161 // We need to clean up the resources after our test
162 await pool.destroy()
163 })
e44639e9 164
6d7beb8c 165 it('Verify that a pool with zero worker works', async () => {
8e8d9101 166 for (const workerChoiceStrategy of Object.values(WorkerChoiceStrategies)) {
6d7beb8c
JB
167 const pool = new DynamicClusterPool(
168 0,
169 max,
170 './tests/worker-files/cluster/testWorker.cjs',
171 {
3a502712 172 workerChoiceStrategy,
6d7beb8c
JB
173 }
174 )
175 expect(pool.starting).toBe(false)
8e8d9101
JB
176 expect(pool.readyEventEmitted).toBe(false)
177 for (let run = 0; run < 2; run++) {
3a502712 178 // eslint-disable-next-line @typescript-eslint/no-unused-expressions
8e8d9101
JB
179 run % 2 !== 0 && pool.enableTasksQueue(true)
180 const maxMultiplier = 4
181 const promises = new Set()
182 expect(pool.workerNodes.length).toBe(pool.info.minSize)
183 for (let i = 0; i < max * maxMultiplier; i++) {
184 promises.add(pool.execute())
185 }
186 await Promise.all(promises)
187 expect(pool.readyEventEmitted).toBe(true)
188 expect(pool.workerNodes.length).toBeGreaterThan(pool.info.minSize)
189 expect(pool.workerNodes.length).toBeLessThanOrEqual(pool.info.maxSize)
190 await waitPoolEvents(pool, PoolEvents.empty, 1)
191 expect(pool.readyEventEmitted).toBe(false)
192 expect(pool.workerNodes.length).toBe(pool.info.minSize)
28881126 193 }
6d7beb8c
JB
194 // We need to clean up the resources after our test
195 await pool.destroy()
a2283a19 196 }
e44639e9 197 })
506c2a14 198})