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