Merge branch 'master' of github.com:poolifier/poolifier into feature/task-functions
[poolifier.git] / tests / worker / abstract-worker.test.js
CommitLineData
a61a0724 1const { expect } = require('expect')
df9aaf20 2const sinon = require('sinon')
8620fb25 3const { ClusterWorker, KillBehaviors, ThreadWorker } = require('../../lib')
6cd5248f 4const { DEFAULT_TASK_NAME, EMPTY_FUNCTION } = require('../../lib/utils')
7fc5cce6 5
e1ffb94f 6describe('Abstract worker test suite', () => {
1f68cede 7 class StubWorkerWithMainWorker extends ThreadWorker {
e1ffb94f
JB
8 constructor (fn, opts) {
9 super(fn, opts)
78cea37e 10 this.mainWorker = undefined
e1ffb94f 11 }
7fc5cce6 12 }
c510fea7 13
dc021bcc
JB
14 afterEach(() => {
15 sinon.restore()
16 })
17
e088a00c 18 it('Verify worker options default values', () => {
8620fb25 19 const worker = new ThreadWorker(() => {})
cca3bb1a
JB
20 expect(worker.opts).toStrictEqual({
21 killBehavior: KillBehaviors.SOFT,
22 maxInactiveTime: 60000,
23 killHandler: EMPTY_FUNCTION
24 })
8620fb25
JB
25 })
26
27 it('Verify that worker options are set at worker creation', () => {
df9aaf20
JB
28 const killHandler = () => {
29 console.info('Worker received kill message')
30 }
8620fb25 31 const worker = new ClusterWorker(() => {}, {
df9aaf20 32 killBehavior: KillBehaviors.HARD,
cca3bb1a 33 maxInactiveTime: 6000,
df9aaf20
JB
34 killHandler,
35 async: true
8620fb25 36 })
cca3bb1a
JB
37 expect(worker.opts).toStrictEqual({
38 killBehavior: KillBehaviors.HARD,
39 maxInactiveTime: 6000,
40 killHandler
41 })
8620fb25
JB
42 })
43
a86b6df1
JB
44 it('Verify that taskFunctions parameter is mandatory', () => {
45 expect(() => new ClusterWorker()).toThrowError(
46 'taskFunctions parameter is mandatory'
47 )
d4aeae5a
JB
48 })
49
f34fdabe 50 it('Verify that taskFunctions parameter is a function or a plain object', () => {
a86b6df1 51 expect(() => new ClusterWorker(0)).toThrowError(
f34fdabe
JB
52 new TypeError(
53 'taskFunctions parameter is not a function or a plain object'
54 )
d4aeae5a
JB
55 )
56 expect(() => new ClusterWorker('')).toThrowError(
f34fdabe
JB
57 new TypeError(
58 'taskFunctions parameter is not a function or a plain object'
59 )
a86b6df1
JB
60 )
61 expect(() => new ClusterWorker(true)).toThrowError(
f34fdabe
JB
62 new TypeError(
63 'taskFunctions parameter is not a function or a plain object'
64 )
d4aeae5a 65 )
a86b6df1 66 expect(() => new ClusterWorker([])).toThrowError(
f34fdabe
JB
67 new TypeError(
68 'taskFunctions parameter is not a function or a plain object'
69 )
a86b6df1
JB
70 )
71 expect(() => new ClusterWorker(new Map())).toThrowError(
f34fdabe
JB
72 new TypeError(
73 'taskFunctions parameter is not a function or a plain object'
74 )
a86b6df1
JB
75 )
76 expect(() => new ClusterWorker(new Set())).toThrowError(
f34fdabe
JB
77 new TypeError(
78 'taskFunctions parameter is not a function or a plain object'
79 )
a86b6df1
JB
80 )
81 expect(() => new ClusterWorker(new WeakMap())).toThrowError(
f34fdabe
JB
82 new TypeError(
83 'taskFunctions parameter is not a function or a plain object'
84 )
d4aeae5a 85 )
a86b6df1 86 expect(() => new ClusterWorker(new WeakSet())).toThrowError(
f34fdabe
JB
87 new TypeError(
88 'taskFunctions parameter is not a function or a plain object'
89 )
a86b6df1 90 )
f34fdabe
JB
91 })
92
93 it('Verify that taskFunctions parameter is not an empty object', () => {
630f0acf 94 expect(() => new ClusterWorker({})).toThrowError(
0d80593b 95 new Error('taskFunctions parameter object is empty')
630f0acf 96 )
a86b6df1
JB
97 })
98
2a69b8c5
JB
99 it('Verify that taskFunctions parameter with unique function is taken', () => {
100 const worker = new ThreadWorker(() => {})
6cd5248f 101 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
102 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
103 expect(worker.taskFunctions.size).toBe(2)
6cd5248f 104 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
105 worker.taskFunctions.get('fn1')
106 )
107 })
108
6934964f 109 it('Verify that taskFunctions parameter with multiple task functions is checked', () => {
f34fdabe
JB
110 const fn1 = () => {
111 return 1
112 }
113 const fn2 = ''
6934964f
JB
114 expect(() => new ThreadWorker({ '': fn1 })).toThrowError(
115 new TypeError('A taskFunctions parameter object key is an empty string')
116 )
f34fdabe
JB
117 expect(() => new ThreadWorker({ fn1, fn2 })).toThrowError(
118 new TypeError('A taskFunctions parameter object value is not a function')
119 )
120 })
121
a86b6df1
JB
122 it('Verify that taskFunctions parameter with multiple task functions is taken', () => {
123 const fn1 = () => {
124 return 1
125 }
126 const fn2 = () => {
127 return 2
128 }
129 const worker = new ClusterWorker({ fn1, fn2 })
6cd5248f 130 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
131 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
132 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
133 expect(worker.taskFunctions.size).toBe(3)
6cd5248f 134 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
135 worker.taskFunctions.get('fn1')
136 )
d4aeae5a
JB
137 })
138
df9aaf20
JB
139 it('Verify that sync kill handler is called when worker is killed', () => {
140 const worker = new ClusterWorker(() => {}, {
141 killHandler: sinon.stub().returns()
142 })
143 worker.isMain = false
1e3214b6
JB
144 worker.getMainWorker = sinon.stub().returns({
145 id: 1,
146 send: sinon.stub().returns()
147 })
df9aaf20 148 worker.handleKillMessage()
1e3214b6 149 expect(worker.getMainWorker().send.calledOnce).toBe(true)
df9aaf20
JB
150 expect(worker.opts.killHandler.calledOnce).toBe(true)
151 })
152
07588f30
JB
153 it('Verify that async kill handler is called when worker is killed', () => {
154 const killHandlerStub = sinon.stub().returns()
155 const worker = new ClusterWorker(() => {}, {
156 killHandler: async () => Promise.resolve(killHandlerStub())
157 })
158 worker.isMain = false
159 worker.handleKillMessage()
160 expect(killHandlerStub.calledOnce).toBe(true)
161 })
df9aaf20 162
2431bdb4
JB
163 it('Verify that handleError() method works properly', () => {
164 const error = new Error('Error as an error')
165 const worker = new ClusterWorker(() => {})
166 expect(worker.handleError(error)).not.toBeInstanceOf(Error)
167 expect(worker.handleError(error)).toStrictEqual(error.message)
168 const errorMessage = 'Error as a string'
169 expect(worker.handleError(errorMessage)).toStrictEqual(errorMessage)
7fc5cce6
APA
170 })
171
318d4156 172 it('Verify that getMainWorker() throw error if main worker is not set', () => {
7fc5cce6 173 expect(() =>
1f68cede 174 new StubWorkerWithMainWorker(() => {}).getMainWorker()
e102732c 175 ).toThrowError('Main worker not set')
7fc5cce6 176 })
2a69b8c5
JB
177
178 it('Verify that hasTaskFunction() works', () => {
179 const fn1 = () => {
180 return 1
181 }
182 const fn2 = () => {
183 return 2
184 }
185 const worker = new ClusterWorker({ fn1, fn2 })
66979634
JB
186 expect(worker.hasTaskFunction(0)).toStrictEqual({
187 status: false,
188 error: new TypeError('name parameter is not a string')
189 })
190 expect(worker.hasTaskFunction('')).toStrictEqual({
191 status: false,
192 error: new TypeError('name parameter is an empty string')
193 })
194 expect(worker.hasTaskFunction(DEFAULT_TASK_NAME)).toStrictEqual({
195 status: true
196 })
197 expect(worker.hasTaskFunction('fn1')).toStrictEqual({ status: true })
198 expect(worker.hasTaskFunction('fn2')).toStrictEqual({ status: true })
199 expect(worker.hasTaskFunction('fn3')).toStrictEqual({ status: false })
2a69b8c5
JB
200 })
201
202 it('Verify that addTaskFunction() works', () => {
203 const fn1 = () => {
204 return 1
205 }
206 const fn2 = () => {
207 return 2
208 }
209 const fn1Replacement = () => {
210 return 3
211 }
212 const worker = new ThreadWorker(fn1)
66979634
JB
213 expect(worker.addTaskFunction(0, fn1)).toStrictEqual({
214 status: false,
215 error: new TypeError('name parameter is not a string')
216 })
217 expect(worker.addTaskFunction('', fn1)).toStrictEqual({
218 status: false,
219 error: new TypeError('name parameter is an empty string')
220 })
221 expect(worker.addTaskFunction('fn3', '')).toStrictEqual({
222 status: false,
223 error: new TypeError('fn parameter is not a function')
224 })
6cd5248f 225 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
226 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
227 expect(worker.taskFunctions.size).toBe(2)
6cd5248f 228 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
229 worker.taskFunctions.get('fn1')
230 )
66979634
JB
231 expect(worker.addTaskFunction(DEFAULT_TASK_NAME, fn2)).toStrictEqual({
232 status: false,
233 error: new Error(
234 'Cannot add a task function with the default reserved name'
235 )
236 })
2a69b8c5 237 worker.addTaskFunction('fn2', fn2)
6cd5248f 238 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
239 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
240 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
241 expect(worker.taskFunctions.size).toBe(3)
6cd5248f 242 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
243 worker.taskFunctions.get('fn1')
244 )
245 worker.addTaskFunction('fn1', fn1Replacement)
6cd5248f 246 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
247 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
248 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
249 expect(worker.taskFunctions.size).toBe(3)
6cd5248f 250 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
251 worker.taskFunctions.get('fn1')
252 )
253 })
254
255 it('Verify that removeTaskFunction() works', () => {
256 const fn1 = () => {
257 return 1
258 }
259 const fn2 = () => {
260 return 2
261 }
135f2a9f 262 const worker = new ClusterWorker({ fn1, fn2 })
66979634
JB
263 expect(worker.removeTaskFunction(0, fn1)).toStrictEqual({
264 status: false,
265 error: new TypeError('name parameter is not a string')
266 })
267 expect(worker.removeTaskFunction('', fn1)).toStrictEqual({
268 status: false,
269 error: new TypeError('name parameter is an empty string')
270 })
90d7d101
JB
271 worker.getMainWorker = sinon.stub().returns({
272 id: 1,
273 send: sinon.stub().returns()
274 })
6cd5248f 275 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
276 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
277 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
278 expect(worker.taskFunctions.size).toBe(3)
6cd5248f 279 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
280 worker.taskFunctions.get('fn1')
281 )
66979634
JB
282 expect(worker.removeTaskFunction(DEFAULT_TASK_NAME)).toStrictEqual({
283 status: false,
284 error: new Error(
2a69b8c5
JB
285 'Cannot remove the task function with the default reserved name'
286 )
66979634
JB
287 })
288 expect(worker.removeTaskFunction('fn1')).toStrictEqual({
289 status: false,
290 error: new Error(
2a69b8c5
JB
291 'Cannot remove the task function used as the default task function'
292 )
66979634 293 })
2a69b8c5 294 worker.removeTaskFunction('fn2')
6cd5248f 295 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
296 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
297 expect(worker.taskFunctions.get('fn2')).toBeUndefined()
298 expect(worker.taskFunctions.size).toBe(2)
90d7d101 299 expect(worker.getMainWorker().send.calledOnce).toBe(true)
2a69b8c5
JB
300 })
301
c50b93fb
JB
302 it('Verify that listTaskFunctions() works', () => {
303 const fn1 = () => {
304 return 1
305 }
306 const fn2 = () => {
307 return 2
308 }
309 const worker = new ClusterWorker({ fn1, fn2 })
66979634 310 expect(worker.listTaskFunctionNames()).toStrictEqual([
6cd5248f
JB
311 DEFAULT_TASK_NAME,
312 'fn1',
313 'fn2'
314 ])
c50b93fb
JB
315 })
316
2a69b8c5
JB
317 it('Verify that setDefaultTaskFunction() works', () => {
318 const fn1 = () => {
319 return 1
320 }
321 const fn2 = () => {
322 return 2
323 }
324 const worker = new ThreadWorker({ fn1, fn2 })
66979634
JB
325 expect(worker.setDefaultTaskFunction(0, fn1)).toStrictEqual({
326 status: false,
327 error: new TypeError('name parameter is not a string')
328 })
329 expect(worker.setDefaultTaskFunction('', fn1)).toStrictEqual({
330 status: false,
331 error: new TypeError('name parameter is an empty string')
332 })
6cd5248f 333 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
2a69b8c5
JB
334 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
335 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
336 expect(worker.taskFunctions.size).toBe(3)
6cd5248f 337 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
338 worker.taskFunctions.get('fn1')
339 )
66979634
JB
340 expect(worker.setDefaultTaskFunction(DEFAULT_TASK_NAME)).toStrictEqual({
341 status: false,
342 error: new Error(
2a69b8c5
JB
343 'Cannot set the default task function reserved name as the default task function'
344 )
66979634
JB
345 })
346 expect(worker.setDefaultTaskFunction('fn3')).toStrictEqual({
347 status: false,
348 error: new Error(
6934964f
JB
349 'Cannot set the default task function to a non-existing task function'
350 )
66979634 351 })
2a69b8c5 352 worker.setDefaultTaskFunction('fn1')
6cd5248f 353 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
354 worker.taskFunctions.get('fn1')
355 )
356 worker.setDefaultTaskFunction('fn2')
6cd5248f 357 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
2a69b8c5
JB
358 worker.taskFunctions.get('fn2')
359 )
360 })
c510fea7 361})