diff --git a/.codeclimate.yml b/.codeclimate.yml index 9a7575a..fa4a7f7 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -2,4 +2,5 @@ version: '2' exclude_patterns: - '**/test/' + - '**/examples/' - '.yarn/' diff --git a/src/Deque.ts b/src/Deque.ts index 8096c70..7c0cb4c 100644 --- a/src/Deque.ts +++ b/src/Deque.ts @@ -1,6 +1,10 @@ // Deque is based on https://github.com/petkaantonov/deque/blob/master/js/deque.js // Released under the MIT License: https://github.com/petkaantonov/deque/blob/6ef4b6400ad3ba82853fdcc6531a38eb4f78c18c/LICENSE /*eslint-disable*/ +export const MIN_CAPACITY = 4; +export const MAX_CAPACITY = 1073741824; +export const RESIZE_MULTIPLER = 1; + function arrayMove(src: any[], srcIndex: number, dst: any[], dstIndex: number, len: number) { for (let j = 0; j < len; ++j) { dst[j + dstIndex] = src[j + srcIndex]; @@ -20,10 +24,6 @@ function pow2AtLeast(n: number) { return n + 1; } -export const MIN_CAPACITY = 4; -export const MAX_CAPACITY = 1073741824; -export const RESIZE_MULTIPLER = 1; - export function getCapacity(capacity: number) { return pow2AtLeast(Math.min(Math.max(MIN_CAPACITY, capacity), MAX_CAPACITY)); } diff --git a/src/Sema.ts b/src/Sema.ts index a1681a6..35a7a9d 100644 --- a/src/Sema.ts +++ b/src/Sema.ts @@ -41,7 +41,7 @@ export class Sema { capacity?: number; } = {}, ) { - if (isFn(pauseFn) !== isFn(resumeFn)) { + if (isFn(pauseFn) && !isFn(resumeFn)) { throw new Error('pauseFn and resumeFn must be both set for pausing'); } diff --git a/test/Sema.ts b/test/Sema.ts index df4ca97..190d536 100644 --- a/test/Sema.ts +++ b/test/Sema.ts @@ -1,25 +1,8 @@ -import { Sema } from '../src/index'; +import { Sema } from '../src/Sema'; -describe('General', () => { - test('Pausing works', () => { - const pauseFn = jest.fn(); - const resumeFn = jest.fn(); - const s = new Sema(5, { pauseFn, resumeFn }); - - for (let i = 0; i < 5; i += 1) { - // eslint-disable-next-line - s.acquire().catch(console.error); - } - - expect(pauseFn).not.toHaveBeenCalled(); - expect(resumeFn).not.toHaveBeenCalled(); - - // eslint-disable-next-line - s.acquire().catch(console.error); - expect(pauseFn).toHaveBeenCalled(); - s.release(); - s.release(); - expect(resumeFn).toHaveBeenCalled(); +describe('Sema.constructor', () => { + it('should throw an error if a pauseFn is passed, but a resumeFn isnt', () => { + expect(() => new Sema(3, { pauseFn: () => 3 })).toThrow(); }); test('initFn is called properly', () => { @@ -29,6 +12,17 @@ describe('General', () => { expect(s).toBeInstanceOf(Sema); expect(initFn).toHaveReturnedTimes(3); }); +}); + +describe('Sema.acquire', () => { + it('should aquire a sephamore', async () => { + const s = new Sema(1); + + const token = await s.acquire(); + + expect(token).toBe('1'); + expect(s.waiting()).toEqual(0); + }); test('Tokens are returned properly', async () => { let maxConcurrency = 0; @@ -47,17 +41,6 @@ describe('General', () => { }); }); -describe('Sema.acquire', () => { - it('should aquire a sephamore', async () => { - const s = new Sema(1); - - const token = await s.acquire(); - - expect(token).toBe('1'); - expect(s.waiting()).toEqual(0); - }); -}); - describe('Sema.tryAcquire', () => { it('should return undefined no sephamores are free', async () => { const s = new Sema(1); @@ -79,6 +62,59 @@ describe('Sema.release', () => { expect(s.tryAcquire()).toBeDefined(); expect(s.tryAcquire()).toBeUndefined(); }); + + it('should use the release token value if an initFn is passed during instance creation', () => { + const s = new Sema(1, { initFn: () => 1 }); + + /* eslint-disable @typescript-eslint/ban-ts-comment, no-underscore-dangle */ + + // @ts-expect-error + const spy = jest.spyOn(s.releaseEmitter, 'emit'); + + /* eslint-enable */ + + expect(s.tryAcquire()).toBeDefined(); + + s.release(1); + + expect(spy).toHaveBeenCalledWith('release', 1); + }); + + it('should use the default token value if an initFn is not passed during instance creation', () => { + const s = new Sema(1); + + /* eslint-disable @typescript-eslint/ban-ts-comment, no-underscore-dangle */ + // @ts-expect-error + const spy = jest.spyOn(s.releaseEmitter, 'emit'); + /* eslint-enable */ + + expect(s.tryAcquire()).toBeDefined(); + + s.release(); + + expect(spy).toHaveBeenCalledWith('release', '1'); + }); + + it('should execute the pause function once the limit is met', () => { + const pauseFn = jest.fn(); + const resumeFn = jest.fn(); + const s = new Sema(5, { pauseFn, resumeFn }); + + for (let i = 0; i < 5; i += 1) { + // eslint-disable-next-line + s.acquire().catch(console.error); + } + + expect(pauseFn).not.toHaveBeenCalled(); + expect(resumeFn).not.toHaveBeenCalled(); + + // eslint-disable-next-line + s.acquire().catch(console.error); + expect(pauseFn).toHaveBeenCalled(); + s.release(); + s.release(); + expect(resumeFn).toHaveBeenCalled(); + }); }); describe('Sema.waiting', () => { @@ -131,7 +167,7 @@ describe('Sema.waiting', () => { expect(s.waiting()).toEqual(0); }); - it('should hanlde greater maxConcurrency', async () => { + it('should handle greater maxConcurrency', async () => { const s = new Sema(3); await s.acquire(); @@ -144,3 +180,16 @@ describe('Sema.waiting', () => { expect(s.waiting()).toEqual(0); }); }); + +describe('Sema.drain', () => { + it('should acquire the maxConcurrency of sephamores', async () => { + const s = new Sema(3); + + const spy = jest.spyOn(s, 'acquire'); + + const all = await s.drain(); + + expect(all.length).toBe(3); + expect(spy).toHaveBeenCalledTimes(3); + }); +}); diff --git a/test/Utils.ts b/test/Utils.ts index cdf771d..7209165 100644 --- a/test/Utils.ts +++ b/test/Utils.ts @@ -1,5 +1,4 @@ import { Sema } from '../src/Sema'; - import { createRateLimiter } from '../src/Utils'; jest.useFakeTimers(); @@ -79,4 +78,12 @@ describe('createRateLimiter', () => { expect(acquireFn).toHaveBeenCalledTimes(1); }); + + it('should throw an error if the rptu is not an integer', () => { + expect(() => createRateLimiter(0.5)).toThrow(); + }); + + it('should throw an error if the rptu is negative', () => { + expect(() => createRateLimiter(-5)).toThrow(); + }); }); diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 0000000..6e54e02 --- /dev/null +++ b/test/index.ts @@ -0,0 +1,13 @@ +import { Sema, createRateLimiter } from '../src/index'; + +describe('Exports', () => { + it('should export Sema', () => { + expect(Sema).toBeDefined(); + expect(Sema).toBeInstanceOf(Object); + }); + + it('should export createRateLimiter helper function', () => { + expect(createRateLimiter).toBeDefined(); + expect(createRateLimiter).toBeInstanceOf(Function); + }); +});