refactor: sensible defaults for worker choice strategy policy
[poolifier.git] / tests / worker / abstract-worker.test.js
1 const { expect } = require('expect')
2 const sinon = require('sinon')
3 const { ClusterWorker, KillBehaviors, ThreadWorker } = require('../../lib')
4 const { EMPTY_FUNCTION } = require('../../lib/utils')
5
6 describe('Abstract worker test suite', () => {
7 class StubWorkerWithMainWorker extends ThreadWorker {
8 constructor (fn, opts) {
9 super(fn, opts)
10 this.mainWorker = undefined
11 }
12 }
13
14 it('Verify worker options default values', () => {
15 const worker = new ThreadWorker(() => {})
16 expect(worker.opts.maxInactiveTime).toStrictEqual(60000)
17 expect(worker.opts.killBehavior).toBe(KillBehaviors.SOFT)
18 expect(worker.opts.killHandler).toStrictEqual(EMPTY_FUNCTION)
19 expect(worker.opts.async).toBe(undefined)
20 })
21
22 it('Verify that worker options are set at worker creation', () => {
23 const killHandler = () => {
24 console.info('Worker received kill message')
25 }
26 const worker = new ClusterWorker(() => {}, {
27 maxInactiveTime: 6000,
28 killBehavior: KillBehaviors.HARD,
29 killHandler,
30 async: true
31 })
32 expect(worker.opts.maxInactiveTime).toStrictEqual(6000)
33 expect(worker.opts.killBehavior).toBe(KillBehaviors.HARD)
34 expect(worker.opts.killHandler).toStrictEqual(killHandler)
35 expect(worker.opts.async).toBe(undefined)
36 })
37
38 it('Verify that taskFunctions parameter is mandatory', () => {
39 expect(() => new ClusterWorker()).toThrowError(
40 'taskFunctions parameter is mandatory'
41 )
42 })
43
44 it('Verify that taskFunctions parameter is a function or a plain object', () => {
45 expect(() => new ClusterWorker(0)).toThrowError(
46 new TypeError(
47 'taskFunctions parameter is not a function or a plain object'
48 )
49 )
50 expect(() => new ClusterWorker('')).toThrowError(
51 new TypeError(
52 'taskFunctions parameter is not a function or a plain object'
53 )
54 )
55 expect(() => new ClusterWorker(true)).toThrowError(
56 new TypeError(
57 'taskFunctions parameter is not a function or a plain object'
58 )
59 )
60 expect(() => new ClusterWorker([])).toThrowError(
61 new TypeError(
62 'taskFunctions parameter is not a function or a plain object'
63 )
64 )
65 expect(() => new ClusterWorker(new Map())).toThrowError(
66 new TypeError(
67 'taskFunctions parameter is not a function or a plain object'
68 )
69 )
70 expect(() => new ClusterWorker(new Set())).toThrowError(
71 new TypeError(
72 'taskFunctions parameter is not a function or a plain object'
73 )
74 )
75 expect(() => new ClusterWorker(new WeakMap())).toThrowError(
76 new TypeError(
77 'taskFunctions parameter is not a function or a plain object'
78 )
79 )
80 expect(() => new ClusterWorker(new WeakSet())).toThrowError(
81 new TypeError(
82 'taskFunctions parameter is not a function or a plain object'
83 )
84 )
85 })
86
87 it('Verify that taskFunctions parameter is not an empty object', () => {
88 expect(() => new ClusterWorker({})).toThrowError(
89 new Error('taskFunctions parameter object is empty')
90 )
91 })
92
93 it('Verify that taskFunctions parameter with unique function is taken', () => {
94 const worker = new ThreadWorker(() => {})
95 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
96 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
97 expect(worker.taskFunctions.size).toBe(2)
98 expect(worker.taskFunctions.get('default')).toStrictEqual(
99 worker.taskFunctions.get('fn1')
100 )
101 })
102
103 it('Verify that taskFunctions parameter with multiple task functions contains function', () => {
104 const fn1 = () => {
105 return 1
106 }
107 const fn2 = ''
108 expect(() => new ThreadWorker({ fn1, fn2 })).toThrowError(
109 new TypeError('A taskFunctions parameter object value is not a function')
110 )
111 })
112
113 it('Verify that taskFunctions parameter with multiple task functions is taken', () => {
114 const fn1 = () => {
115 return 1
116 }
117 const fn2 = () => {
118 return 2
119 }
120 const worker = new ClusterWorker({ fn1, fn2 })
121 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
122 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
123 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
124 expect(worker.taskFunctions.size).toBe(3)
125 expect(worker.taskFunctions.get('default')).toStrictEqual(
126 worker.taskFunctions.get('fn1')
127 )
128 })
129
130 it('Verify that sync kill handler is called when worker is killed', () => {
131 const worker = new ClusterWorker(() => {}, {
132 killHandler: sinon.stub().returns()
133 })
134 worker.isMain = false
135 worker.getMainWorker = sinon.stub().returns({
136 id: 1,
137 send: sinon.stub().returns()
138 })
139 worker.handleKillMessage()
140 expect(worker.getMainWorker().send.calledOnce).toBe(true)
141 expect(worker.opts.killHandler.calledOnce).toBe(true)
142 })
143
144 it('Verify that async kill handler is called when worker is killed', () => {
145 const killHandlerStub = sinon.stub().returns()
146 const worker = new ClusterWorker(() => {}, {
147 killHandler: async () => Promise.resolve(killHandlerStub())
148 })
149 worker.isMain = false
150 worker.handleKillMessage()
151 expect(killHandlerStub.calledOnce).toBe(true)
152 })
153
154 it('Verify that handleError() method works properly', () => {
155 const error = new Error('Error as an error')
156 const worker = new ClusterWorker(() => {})
157 expect(worker.handleError(error)).not.toBeInstanceOf(Error)
158 expect(worker.handleError(error)).toStrictEqual(error.message)
159 const errorMessage = 'Error as a string'
160 expect(worker.handleError(errorMessage)).toStrictEqual(errorMessage)
161 })
162
163 it('Verify that getMainWorker() throw error if main worker is not set', () => {
164 expect(() =>
165 new StubWorkerWithMainWorker(() => {}).getMainWorker()
166 ).toThrowError('Main worker not set')
167 })
168
169 it('Verify that hasTaskFunction() works', () => {
170 const fn1 = () => {
171 return 1
172 }
173 const fn2 = () => {
174 return 2
175 }
176 const worker = new ClusterWorker({ fn1, fn2 })
177 expect(worker.hasTaskFunction('default')).toBe(true)
178 expect(worker.hasTaskFunction('fn1')).toBe(true)
179 expect(worker.hasTaskFunction('fn2')).toBe(true)
180 expect(worker.hasTaskFunction('fn3')).toBe(false)
181 })
182
183 it('Verify that addTaskFunction() works', () => {
184 const fn1 = () => {
185 return 1
186 }
187 const fn2 = () => {
188 return 2
189 }
190 const fn1Replacement = () => {
191 return 3
192 }
193 const worker = new ThreadWorker(fn1)
194 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
195 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
196 expect(worker.taskFunctions.size).toBe(2)
197 expect(worker.taskFunctions.get('default')).toStrictEqual(
198 worker.taskFunctions.get('fn1')
199 )
200 expect(() => worker.addTaskFunction('default', fn2)).toThrowError(
201 new Error('Cannot add a task function with the default reserved name')
202 )
203 worker.addTaskFunction('fn2', fn2)
204 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
205 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
206 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
207 expect(worker.taskFunctions.size).toBe(3)
208 expect(worker.taskFunctions.get('default')).toStrictEqual(
209 worker.taskFunctions.get('fn1')
210 )
211 worker.addTaskFunction('fn1', fn1Replacement)
212 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
213 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
214 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
215 expect(worker.taskFunctions.size).toBe(3)
216 expect(worker.taskFunctions.get('default')).toStrictEqual(
217 worker.taskFunctions.get('fn1')
218 )
219 })
220
221 it('Verify that removeTaskFunction() works', () => {
222 const fn1 = () => {
223 return 1
224 }
225 const fn2 = () => {
226 return 2
227 }
228 const worker = new ClusterWorker({ fn1, fn2 })
229 worker.getMainWorker = sinon.stub().returns({
230 id: 1,
231 send: sinon.stub().returns()
232 })
233 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
234 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
235 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
236 expect(worker.taskFunctions.size).toBe(3)
237 expect(worker.taskFunctions.get('default')).toStrictEqual(
238 worker.taskFunctions.get('fn1')
239 )
240 expect(() => worker.removeTaskFunction('default')).toThrowError(
241 new Error(
242 'Cannot remove the task function with the default reserved name'
243 )
244 )
245 expect(() => worker.removeTaskFunction('fn1')).toThrowError(
246 new Error(
247 'Cannot remove the task function used as the default task function'
248 )
249 )
250 worker.removeTaskFunction('fn2')
251 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
252 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
253 expect(worker.taskFunctions.get('fn2')).toBeUndefined()
254 expect(worker.taskFunctions.size).toBe(2)
255 expect(worker.getMainWorker().send.calledOnce).toBe(true)
256 })
257
258 it('Verify that listTaskFunctions() works', () => {
259 const fn1 = () => {
260 return 1
261 }
262 const fn2 = () => {
263 return 2
264 }
265 const worker = new ClusterWorker({ fn1, fn2 })
266 expect(worker.listTaskFunctions()).toStrictEqual(['default', 'fn1', 'fn2'])
267 })
268
269 it('Verify that setDefaultTaskFunction() works', () => {
270 const fn1 = () => {
271 return 1
272 }
273 const fn2 = () => {
274 return 2
275 }
276 const worker = new ThreadWorker({ fn1, fn2 })
277 expect(worker.taskFunctions.get('default')).toBeInstanceOf(Function)
278 expect(worker.taskFunctions.get('fn1')).toBeInstanceOf(Function)
279 expect(worker.taskFunctions.get('fn2')).toBeInstanceOf(Function)
280 expect(worker.taskFunctions.size).toBe(3)
281 expect(worker.taskFunctions.get('default')).toStrictEqual(
282 worker.taskFunctions.get('fn1')
283 )
284 expect(() => worker.setDefaultTaskFunction('default')).toThrowError(
285 new Error(
286 'Cannot set the default task function reserved name as the default task function'
287 )
288 )
289 worker.setDefaultTaskFunction('fn1')
290 expect(worker.taskFunctions.get('default')).toStrictEqual(
291 worker.taskFunctions.get('fn1')
292 )
293 worker.setDefaultTaskFunction('fn2')
294 expect(worker.taskFunctions.get('default')).toStrictEqual(
295 worker.taskFunctions.get('fn2')
296 )
297 })
298 })