the-forest/client/node_modules/promise-limit/test/spec.js

271 lines
7.0 KiB
JavaScript
Raw Normal View History

2024-09-17 20:35:18 -04:00
/* eslint-env mocha */
var times = require('lowscore/times')
var range = require('lowscore/range')
var limiter = require('..')
var chai = require('chai')
var expect = chai.expect
chai.use(require('chai-as-promised'))
var max = require('./max')
describe('promise-limit', function () {
var output
beforeEach(function () {
output = []
})
function wait (text, n) {
output.push('starting', text)
return new Promise(function (resolve) {
setTimeout(resolve, n)
}).then(function () {
output.push('finished', text)
return text
})
}
function expectMaxOutstanding (n) {
var outstanding = 0
var outstandingOverTime = output.map(function (line) {
if (line.match(/starting/)) {
outstanding++
} else if (line.match(/finished/)) {
outstanding--
}
return outstanding
})
var maxOutstanding = max(outstandingOverTime)
expect(maxOutstanding).to.equal(n)
}
it('limits the number of outstanding calls to a function', function () {
var limit = limiter(5)
return Promise.all(times(9, function (i) {
return limit(function () {
return wait('job ' + (i + 1), 100)
})
})).then(function () {
expectMaxOutstanding(5)
})
})
it("doesn't limit if the number outstanding is the limit", function () {
var limit = limiter(5)
return Promise.all(times(5, function (i) {
return limit(function () {
return wait('job ' + (i + 1), 100)
})
})).then(function () {
expectMaxOutstanding(5)
})
})
it("doesn't limit if the number outstanding less than the limit", function () {
var limit = limiter(5)
return Promise.all(times(4, function (i) {
return limit(function () {
return wait('job ' + (i + 1), 100)
})
})).then(function () {
expectMaxOutstanding(4)
})
})
it('returns the results from each job', function () {
var limit = limiter(5)
return Promise.all(times(9, function (i) {
return limit(function () {
return wait('job ' + (i + 1), 100)
})
})).then(function (results) {
expect(results).to.eql(times(9, function (i) {
return 'job ' + (i + 1)
}))
})
})
it('returns a rejected promise if the function throws an error', function () {
var limit = limiter(5)
var promise = limit(function () {
throw new Error('uh oh')
})
expect(promise).to.be.a('promise')
return promise.then(function () {
throw new Error('the promise resolved, instead of rejecting')
}).catch(function (err) {
expect(String(err)).to.equal('Error: uh oh')
})
})
it('should fulfil or reject when the function fulfils or rejects', function () {
var limit = limiter(2)
var numbers = [1, 2, 3, 4, 5, 6]
function rejectOdd (n) {
return new Promise(function (resolve, reject) {
if (n % 2 === 0) {
resolve(n + ' is even')
} else {
reject(new Error(n + ' is odd'))
}
})
}
return Promise.all(numbers.map(function (i) {
return limit(function () {
return rejectOdd(i)
}).then(function (r) {
return 'pass: ' + r
}, function (e) {
return 'fail: ' + e.message
})
})).then(function (results) {
expect(results).to.eql([
'fail: 1 is odd',
'pass: 2 is even',
'fail: 3 is odd',
'pass: 4 is even',
'fail: 5 is odd',
'pass: 6 is even'
])
})
})
describe('no limit', function () {
function expectNoLimit (limit) {
return Promise.all(times(9, function (i) {
return limit(function () { return wait('job ' + (i + 1), 100) })
})).then(function () {
expectMaxOutstanding(9)
}).then(function () {
return expectNoMapLimit(limit)
})
}
function expectNoMapLimit (limit) {
return limit.map(range(0, 9), function (i) {
return wait('job ' + (i + 1), 100)
}).then(function () {
expectMaxOutstanding(9)
})
}
it("doesn't limit if the limit is 0", function () {
var limit = limiter(0)
return expectNoLimit(limit)
})
it("doesn't limit if the limit is undefined", function () {
var limit = limiter()
return expectNoLimit(limit)
})
})
describe('map', function () {
function failsAt1 (num) {
if (num === 1) return Promise.reject(new Error('rejecting number ' + num))
else return Promise.resolve('accepting number ' + num)
}
function resolvesAll (num) {
return Promise.resolve('accepting number ' + num)
}
it('returns all results when all are resolved', function () {
var limit = limiter(2)
return limit.map([0, 1, 2, 3], resolvesAll).then(function (results) {
expect(results).to.eql([
'accepting number 0',
'accepting number 1',
'accepting number 2',
'accepting number 3'
])
})
})
it('returns first failure when one fails', function () {
var limit = limiter(2)
return expect(limit.map([0, 1, 2, 3], failsAt1)).to.be.rejectedWith('rejecting number 1')
})
it('limiter can still be used after map with failure', function () {
var limit = limiter(2)
return expect(limit.map([0, 1, 2, 3], failsAt1)).to.be.rejectedWith('rejecting number 1').then(function () {
return limit.map([0, 1, 2, 3], resolvesAll).then(function (results) {
expect(results).to.eql([
'accepting number 0',
'accepting number 1',
'accepting number 2',
'accepting number 3'
])
})
})
})
})
describe('large numbers of tasks failing', function () {
context('when error thrown', function () {
function alwaysFails (n) {
throw new Error('argh: ' + n)
}
it("doesn't exceed stack size", function () {
var limit = limiter(2)
return expect(limit.map(range(0, 1000), alwaysFails)).to.be.rejectedWith('argh: 0')
})
})
context('when error rejected', function () {
function alwaysFails (n) {
return Promise.reject(new Error('argh: ' + n))
}
it("doesn't exceed stack size", function () {
var limit = limiter(2)
return expect(limit.map(range(0, 1000), alwaysFails)).to.be.rejectedWith('argh: 0')
})
})
})
describe('queue length', function () {
it('updates the queue length when there are more jobs than there is concurrency', function () {
var limit = limiter(2)
var one = limit(function () { return wait('one', 10) })
expect(limit.queue).to.equal(0)
var two = limit(function () { return wait('two', 20) })
expect(limit.queue).to.equal(0)
limit(function () { return wait('three', 100) })
expect(limit.queue).to.equal(1)
limit(function () { return wait('four', 100) })
expect(limit.queue).to.equal(2)
return one.then(function () {
expect(limit.queue).to.equal(1)
return two.then(function () {
expect(limit.queue).to.equal(0)
})
})
})
})
})