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 | ||
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 | }) |