Commit | Line | Data |
---|---|---|
9974369e | 1 | import { Worker as ClusterWorker } from 'node:cluster' |
ded253e2 JB |
2 | import { MessageChannel, Worker as ThreadWorker } from 'node:worker_threads' |
3 | ||
a074ffee | 4 | import { expect } from 'expect' |
ded253e2 | 5 | |
d35e5717 | 6 | import { CircularArray } from '../../lib/circular-array.cjs' |
ded253e2 JB |
7 | import { WorkerTypes } from '../../lib/index.cjs' |
8 | import { WorkerNode } from '../../lib/pools/worker-node.cjs' | |
95d1a734 | 9 | import { PriorityQueue } from '../../lib/priority-queue.cjs' |
d35e5717 | 10 | import { DEFAULT_TASK_NAME } from '../../lib/utils.cjs' |
26fb3c18 JB |
11 | |
12 | describe('Worker node test suite', () => { | |
c3719753 JB |
13 | const threadWorkerNode = new WorkerNode( |
14 | WorkerTypes.thread, | |
15 | './tests/worker-files/thread/testWorker.mjs', | |
59ca7cff | 16 | { tasksQueueBackPressureSize: 12, tasksQueueBucketSize: 6 } |
c3719753 JB |
17 | ) |
18 | const clusterWorkerNode = new WorkerNode( | |
19 | WorkerTypes.cluster, | |
d35e5717 | 20 | './tests/worker-files/cluster/testWorker.cjs', |
59ca7cff | 21 | { tasksQueueBackPressureSize: 12, tasksQueueBucketSize: 6 } |
c3719753 | 22 | ) |
26fb3c18 JB |
23 | |
24 | it('Worker node instantiation', () => { | |
948faff7 | 25 | expect(() => new WorkerNode()).toThrow( |
c3719753 | 26 | new TypeError('Cannot construct a worker node without a worker type') |
26fb3c18 | 27 | ) |
c3719753 JB |
28 | expect( |
29 | () => | |
30 | new WorkerNode( | |
31 | 'invalidWorkerType', | |
32 | './tests/worker-files/thread/testWorker.mjs', | |
33 | { tasksQueueBackPressureSize: 12 } | |
34 | ) | |
35 | ).toThrow( | |
36 | new TypeError( | |
37 | "Cannot construct a worker node with an invalid worker type 'invalidWorkerType'" | |
38 | ) | |
39 | ) | |
40 | expect( | |
41 | () => | |
42 | new WorkerNode( | |
43 | WorkerTypes.thread, | |
44 | './tests/worker-files/thread/testWorker.mjs' | |
45 | ) | |
46 | ).toThrow( | |
26fb3c18 | 47 | new TypeError( |
c3719753 | 48 | 'Cannot construct a worker node without worker node options' |
26fb3c18 JB |
49 | ) |
50 | ) | |
51 | expect( | |
c3719753 JB |
52 | () => |
53 | new WorkerNode( | |
54 | WorkerTypes.thread, | |
55 | './tests/worker-files/thread/testWorker.mjs', | |
56 | '' | |
57 | ) | |
948faff7 | 58 | ).toThrow( |
26fb3c18 | 59 | new TypeError( |
c3719753 | 60 | 'Cannot construct a worker node with invalid options: must be a plain object' |
26fb3c18 JB |
61 | ) |
62 | ) | |
c3719753 JB |
63 | expect( |
64 | () => | |
65 | new WorkerNode( | |
66 | WorkerTypes.thread, | |
67 | './tests/worker-files/thread/testWorker.mjs', | |
68 | {} | |
69 | ) | |
70 | ).toThrow( | |
5b49e864 | 71 | new TypeError( |
c3719753 | 72 | 'Cannot construct a worker node without a tasks queue back pressure size option' |
5b49e864 JB |
73 | ) |
74 | ) | |
c3719753 JB |
75 | expect( |
76 | () => | |
77 | new WorkerNode( | |
78 | WorkerTypes.thread, | |
79 | './tests/worker-files/thread/testWorker.mjs', | |
80 | { tasksQueueBackPressureSize: 'invalidTasksQueueBackPressureSize' } | |
81 | ) | |
82 | ).toThrow( | |
83 | new TypeError( | |
84 | 'Cannot construct a worker node with a tasks queue back pressure size option that is not an integer' | |
85 | ) | |
86 | ) | |
87 | expect( | |
88 | () => | |
89 | new WorkerNode( | |
90 | WorkerTypes.thread, | |
91 | './tests/worker-files/thread/testWorker.mjs', | |
92 | { tasksQueueBackPressureSize: 0.2 } | |
93 | ) | |
94 | ).toThrow( | |
95 | new TypeError( | |
96 | 'Cannot construct a worker node with a tasks queue back pressure size option that is not an integer' | |
97 | ) | |
98 | ) | |
99 | expect( | |
100 | () => | |
101 | new WorkerNode( | |
102 | WorkerTypes.thread, | |
103 | './tests/worker-files/thread/testWorker.mjs', | |
104 | { tasksQueueBackPressureSize: 0 } | |
105 | ) | |
106 | ).toThrow( | |
5b49e864 | 107 | new RangeError( |
c3719753 | 108 | 'Cannot construct a worker node with a tasks queue back pressure size option that is not a positive integer' |
5b49e864 JB |
109 | ) |
110 | ) | |
c3719753 JB |
111 | expect( |
112 | () => | |
113 | new WorkerNode( | |
114 | WorkerTypes.thread, | |
115 | './tests/worker-files/thread/testWorker.mjs', | |
116 | { tasksQueueBackPressureSize: -1 } | |
117 | ) | |
118 | ).toThrow( | |
5b49e864 | 119 | new RangeError( |
c3719753 | 120 | 'Cannot construct a worker node with a tasks queue back pressure size option that is not a positive integer' |
5b49e864 JB |
121 | ) |
122 | ) | |
59ca7cff JB |
123 | expect( |
124 | () => | |
125 | new WorkerNode( | |
126 | WorkerTypes.thread, | |
127 | './tests/worker-files/thread/testWorker.mjs', | |
128 | { | |
129 | tasksQueueBackPressureSize: 12 | |
130 | } | |
131 | ) | |
132 | ).toThrow( | |
133 | new TypeError( | |
134 | 'Cannot construct a worker node without a tasks queue bucket size option' | |
135 | ) | |
136 | ) | |
137 | expect( | |
138 | () => | |
139 | new WorkerNode( | |
140 | WorkerTypes.thread, | |
141 | './tests/worker-files/thread/testWorker.mjs', | |
142 | { | |
143 | tasksQueueBackPressureSize: 12, | |
144 | tasksQueueBucketSize: 'invalidTasksQueueBucketSize' | |
145 | } | |
146 | ) | |
147 | ).toThrow( | |
148 | new TypeError( | |
149 | 'Cannot construct a worker node with a tasks queue bucket size option that is not an integer' | |
150 | ) | |
151 | ) | |
152 | expect( | |
153 | () => | |
154 | new WorkerNode( | |
155 | WorkerTypes.thread, | |
156 | './tests/worker-files/thread/testWorker.mjs', | |
157 | { tasksQueueBackPressureSize: 12, tasksQueueBucketSize: 0.2 } | |
158 | ) | |
159 | ).toThrow( | |
160 | new TypeError( | |
161 | 'Cannot construct a worker node with a tasks queue bucket size option that is not an integer' | |
162 | ) | |
163 | ) | |
164 | expect( | |
165 | () => | |
166 | new WorkerNode( | |
167 | WorkerTypes.thread, | |
168 | './tests/worker-files/thread/testWorker.mjs', | |
169 | { tasksQueueBackPressureSize: 12, tasksQueueBucketSize: 0 } | |
170 | ) | |
171 | ).toThrow( | |
172 | new RangeError( | |
173 | 'Cannot construct a worker node with a tasks queue bucket size option that is not a positive integer' | |
174 | ) | |
175 | ) | |
176 | expect( | |
177 | () => | |
178 | new WorkerNode( | |
179 | WorkerTypes.thread, | |
180 | './tests/worker-files/thread/testWorker.mjs', | |
181 | { tasksQueueBackPressureSize: 12, tasksQueueBucketSize: -1 } | |
182 | ) | |
183 | ).toThrow( | |
184 | new RangeError( | |
185 | 'Cannot construct a worker node with a tasks queue bucket size option that is not a positive integer' | |
186 | ) | |
187 | ) | |
75de9f41 | 188 | expect(threadWorkerNode).toBeInstanceOf(WorkerNode) |
9974369e | 189 | expect(threadWorkerNode.worker).toBeInstanceOf(ThreadWorker) |
75de9f41 | 190 | expect(threadWorkerNode.info).toStrictEqual({ |
c3719753 | 191 | id: threadWorkerNode.worker.threadId, |
26fb3c18 JB |
192 | type: WorkerTypes.thread, |
193 | dynamic: false, | |
5eb72b9e JB |
194 | ready: false, |
195 | stealing: false | |
26fb3c18 | 196 | }) |
75de9f41 JB |
197 | expect(threadWorkerNode.usage).toStrictEqual({ |
198 | tasks: { | |
199 | executed: 0, | |
200 | executing: 0, | |
201 | queued: 0, | |
202 | maxQueued: 0, | |
463226a4 | 203 | sequentiallyStolen: 0, |
75de9f41 JB |
204 | stolen: 0, |
205 | failed: 0 | |
206 | }, | |
207 | runTime: { | |
4ba4c7f9 | 208 | history: new CircularArray() |
75de9f41 JB |
209 | }, |
210 | waitTime: { | |
4ba4c7f9 | 211 | history: new CircularArray() |
75de9f41 JB |
212 | }, |
213 | elu: { | |
214 | idle: { | |
4ba4c7f9 | 215 | history: new CircularArray() |
75de9f41 JB |
216 | }, |
217 | active: { | |
4ba4c7f9 | 218 | history: new CircularArray() |
75de9f41 JB |
219 | } |
220 | } | |
221 | }) | |
222 | expect(threadWorkerNode.messageChannel).toBeInstanceOf(MessageChannel) | |
223 | expect(threadWorkerNode.tasksQueueBackPressureSize).toBe(12) | |
95d1a734 | 224 | expect(threadWorkerNode.tasksQueue).toBeInstanceOf(PriorityQueue) |
75de9f41 | 225 | expect(threadWorkerNode.tasksQueue.size).toBe(0) |
59ca7cff | 226 | expect(threadWorkerNode.tasksQueue.k).toBe(6) |
68f1f531 JB |
227 | expect(threadWorkerNode.tasksQueueSize()).toBe( |
228 | threadWorkerNode.tasksQueue.size | |
229 | ) | |
230 | expect(threadWorkerNode.onBackPressureStarted).toBe(false) | |
75de9f41 JB |
231 | expect(threadWorkerNode.taskFunctionsUsage).toBeInstanceOf(Map) |
232 | ||
233 | expect(clusterWorkerNode).toBeInstanceOf(WorkerNode) | |
9974369e | 234 | expect(clusterWorkerNode.worker).toBeInstanceOf(ClusterWorker) |
75de9f41 | 235 | expect(clusterWorkerNode.info).toStrictEqual({ |
c3719753 | 236 | id: clusterWorkerNode.worker.id, |
75de9f41 JB |
237 | type: WorkerTypes.cluster, |
238 | dynamic: false, | |
5eb72b9e JB |
239 | ready: false, |
240 | stealing: false | |
75de9f41 JB |
241 | }) |
242 | expect(clusterWorkerNode.usage).toStrictEqual({ | |
26fb3c18 JB |
243 | tasks: { |
244 | executed: 0, | |
245 | executing: 0, | |
246 | queued: 0, | |
247 | maxQueued: 0, | |
463226a4 | 248 | sequentiallyStolen: 0, |
26fb3c18 JB |
249 | stolen: 0, |
250 | failed: 0 | |
251 | }, | |
252 | runTime: { | |
4ba4c7f9 | 253 | history: new CircularArray() |
26fb3c18 JB |
254 | }, |
255 | waitTime: { | |
4ba4c7f9 | 256 | history: new CircularArray() |
26fb3c18 JB |
257 | }, |
258 | elu: { | |
259 | idle: { | |
4ba4c7f9 | 260 | history: new CircularArray() |
26fb3c18 JB |
261 | }, |
262 | active: { | |
4ba4c7f9 | 263 | history: new CircularArray() |
26fb3c18 JB |
264 | } |
265 | } | |
266 | }) | |
75de9f41 JB |
267 | expect(clusterWorkerNode.messageChannel).toBeUndefined() |
268 | expect(clusterWorkerNode.tasksQueueBackPressureSize).toBe(12) | |
95d1a734 | 269 | expect(clusterWorkerNode.tasksQueue).toBeInstanceOf(PriorityQueue) |
75de9f41 | 270 | expect(clusterWorkerNode.tasksQueue.size).toBe(0) |
59ca7cff | 271 | expect(clusterWorkerNode.tasksQueue.k).toBe(6) |
68f1f531 JB |
272 | expect(clusterWorkerNode.tasksQueueSize()).toBe( |
273 | clusterWorkerNode.tasksQueue.size | |
274 | ) | |
275 | expect(clusterWorkerNode.onBackPressureStarted).toBe(false) | |
75de9f41 | 276 | expect(clusterWorkerNode.taskFunctionsUsage).toBeInstanceOf(Map) |
26fb3c18 JB |
277 | }) |
278 | ||
279 | it('Worker node getTaskFunctionWorkerUsage()', () => { | |
280 | expect(() => | |
75de9f41 | 281 | threadWorkerNode.getTaskFunctionWorkerUsage('invalidTaskFunction') |
948faff7 | 282 | ).toThrow( |
26fb3c18 | 283 | new TypeError( |
31847469 | 284 | "Cannot get task function worker usage for task function name 'invalidTaskFunction' when task function properties list is not yet defined" |
26fb3c18 JB |
285 | ) |
286 | ) | |
31847469 JB |
287 | threadWorkerNode.info.taskFunctionsProperties = [ |
288 | { name: DEFAULT_TASK_NAME }, | |
289 | { name: 'fn1' } | |
290 | ] | |
26fb3c18 | 291 | expect(() => |
75de9f41 | 292 | threadWorkerNode.getTaskFunctionWorkerUsage('invalidTaskFunction') |
948faff7 | 293 | ).toThrow( |
26fb3c18 | 294 | new TypeError( |
31847469 | 295 | "Cannot get task function worker usage for task function name 'invalidTaskFunction' when task function properties list has less than 3 elements" |
26fb3c18 JB |
296 | ) |
297 | ) | |
31847469 JB |
298 | threadWorkerNode.info.taskFunctionsProperties = [ |
299 | { name: DEFAULT_TASK_NAME }, | |
300 | { name: 'fn1' }, | |
301 | { name: 'fn2' } | |
302 | ] | |
6cd5248f | 303 | expect( |
75de9f41 | 304 | threadWorkerNode.getTaskFunctionWorkerUsage(DEFAULT_TASK_NAME) |
6cd5248f | 305 | ).toStrictEqual({ |
26fb3c18 JB |
306 | tasks: { |
307 | executed: 0, | |
308 | executing: 0, | |
309 | queued: 0, | |
463226a4 | 310 | sequentiallyStolen: 0, |
5ad42e34 | 311 | stolen: 0, |
26fb3c18 JB |
312 | failed: 0 |
313 | }, | |
314 | runTime: { | |
4ba4c7f9 | 315 | history: new CircularArray() |
26fb3c18 JB |
316 | }, |
317 | waitTime: { | |
4ba4c7f9 | 318 | history: new CircularArray() |
26fb3c18 JB |
319 | }, |
320 | elu: { | |
321 | idle: { | |
4ba4c7f9 | 322 | history: new CircularArray() |
26fb3c18 JB |
323 | }, |
324 | active: { | |
4ba4c7f9 | 325 | history: new CircularArray() |
26fb3c18 JB |
326 | } |
327 | } | |
328 | }) | |
75de9f41 | 329 | expect(threadWorkerNode.getTaskFunctionWorkerUsage('fn1')).toStrictEqual({ |
26fb3c18 JB |
330 | tasks: { |
331 | executed: 0, | |
332 | executing: 0, | |
333 | queued: 0, | |
463226a4 | 334 | sequentiallyStolen: 0, |
5ad42e34 | 335 | stolen: 0, |
26fb3c18 JB |
336 | failed: 0 |
337 | }, | |
338 | runTime: { | |
4ba4c7f9 | 339 | history: new CircularArray() |
26fb3c18 JB |
340 | }, |
341 | waitTime: { | |
4ba4c7f9 | 342 | history: new CircularArray() |
26fb3c18 JB |
343 | }, |
344 | elu: { | |
345 | idle: { | |
4ba4c7f9 | 346 | history: new CircularArray() |
26fb3c18 JB |
347 | }, |
348 | active: { | |
4ba4c7f9 | 349 | history: new CircularArray() |
26fb3c18 JB |
350 | } |
351 | } | |
352 | }) | |
75de9f41 | 353 | expect(threadWorkerNode.getTaskFunctionWorkerUsage('fn2')).toStrictEqual({ |
26fb3c18 JB |
354 | tasks: { |
355 | executed: 0, | |
356 | executing: 0, | |
357 | queued: 0, | |
463226a4 | 358 | sequentiallyStolen: 0, |
5ad42e34 | 359 | stolen: 0, |
26fb3c18 JB |
360 | failed: 0 |
361 | }, | |
362 | runTime: { | |
4ba4c7f9 | 363 | history: new CircularArray() |
26fb3c18 JB |
364 | }, |
365 | waitTime: { | |
4ba4c7f9 | 366 | history: new CircularArray() |
26fb3c18 JB |
367 | }, |
368 | elu: { | |
369 | idle: { | |
4ba4c7f9 | 370 | history: new CircularArray() |
26fb3c18 JB |
371 | }, |
372 | active: { | |
4ba4c7f9 | 373 | history: new CircularArray() |
26fb3c18 JB |
374 | } |
375 | } | |
376 | }) | |
75de9f41 | 377 | expect(threadWorkerNode.taskFunctionsUsage.size).toBe(2) |
26fb3c18 | 378 | }) |
adee6053 JB |
379 | |
380 | it('Worker node deleteTaskFunctionWorkerUsage()', () => { | |
31847469 JB |
381 | expect(threadWorkerNode.info.taskFunctionsProperties).toStrictEqual([ |
382 | { name: DEFAULT_TASK_NAME }, | |
383 | { name: 'fn1' }, | |
384 | { name: 'fn2' } | |
adee6053 JB |
385 | ]) |
386 | expect(threadWorkerNode.taskFunctionsUsage.size).toBe(2) | |
387 | expect( | |
388 | threadWorkerNode.deleteTaskFunctionWorkerUsage('invalidTaskFunction') | |
389 | ).toBe(false) | |
390 | expect(threadWorkerNode.taskFunctionsUsage.size).toBe(2) | |
391 | expect(threadWorkerNode.deleteTaskFunctionWorkerUsage('fn1')).toBe(true) | |
392 | expect(threadWorkerNode.taskFunctionsUsage.size).toBe(1) | |
393 | }) | |
26fb3c18 | 394 | }) |