123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- /* 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)
- })
- })
- })
- })
- })
|