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