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