Merge branch 'master' of github.com:jerome-benoit/poolifier
[poolifier.git] / tests / worker / abstract-worker.test.js
1 const { expect } = require('expect')
2 const sinon = require('sinon')
3 const { ClusterWorker, KillBehaviors, ThreadWorker } = require('../../lib')
4 const { DEFAULT_TASK_NAME, EMPTY_FUNCTION } = require('../../lib/utils')
5
6 describe('Abstract worker test suite', () => {
7 class StubWorkerWithMainWorker extends ThreadWorker {
8 constructor (fn, opts) {
9 super(fn, opts)
10 this.mainWorker = undefined
11 }
12 }
13
14 afterEach(() => {
15 sinon.restore()
16 })
17
18 it('Verify worker options default values', () => {
19 const worker = new ThreadWorker(() => {})
20 expect(worker.opts).toStrictEqual({
21 killBehavior: KillBehaviors.SOFT,
22 maxInactiveTime: 60000,
23 killHandler: EMPTY_FUNCTION
24 })
25 })
26
27 it('Verify that worker options are set at worker creation', () => {
28 const killHandler = () => {
29 console.info('Worker received kill message')
30 }
31 const worker = new ClusterWorker(() => {}, {
32 killBehavior: KillBehaviors.HARD,
33 maxInactiveTime: 6000,
34 killHandler,
35 async: true
36 })
37 expect(worker.opts).toStrictEqual({
38 killBehavior: KillBehaviors.HARD,
39 maxInactiveTime: 6000,
40 killHandler
41 })
42 })
43
44 it('Verify that taskFunctions parameter is mandatory', () => {
45 expect(() => new ClusterWorker()).toThrowError(
46 'taskFunctions parameter is mandatory'
47 )
48 })
49
50 it('Verify that taskFunctions parameter is a function or a plain object', () => {
51 expect(() => new ClusterWorker(0)).toThrowError(
52 new TypeError(
53 'taskFunctions parameter is not a function or a plain object'
54 )
55 )
56 expect(() => new ClusterWorker('')).toThrowError(
57 new TypeError(
58 'taskFunctions parameter is not a function or a plain object'
59 )
60 )
61 expect(() => new ClusterWorker(true)).toThrowError(
62 new TypeError(
63 'taskFunctions parameter is not a function or a plain object'
64 )
65 )
66 expect(() => new ClusterWorker([])).toThrowError(
67 new TypeError(
68 'taskFunctions parameter is not a function or a plain object'
69 )
70 )
71 expect(() => new ClusterWorker(new Map())).toThrowError(
72 new TypeError(
73 'taskFunctions parameter is not a function or a plain object'
74 )
75 )
76 expect(() => new ClusterWorker(new Set())).toThrowError(
77 new TypeError(
78 'taskFunctions parameter is not a function or a plain object'
79 )
80 )
81 expect(() => new ClusterWorker(new WeakMap())).toThrowError(
82 new TypeError(
83 'taskFunctions parameter is not a function or a plain object'
84 )
85 )
86 expect(() => new ClusterWorker(new WeakSet())).toThrowError(
87 new TypeError(
88 'taskFunctions parameter is not a function or a plain object'
89 )
90 )
91 })
92
93 it('Verify that taskFunctions parameter is not an empty object', () => {
94 expect(() => new ClusterWorker({})).toThrowError(
95 new Error('taskFunctions parameter object is empty')
96 )
97 })
98
99 it('Verify that taskFunctions parameter with unique function is taken', () => {
100 const worker = new ThreadWorker(() => {})
101 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
102 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
103 expect(worker.taskFunctions.size).toBe(2)
104 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
105 worker.taskFunctions.get('fn1')
106 )
107 })
108
109 it('Verify that taskFunctions parameter with multiple task functions is checked', () => {
110 const fn1 = () => {
111 return 1
112 }
113 const fn2 = ''
114 expect(() => new ThreadWorker({ '': fn1 })).toThrowError(
115 new TypeError('A taskFunctions parameter object key is an empty string')
116 )
117 expect(() => new ThreadWorker({ fn1, fn2 })).toThrowError(
118 new TypeError('A taskFunctions parameter object value is not a function')
119 )
120 })
121
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 })
130 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
131 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
132 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
133 expect(worker.taskFunctions.size).toBe(3)
134 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
135 worker.taskFunctions.get('fn1')
136 )
137 })
138
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
144 worker.getMainWorker = sinon.stub().returns({
145 id: 1,
146 send: sinon.stub().returns()
147 })
148 worker.handleKillMessage()
149 expect(worker.getMainWorker().send.calledOnce).toBe(true)
150 expect(worker.opts.killHandler.calledOnce).toBe(true)
151 })
152
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 })
162
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)
170 })
171
172 it('Verify that getMainWorker() throw error if main worker is not set', () => {
173 expect(() =>
174 new StubWorkerWithMainWorker(() => {}).getMainWorker()
175 ).toThrowError('Main worker not set')
176 })
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 })
186 expect(() => worker.hasTaskFunction(0)).toThrowError(
187 new TypeError('name parameter is not a string')
188 )
189 expect(() => worker.hasTaskFunction('')).toThrowError(
190 new TypeError('name parameter is an empty string')
191 )
192 expect(worker.hasTaskFunction(DEFAULT_TASK_NAME)).toBe(true)
193 expect(worker.hasTaskFunction('fn1')).toBe(true)
194 expect(worker.hasTaskFunction('fn2')).toBe(true)
195 expect(worker.hasTaskFunction('fn3')).toBe(false)
196 })
197
198 it('Verify that addTaskFunction() works', () => {
199 const fn1 = () => {
200 return 1
201 }
202 const fn2 = () => {
203 return 2
204 }
205 const fn1Replacement = () => {
206 return 3
207 }
208 const worker = new ThreadWorker(fn1)
209 expect(() => worker.addTaskFunction(0, fn1)).toThrowError(
210 new TypeError('name parameter is not a string')
211 )
212 expect(() => worker.addTaskFunction('', fn1)).toThrowError(
213 new TypeError('name parameter is an empty string')
214 )
215 expect(() => worker.addTaskFunction('fn3', '')).toThrowError(
216 new TypeError('fn parameter is not a function')
217 )
218 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
219 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
220 expect(worker.taskFunctions.size).toBe(2)
221 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
222 worker.taskFunctions.get('fn1')
223 )
224 expect(() => worker.addTaskFunction(DEFAULT_TASK_NAME, fn2)).toThrowError(
225 new Error('Cannot add a task function with the default reserved name')
226 )
227 worker.addTaskFunction('fn2', fn2)
228 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
229 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
230 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
231 expect(worker.taskFunctions.size).toBe(3)
232 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
233 worker.taskFunctions.get('fn1')
234 )
235 worker.addTaskFunction('fn1', fn1Replacement)
236 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
237 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
238 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
239 expect(worker.taskFunctions.size).toBe(3)
240 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
241 worker.taskFunctions.get('fn1')
242 )
243 })
244
245 it('Verify that removeTaskFunction() works', () => {
246 const fn1 = () => {
247 return 1
248 }
249 const fn2 = () => {
250 return 2
251 }
252 const worker = new ClusterWorker({ fn1, fn2 })
253 expect(() => worker.removeTaskFunction(0, fn1)).toThrowError(
254 new TypeError('name parameter is not a string')
255 )
256 expect(() => worker.removeTaskFunction('', fn1)).toThrowError(
257 new TypeError('name parameter is an empty string')
258 )
259 worker.getMainWorker = sinon.stub().returns({
260 id: 1,
261 send: sinon.stub().returns()
262 })
263 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
264 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
265 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
266 expect(worker.taskFunctions.size).toBe(3)
267 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
268 worker.taskFunctions.get('fn1')
269 )
270 expect(() => worker.removeTaskFunction(DEFAULT_TASK_NAME)).toThrowError(
271 new Error(
272 'Cannot remove the task function with the default reserved name'
273 )
274 )
275 expect(() => worker.removeTaskFunction('fn1')).toThrowError(
276 new Error(
277 'Cannot remove the task function used as the default task function'
278 )
279 )
280 worker.removeTaskFunction('fn2')
281 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
282 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
283 expect(worker.taskFunctions.get('fn2')).toBeUndefined()
284 expect(worker.taskFunctions.size).toBe(2)
285 expect(worker.getMainWorker().send.calledOnce).toBe(true)
286 })
287
288 it('Verify that listTaskFunctions() works', () => {
289 const fn1 = () => {
290 return 1
291 }
292 const fn2 = () => {
293 return 2
294 }
295 const worker = new ClusterWorker({ fn1, fn2 })
296 expect(worker.listTaskFunctions()).toStrictEqual([
297 DEFAULT_TASK_NAME,
298 'fn1',
299 'fn2'
300 ])
301 })
302
303 it('Verify that setDefaultTaskFunction() works', () => {
304 const fn1 = () => {
305 return 1
306 }
307 const fn2 = () => {
308 return 2
309 }
310 const worker = new ThreadWorker({ fn1, fn2 })
311 expect(() => worker.setDefaultTaskFunction(0, fn1)).toThrowError(
312 new TypeError('name parameter is not a string')
313 )
314 expect(() => worker.setDefaultTaskFunction('', fn1)).toThrowError(
315 new TypeError('name parameter is an empty string')
316 )
317 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toBeInstanceOf(Function)
318 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
319 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
320 expect(worker.taskFunctions.size).toBe(3)
321 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
322 worker.taskFunctions.get('fn1')
323 )
324 expect(() => worker.setDefaultTaskFunction(DEFAULT_TASK_NAME)).toThrowError(
325 new Error(
326 'Cannot set the default task function reserved name as the default task function'
327 )
328 )
329 expect(() => worker.setDefaultTaskFunction('fn3')).toThrowError(
330 new Error(
331 'Cannot set the default task function to a non-existing task function'
332 )
333 )
334 worker.setDefaultTaskFunction('fn1')
335 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
336 worker.taskFunctions.get('fn1')
337 )
338 worker.setDefaultTaskFunction('fn2')
339 expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
340 worker.taskFunctions.get('fn2')
341 )
342 })
343 })