Completed
Push — dev ( d30456...4fd03f )
by Fike
33s
created

test/suites/integration/concurrent/TaskQueue.spec.js   F

Complexity

Total Complexity 213
Complexity/F 2.42

Size

Lines of Code 402
Function Count 88

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 0
c 2
b 0
f 0
nc 1
dl 0
loc 402
rs 3.12
wmc 213
mnd 1
bc 93
fnc 88
bpm 1.0568
cpm 2.4204
noi 4

How to fix   Complexity   

Complexity

Complex classes like test/suites/integration/concurrent/TaskQueue.spec.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/* eslint-env mocha */
2
/* eslint-disable no-unused-expressions */
3
4
var Sinon = require('sinon')
5
var Chai = require('chai')
6
var expect = Chai.expect
7
var Concurrent = require('../../../../lib').Concurrent
8
var TaskQueue = Concurrent.TaskQueue
9
var RejectionException = Concurrent.RejectionException
10
var TimeoutException = Concurrent.TimeoutException
11
var Future = Concurrent.Future
12
13
var branchStopper = function () {
14
  throw new Error('This branch should not have been entered')
15
}
16
17
describe('Integration', function () {
18
  describe('/concurrent', function () {
19
    describe('/TaskQueue.js', function () {
20
      describe('.TaskQueue', function () {
21
        describe('< new', function () {
22
          it('may be created without any options', function () {
23
            return new TaskQueue()
24
          })
25
26
          it('is paused by default', function () {
27
            expect(new TaskQueue().isPaused()).to.be.true
28
          })
29
30
          it('is not terminated by default', function () {
31
            expect(new TaskQueue().isClosed()).to.be.false
32
          })
33
34
          it('allows to set name', function () {
35
            // yet another test for 100% coverage
36
            /* eslint-disable no-new */
37
            var logger = {attach: Sinon.stub()}
38
            var name = 'custom-name'
39
            new TaskQueue({name: name, logger: {instance: logger}})
40
            expect(logger.attach.callCount).to.eq(1)
41
            expect(logger.attach.getCall(0).args[0]).to.eq('name')
42
            expect(logger.attach.getCall(0).args[1]).to.eq(name)
43
          })
44
        })
45
46
        describe('#setName', function () {
47
          it('allows to set name externally', function () {
48
            var logger = {attach: Sinon.stub()}
49
            var name = 'custom-name'
50
            var queue = new TaskQueue({logger: {instance: logger}})
51
            queue.setName(name)
52
            expect(logger.attach.callCount).to.eq(1)
53
            expect(logger.attach.getCall(0).args[0]).to.eq('name')
54
            expect(logger.attach.getCall(0).args[1]).to.eq(name)
55
          })
56
        })
57
58
        describe('#start', function () {
59
          it('processes single task submitted before start', function () {
60
            var queue = new TaskQueue()
61
            var factory = Sinon.stub()
62
            var promise = queue.push(factory)
63
            queue.start()
64
            return promise
65
              .then(function () {
66
                expect(factory.callCount).to.eq(1)
67
              })
68
          })
69
70
          it('processes single task submitted after start', function () {
71
            var queue = new TaskQueue()
72
            var factory = Sinon.stub()
73
            queue.start()
74
            var promises = [
75
              queue.push(factory),
76
              queue.push(factory),
77
              queue.push(factory)
78
            ]
79
            return Promise
80
              .all(promises)
81
              .then(function () {
82
                expect(factory.callCount).to.eq(3)
83
              })
84
          })
85
86
          it('processes several tasks submitted before start', function () {
87
            var queue = new TaskQueue()
88
            var factory = Sinon.stub()
89
            queue.push(factory)
90
            queue.push(factory)
91
            var promise = queue.push(factory)
92
            queue.start()
93
            return promise
94
              .then(function () {
95
                expect(factory.callCount).to.eq(3)
96
              })
97
          })
98
99
          it('processes several tasks submitted after start', function () {
100
            var queue = new TaskQueue()
101
            var factory = Sinon.stub()
102
            queue.start()
103
            var promises = [
104
              queue.push(factory),
105
              queue.push(factory),
106
              queue.push(factory)
107
            ]
108
            return Promise
109
              .all(promises)
110
              .then(function () {
111
                expect(factory.callCount).to.eq(3)
112
              })
113
          })
114
115
          it('processes several tasks submitted before and after start', function () {
116
            var queue = new TaskQueue()
117
            var factory = Sinon.stub()
118
            var promises = [
119
              queue.push(factory),
120
              queue.push(factory)
121
            ]
122
            queue.start()
123
            promises.push(queue.push(factory))
124
            promises.push(queue.push(factory))
125
            return Promise
126
              .all(promises)
127
              .then(function () {
128
                expect(factory.callCount).to.eq(4)
129
              })
130
          })
131
        })
132
133
        describe('#push', function () {
134
          it('times out task exceeding specified timeout', function () {
135
            var queue = TaskQueue.started()
136
            var factory = Sinon.spy(function () {
137
              return new Promise(function () {})
138
            })
139
            return queue
140
              .push(factory, {timeout: 0})
141
              .then(branchStopper, function (error) {
142
                expect(factory.callCount).to.eq(1)
143
                expect(error).to.be.instanceOf(TimeoutException)
144
              })
145
          })
146
147
          it('returns promise rejected with thrown error', function () {
148
            var queue = new TaskQueue().start()
149
            var error = new Error()
150
            var factory = function () { throw error }
151
            var future = queue.push(factory)
152
            return expect(future).to.eventually.be.rejectedWith(error)
153
          })
154
        })
155
156
        describe('#pause', function () {
157
          it('returns promise resolving after current task completion', function () {
158
            var queue = TaskQueue.started()
159
            var barrier = new Future()
160
            var invoked = Sinon.spy(function () { return barrier })
161
            var suppressed = Sinon.stub()
162
            queue.push(invoked)
163
            queue.push(suppressed)
164
            var pause = queue.pause()
165
            barrier.resolve()
166
            return pause
167
              .then(function () {
168
                expect(invoked.callCount).to.eq(1)
169
                expect(suppressed.callCount).to.eq(0)
170
              })
171
          })
172
173
          it('stops task processing after current task completion', function () {
174
            var queue = TaskQueue.started()
175
            var barrier = new Future()
176
            var invoked = Sinon.spy(function () { return barrier })
177
            var suppressed = Sinon.stub()
178
            var paused = false
179
            queue.push(invoked)
180
            queue.push(suppressed)
181
            var pause = queue.pause()
182
            pause.then(function () { paused = true })
183
            barrier.resolve()
184
            var future = new Promise(function (resolve) {
185
              setTimeout(resolve, 1)
186
            })
187
            return future
188
              .then(function () {
189
                expect(invoked.callCount).to.eq(1)
190
                expect(suppressed.callCount).to.eq(0)
191
                expect(paused).to.be.true
192
              })
193
          })
194
195
          it('correctly executes if there is no current task', function () {
196
            return TaskQueue.started().pause()
197
          })
198
        })
199
200
        describe('#close', function () {
201
          it('works with no tasks in queue', function () {
202
            return (new TaskQueue()).close()
203
          })
204
205
          it('returns when all current tasks are resolved', function () {
206
            var queue = new TaskQueue()
207
            var expectation = []
208
            var result = []
209
            var barriers = []
210
            for (var i = 0; i < 3; i++) {
211
              var barrier = new Future()
212
              barriers.push(barrier)
213
              expectation.push(i)
214
              var closure = function (i, future) {
215
                queue.push(function () {
216
                  return future.then(function () {
217
                    result.push(i)
218
                  })
219
                })
220
              }
221
              closure(i, barrier)
222
            }
223
            var finalization = queue.close()
224
            queue.start()
225
            barriers.forEach(function (barrier) {
226
              barrier.resolve()
227
            })
228
            return finalization
229
              .then(function () {
230
                expect(result).to.deep.eq(expectation)
231
              })
232
          })
233
234
          it('returns successfully even if tasks fail', function () {
235
            var queue = new TaskQueue().start()
236
            queue.push(function () { return Promise.reject(new Error()) })
237
            return queue.close()
238
          })
239
240
          it('forces rejection of new tasks', function () {
241
            var queue = new TaskQueue()
242
            queue.close()
243
            var future = queue.push(function () {})
244
            return expect(future).to.eventually.be.rejected
245
          })
246
        })
247
248
        describe('#terminate', function () {
249
          it('returns promised statistics instantly if there is no current task', function () {
250
            var queue = new TaskQueue()
251
            var expectation = {
252
              enqueued: 0,
253
              completed: 0,
254
              successful: 0,
255
              discarded: 0,
256
              rejected: 0
257
            }
258
            return expect(queue.terminate()).to.eventually.deep.eq(expectation)
259
          })
260
261
          it('returns promise resolving as soon as current task completes', function () {
262
            var queue = TaskQueue.started()
263
            queue.push(function () { return Promise.resolve() })
264
            var expectation = {
265
              enqueued: 1,
266
              completed: 1,
267
              successful: 1,
268
              discarded: 0,
269
              rejected: 0
270
            }
271
            return expect(queue.terminate()).to.eventually.deep.eq(expectation)
272
          })
273
274
          it('discards extra tasks', function () {
275
            var barrier = new Future()
276
            var queue = TaskQueue.started()
277
            var discarded = Sinon.stub()
278
            var factory = Sinon.spy(function () { return barrier })
279
            var expectation = {
280
              enqueued: 2,
281
              completed: 1,
282
              successful: 1,
283
              discarded: 1,
284
              rejected: 0
285
            }
286
            queue.push(factory)
287
            queue.push(discarded)
288
            var termination = queue.terminate()
289
            barrier.resolve()
290
            return termination
291
              .then(function (statistics) {
292
                expect(statistics).to.deep.eq(expectation)
293
                expect(factory.callCount).to.eq(1)
294
                expect(discarded.callCount).to.eq(0)
295
              })
296
          })
297
298
          it('forces rejection of new tasks', function () {
299
            var queue = TaskQueue.started()
300
            queue.terminate()
301
            var task = queue.push(function () {})
302
            return expect(task).to.eventually.be.rejectedWith(RejectionException)
303
          })
304
        })
305
306
        describe('#statistics', function () {
307
          it('returns correct numbers', function () {
308
            var queue = new TaskQueue().start()
309
            queue.push(function () {})
310
            var future = queue.push(function () { throw new Error() })
311
            queue.push(function () { return new Promise(function () {}) })
312
            return future
313
              .then(branchStopper, function () {
314
                expect(queue.getStatistics().enqueued).to.eq(3)
315
                expect(queue.getStatistics().completed).to.eq(2)
316
                expect(queue.getStatistics().successful).to.eq(1)
317
              })
318
          })
319
        })
320
321
        describe('#getLength', function () {
322
          it('returns zero for fresh queue', function () {
323
            expect(new TaskQueue().getLength()).to.eq(0)
324
          })
325
326
          it('returns actual length if queue has been populated', function () {
327
            var queue = new TaskQueue()
328
            var limit = 4
329
            for (var i = 0; i < limit; i++) {
330
              queue.push(function () { return new Promise(function () {}) })
331
            }
332
            expect(queue.getLength()).to.eq(limit)
333
          })
334
335
          it('returns zero when last task is completed', function () {
336
            var queue = TaskQueue.started()
337
            var limit = 4
338
            for (var i = 0; i < limit; i++) {
339
              queue.push(function () {})
340
            }
341
            return queue
342
              .push(function () {})
343
              .then(function () {
344
                expect(queue.getLength()).to.eq(0)
345
              })
346
          })
347
        })
348
349
        describe('> invariants', function () {
350
          it('executes tasks sequentially', function () {
351
            var queue = TaskQueue.started()
352
            var barriers = []
353
            var limit = 4
354
            var result = []
355
            var expectation = []
356
            var i
357
            for (i = 0; i < limit; i++) {
358
              expectation.push(i)
359
              var closure = function (i) {
360
                var future = new Future()
361
                barriers[i] = future
362
                queue.push(function () {
363
                  return future
364
                    .then(function () {
365
                      result.push(i)
366
                    })
367
                })
368
              }
369
              closure(i)
370
            }
371
            for (i = limit - 1; i >= 0; i--) {
372
              barriers[i].resolve()
373
            }
374
            return queue
375
              .close()
376
              .then(function () {
377
                expect(result).to.deep.eq(expectation)
378
              })
379
          })
380
381
          it('tolerates non-error rejection', function () {
382
            var value = {}
383
            var rejected = Promise.reject(value)
384
            var queue = TaskQueue.started()
385
            var task = queue.push(function () {
386
              return rejected
387
            })
388
            return expect(task).to.eventually.be.rejectedWith(value)
389
          })
390
        })
391
      })
392
393
      describe('.RejectionException', function () {
394
        it('is supplied with corresponding name', function () {
395
          expect(new RejectionException()).to.have.property('name').eq('RejectionException')
396
        })
397
398
        it('provides default message', function () {
399
          // yes i'm still serious about that 100% coverage that nobody needs
400
          expect(new RejectionException()).to.have.property('message')
401
        })
402
      })
403
    })
404
  })
405
})
406