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