X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=tests%2Fpools%2Fabstract%2Fabstract-pool.test.js;h=54f9382ab9c7d8f0064d699fa0c7a56bba8a4295;hb=4597ddcd5fdeb8cef6a991e965038650fe200509;hp=83290d6a0553243840e9c8b313d2ececacd4386d;hpb=737c6d97f8089996a9e30f77104d58afcf5791ba;p=poolifier.git diff --git a/tests/pools/abstract/abstract-pool.test.js b/tests/pools/abstract/abstract-pool.test.js index 83290d6a..54f9382a 100644 --- a/tests/pools/abstract/abstract-pool.test.js +++ b/tests/pools/abstract/abstract-pool.test.js @@ -1,20 +1,22 @@ const { expect } = require('expect') const { - FixedClusterPool, DynamicThreadPool, + FixedClusterPool, FixedThreadPool, + PoolEvents, WorkerChoiceStrategies } = require('../../../lib/index') +const { CircularArray } = require('../../../lib/circular-array') describe('Abstract pool test suite', () => { const numberOfWorkers = 1 const workerNotFoundInPoolError = new Error( - 'Worker could not be found in the pool' + 'Worker could not be found in the pool worker nodes' ) class StubPoolWithRemoveAllWorker extends FixedThreadPool { removeAllWorker () { - this.workers = new Map() - this.promiseMap.clear() + this.workerNodes = [] + this.promiseResponseMap.clear() } } class StubPoolWithIsMain extends FixedThreadPool { @@ -33,7 +35,7 @@ describe('Abstract pool test suite', () => { errorHandler: e => console.error(e) } ) - ).toThrowError(new Error('Cannot start a pool from a worker!')) + ).toThrowError('Cannot start a pool from a worker!') }) it('Verify that filePath is checked', () => { @@ -50,9 +52,7 @@ describe('Abstract pool test suite', () => { it('Verify that numberOfWorkers is checked', () => { expect(() => new FixedThreadPool()).toThrowError( - new Error( - 'Cannot instantiate a pool without specifying the number of workers' - ) + 'Cannot instantiate a pool without specifying the number of workers' ) }) @@ -85,9 +85,14 @@ describe('Abstract pool test suite', () => { ) expect(pool.opts.enableEvents).toBe(true) expect(pool.emitter).toBeDefined() + expect(pool.opts.enableTasksQueue).toBe(false) + expect(pool.opts.tasksQueueOptions).toBeUndefined() expect(pool.opts.workerChoiceStrategy).toBe( WorkerChoiceStrategies.ROUND_ROBIN ) + expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ + medRunTime: false + }) expect(pool.opts.messageHandler).toBeUndefined() expect(pool.opts.errorHandler).toBeUndefined() expect(pool.opts.onlineHandler).toBeUndefined() @@ -99,7 +104,10 @@ describe('Abstract pool test suite', () => { './tests/worker-files/thread/testWorker.js', { workerChoiceStrategy: WorkerChoiceStrategies.LESS_USED, + workerChoiceStrategyOptions: { medRunTime: true }, enableEvents: false, + enableTasksQueue: true, + tasksQueueOptions: { concurrency: 2 }, messageHandler: testHandler, errorHandler: testHandler, onlineHandler: testHandler, @@ -108,9 +116,14 @@ describe('Abstract pool test suite', () => { ) expect(pool.opts.enableEvents).toBe(false) expect(pool.emitter).toBeUndefined() + expect(pool.opts.enableTasksQueue).toBe(true) + expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 2 }) expect(pool.opts.workerChoiceStrategy).toBe( WorkerChoiceStrategies.LESS_USED ) + expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ + medRunTime: true + }) expect(pool.opts.messageHandler).toStrictEqual(testHandler) expect(pool.opts.errorHandler).toStrictEqual(testHandler) expect(pool.opts.onlineHandler).toStrictEqual(testHandler) @@ -118,7 +131,115 @@ describe('Abstract pool test suite', () => { await pool.destroy() }) - it('Simulate worker not found during getWorkerTasksUsage', async () => { + it('Verify that pool options are validated', async () => { + expect( + () => + new FixedThreadPool( + numberOfWorkers, + './tests/worker-files/thread/testWorker.js', + { + enableTasksQueue: true, + tasksQueueOptions: { concurrency: 0 } + } + ) + ).toThrowError("Invalid worker tasks concurrency '0'") + expect( + () => + new FixedThreadPool( + numberOfWorkers, + './tests/worker-files/thread/testWorker.js', + { + workerChoiceStrategy: 'invalidStrategy' + } + ) + ).toThrowError("Invalid worker choice strategy 'invalidStrategy'") + }) + + it('Verify that worker choice strategy options can be set', async () => { + const pool = new FixedThreadPool( + numberOfWorkers, + './tests/worker-files/thread/testWorker.js', + { workerChoiceStrategy: WorkerChoiceStrategies.FAIR_SHARE } + ) + expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ + medRunTime: false + }) + for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext + .workerChoiceStrategies) { + expect(workerChoiceStrategy.opts).toStrictEqual({ medRunTime: false }) + } + expect( + pool.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime + ).toBe(true) + expect( + pool.workerChoiceStrategyContext.getRequiredStatistics().medRunTime + ).toBe(false) + pool.setWorkerChoiceStrategyOptions({ medRunTime: true }) + expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ + medRunTime: true + }) + for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext + .workerChoiceStrategies) { + expect(workerChoiceStrategy.opts).toStrictEqual({ medRunTime: true }) + } + expect( + pool.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime + ).toBe(false) + expect( + pool.workerChoiceStrategyContext.getRequiredStatistics().medRunTime + ).toBe(true) + pool.setWorkerChoiceStrategyOptions({ medRunTime: false }) + expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ + medRunTime: false + }) + for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext + .workerChoiceStrategies) { + expect(workerChoiceStrategy.opts).toStrictEqual({ medRunTime: false }) + } + expect( + pool.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime + ).toBe(true) + expect( + pool.workerChoiceStrategyContext.getRequiredStatistics().medRunTime + ).toBe(false) + await pool.destroy() + }) + + it('Verify that tasks queue can be enabled/disabled', async () => { + const pool = new FixedThreadPool( + numberOfWorkers, + './tests/worker-files/thread/testWorker.js' + ) + expect(pool.opts.enableTasksQueue).toBe(false) + expect(pool.opts.tasksQueueOptions).toBeUndefined() + pool.enableTasksQueue(true) + expect(pool.opts.enableTasksQueue).toBe(true) + expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 1 }) + pool.enableTasksQueue(true, { concurrency: 2 }) + expect(pool.opts.enableTasksQueue).toBe(true) + expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 2 }) + pool.enableTasksQueue(false) + expect(pool.opts.enableTasksQueue).toBe(false) + expect(pool.opts.tasksQueueOptions).toBeUndefined() + await pool.destroy() + }) + + it('Verify that tasks queue options can be set', async () => { + const pool = new FixedThreadPool( + numberOfWorkers, + './tests/worker-files/thread/testWorker.js', + { enableTasksQueue: true } + ) + expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 1 }) + pool.setTasksQueueOptions({ concurrency: 2 }) + expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 2 }) + expect(() => pool.setTasksQueueOptions({ concurrency: 0 })).toThrowError( + "Invalid worker tasks concurrency '0'" + ) + await pool.destroy() + }) + + it('Simulate worker not found at getWorkerTasksUsage()', async () => { const pool = new StubPoolWithRemoveAllWorker( numberOfWorkers, './tests/worker-files/cluster/testWorker.js', @@ -126,8 +247,10 @@ describe('Abstract pool test suite', () => { errorHandler: e => console.error(e) } ) + expect(pool.workerNodes.length).toBe(numberOfWorkers) // Simulate worker not found. pool.removeAllWorker() + expect(pool.workerNodes.length).toBe(0) expect(() => pool.getWorkerTasksUsage()).toThrowError( workerNotFoundInPoolError ) @@ -139,12 +262,29 @@ describe('Abstract pool test suite', () => { numberOfWorkers, './tests/worker-files/cluster/testWorker.js' ) - for (const value of pool.workers.values()) { - expect(value.tasksUsage).toBeDefined() - expect(value.tasksUsage.run).toBe(0) - expect(value.tasksUsage.running).toBe(0) - expect(value.tasksUsage.runTime).toBe(0) - expect(value.tasksUsage.avgRunTime).toBe(0) + for (const workerNode of pool.workerNodes) { + expect(workerNode.tasksUsage).toBeDefined() + expect(workerNode.tasksUsage.run).toBe(0) + expect(workerNode.tasksUsage.running).toBe(0) + expect(workerNode.tasksUsage.runTime).toBe(0) + expect(workerNode.tasksUsage.runTimeHistory).toBeInstanceOf(CircularArray) + expect(workerNode.tasksUsage.runTimeHistory.length).toBe(0) + expect(workerNode.tasksUsage.avgRunTime).toBe(0) + expect(workerNode.tasksUsage.medRunTime).toBe(0) + expect(workerNode.tasksUsage.error).toBe(0) + } + await pool.destroy() + }) + + it('Verify that worker pool tasks queue are initialized', async () => { + const pool = new FixedClusterPool( + numberOfWorkers, + './tests/worker-files/cluster/testWorker.js' + ) + for (const workerNode of pool.workerNodes) { + expect(workerNode.tasksQueue).toBeDefined() + expect(workerNode.tasksQueue).toBeInstanceOf(Array) + expect(workerNode.tasksQueue.length).toBe(0) } await pool.destroy() }) @@ -158,20 +298,28 @@ describe('Abstract pool test suite', () => { for (let i = 0; i < numberOfWorkers * 2; i++) { promises.push(pool.execute()) } - for (const value of pool.workers.values()) { - expect(value.tasksUsage).toBeDefined() - expect(value.tasksUsage.run).toBe(0) - expect(value.tasksUsage.running).toBe(numberOfWorkers * 2) - expect(value.tasksUsage.runTime).toBe(0) - expect(value.tasksUsage.avgRunTime).toBe(0) + for (const workerNode of pool.workerNodes) { + expect(workerNode.tasksUsage).toBeDefined() + expect(workerNode.tasksUsage.run).toBe(0) + expect(workerNode.tasksUsage.running).toBe(numberOfWorkers * 2) + expect(workerNode.tasksUsage.runTime).toBe(0) + expect(workerNode.tasksUsage.runTimeHistory).toBeInstanceOf(CircularArray) + expect(workerNode.tasksUsage.runTimeHistory.length).toBe(0) + expect(workerNode.tasksUsage.avgRunTime).toBe(0) + expect(workerNode.tasksUsage.medRunTime).toBe(0) + expect(workerNode.tasksUsage.error).toBe(0) } await Promise.all(promises) - for (const value of pool.workers.values()) { - expect(value.tasksUsage).toBeDefined() - expect(value.tasksUsage.run).toBe(numberOfWorkers * 2) - expect(value.tasksUsage.running).toBe(0) - expect(value.tasksUsage.runTime).toBeGreaterThanOrEqual(0) - expect(value.tasksUsage.avgRunTime).toBeGreaterThanOrEqual(0) + for (const workerNode of pool.workerNodes) { + expect(workerNode.tasksUsage).toBeDefined() + expect(workerNode.tasksUsage.run).toBe(numberOfWorkers * 2) + expect(workerNode.tasksUsage.running).toBe(0) + expect(workerNode.tasksUsage.runTime).toBeGreaterThanOrEqual(0) + expect(workerNode.tasksUsage.runTimeHistory).toBeInstanceOf(CircularArray) + expect(workerNode.tasksUsage.runTimeHistory.length).toBe(0) + expect(workerNode.tasksUsage.avgRunTime).toBeGreaterThanOrEqual(0) + expect(workerNode.tasksUsage.medRunTime).toBe(0) + expect(workerNode.tasksUsage.error).toBe(0) } await pool.destroy() }) @@ -187,24 +335,51 @@ describe('Abstract pool test suite', () => { promises.push(pool.execute()) } await Promise.all(promises) - for (const value of pool.workers.values()) { - expect(value.tasksUsage).toBeDefined() - expect(value.tasksUsage.run).toBe(numberOfWorkers * 2) - expect(value.tasksUsage.running).toBe(0) - expect(value.tasksUsage.runTime).toBeGreaterThanOrEqual(0) - expect(value.tasksUsage.avgRunTime).toBeGreaterThanOrEqual(0) + for (const workerNode of pool.workerNodes) { + expect(workerNode.tasksUsage).toBeDefined() + expect(workerNode.tasksUsage.run).toBe(numberOfWorkers * 2) + expect(workerNode.tasksUsage.running).toBe(0) + expect(workerNode.tasksUsage.runTime).toBeGreaterThanOrEqual(0) + expect(workerNode.tasksUsage.runTimeHistory).toBeInstanceOf(CircularArray) + expect(workerNode.tasksUsage.runTimeHistory.length).toBe(0) + expect(workerNode.tasksUsage.avgRunTime).toBeGreaterThanOrEqual(0) + expect(workerNode.tasksUsage.medRunTime).toBe(0) + expect(workerNode.tasksUsage.error).toBe(0) } pool.setWorkerChoiceStrategy(WorkerChoiceStrategies.FAIR_SHARE) - for (const value of pool.workers.values()) { - expect(value.tasksUsage).toBeDefined() - expect(value.tasksUsage.run).toBe(0) - expect(value.tasksUsage.running).toBe(0) - expect(value.tasksUsage.runTime).toBe(0) - expect(value.tasksUsage.avgRunTime).toBe(0) + for (const workerNode of pool.workerNodes) { + expect(workerNode.tasksUsage).toBeDefined() + expect(workerNode.tasksUsage.run).toBe(0) + expect(workerNode.tasksUsage.running).toBe(0) + expect(workerNode.tasksUsage.runTime).toBe(0) + expect(workerNode.tasksUsage.runTimeHistory).toBeInstanceOf(CircularArray) + expect(workerNode.tasksUsage.runTimeHistory.length).toBe(0) + expect(workerNode.tasksUsage.avgRunTime).toBe(0) + expect(workerNode.tasksUsage.medRunTime).toBe(0) + expect(workerNode.tasksUsage.error).toBe(0) } await pool.destroy() }) + it("Verify that pool event emitter 'full' event can register a callback", async () => { + const pool = new DynamicThreadPool( + numberOfWorkers, + numberOfWorkers, + './tests/worker-files/thread/testWorker.js' + ) + const promises = [] + let poolFull = 0 + pool.emitter.on(PoolEvents.full, () => ++poolFull) + for (let i = 0; i < numberOfWorkers * 2; i++) { + promises.push(pool.execute()) + } + await Promise.all(promises) + // The `full` event is triggered when the number of submitted tasks at once reach the max number of workers in the dynamic pool. + // So in total numberOfWorkers + 1 times for a loop submitting up to numberOfWorkers * 2 tasks to the dynamic pool. + expect(poolFull).toBe(numberOfWorkers + 1) + await pool.destroy() + }) + it("Verify that pool event emitter 'busy' event can register a callback", async () => { const pool = new FixedThreadPool( numberOfWorkers, @@ -212,7 +387,7 @@ describe('Abstract pool test suite', () => { ) const promises = [] let poolBusy = 0 - pool.emitter.on('busy', () => poolBusy++) + pool.emitter.on(PoolEvents.busy, () => ++poolBusy) for (let i = 0; i < numberOfWorkers * 2; i++) { promises.push(pool.execute()) }